Skip to content

Commit

Permalink
Merge pull request #24 from nikololiahim/docs
Browse files Browse the repository at this point in the history
Added documentation to mutual recursion analyzer
  • Loading branch information
fizruk authored Jan 18, 2022
2 parents c894c93 + 438635d commit f1394b1
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Odin (object dependency inspector) — a static analyzer for [EO programming lan

> The project is still in active development stage and might not be usable or fully documented yet.
# Documentation
Documentation for the mutual recursion analyzer is available [here](docs/analysis/mutual_recursion_analyzer.md)

# Running

To run the project you will need [sbt](https://www.scala-sbt.org/1.x/docs/Setup.html) and JDK 8+.
Expand Down
179 changes: 179 additions & 0 deletions docs/analysis/mutual_recursion_analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Odin: Mutual Recursion Analyzer

The analyzer is capable of detecting mutual recursion caused by method redefinition during inheritance.

## Identifiable patterns
Only the following patterns are recognized by the algorithm:

### 1. Object declarations:
```
[] > name
{0 or 1 decoratee}
{>= 0 method declarations}
{>= 0 object declarations}
```

### 2. Decoratee
```
simple_name > @
or
an.attribute.of.some.object > @
```

### 3. Method declarations:
```
[self {>= 0 params}] > methodName
{>= 0 method calls} > @
```

### 4. Method calls:
```
self.methodName self {>= 0 params}
```



## Known Shortcomings

### 1. Some decorations are ignored.

The algorithm supports only two kinds of decorations: simple applications, like `fruit > @`, and attribute decorations, like `fruits.citrus.orange > @`. **All the other patterns are ignored, thus an object containing such pattern is considered to not have a `@` attribute at all!**

For example, in this program:
```
[] > baobab
1.add 2 > @
[self] > stall
```
the statement `1.add 2 > @` is ignored so,
the algorithm 'sees' this program like so:
```
[] > baobab
[self] > stall
```

### 2. Unreachable code
The algorithm takes into consideration every possible method call that is present in the body, whether it is actually called does not matter. For example, in method `zen`:
```
[self] > zen
if. > @
false
self.no self
self.yes self
```
The algorithm will consider both `no` and `yes` method calls, even though `no` is never called.

The same goes for unused local definitions. In EO, "method" is an object containing a special `@` (phi) attribute. When the method is called:
```
self.method self
```

it is the object labelled by `@` that gets executed. Any other local bindings that are not used in the binding associated with `@` are ignored. For example:
```
[self] > m
fib 100000 > huge_computation!
1.add 1 > @
```

Only `1.add 1` will be computed when method `m` is called. The `huge_computatuion` will never be performed (since it is never called in the declaration of `@`).

That being said, if `huge_computation` is a method call, say:
```
[self] > m
# a valid method call now!
self.fib self 100000 > huge_computation!
1.add 1 > @
```

The algorithm would still consider it as "called". In other words, it will be a part of the analyzer output if it is a part of some mutual recursion chain.

Furthermore, the algorithm does not run any checks for the presence of `@` attribute. As a result, a method that would produce a runtime error when called, like:

```
[self] > m
self.fib self 100000 > huge_computation!
# this is no longer `@`
1.add 1 > two
```

is still considered by the algorithm.

### 3. Inheritance chains with cycles
Even though the following code is perfectly valid in EO:
```
[] > a
b > @
[] > b
a > @
```

This code (and the similar variations) will cause the analyzer to fail with `StackOverflowError`.

## Algorithm description
1. Parse the source code to build its AST.

2. Create a tree of partial objects that preserves the object nestedness. Basically, collect all the information that can be collect within a single pass.

3. This tree is then refined to create a complete representation of the program, with method redefinition.

4. Traverse the callgraphs finding cycles that involve methods coming from multiple objects. For example, the callchain ***a.f -> a.g -> a.f*** will not be in the output, meanwhile the callchain ***a.f -> b.g -> a.f*** will be a part of it.


## Expected output

Example input:

```
[] > a
b > @
[self] > f
self > @
[] > b
[] > d
a > @
[self] > g
self > @
[] > c
[] > e
a > @
[self] > h
self.f self > @
[] > t
c.e > @
[self] > f
self.h self > @
```

Example output (prettified):
```
t:
t.h (was last redefined in "c.e") ->
t.f ->
t.h (was last redefined in "c.e")
```

## Possible errors
The analyzer can fail with the following errors:

1. The analyzer encounters decoratee objects that are not defined.
```
[] > a
b > @
```

> <span style="color:red"> `Exception in thread "main" java.lang.Exception: Parent (or decoratee) object "b" of object "a" is specified, but not defined in the program!` </span>.


2. The analyzer encounters calls of non-existent methods.

```
[] > a
[self] > f
self.non_existent self > @
```
> <span style="color:red"> `Exception in thread "main" java.lang.Exception: Method "non_existent" was called from the object "a", although it is not defined there!` </span>.

1 comment on commit f1394b1

@0pdd
Copy link
Member

@0pdd 0pdd commented on f1394b1 Jan 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't able to retrieve PDD puzzles from the code base and submit them to GitHub. If you think that it's a bug on our side, please submit it to yegor256/0pdd:

set -x && set -e && set -o pipefail && cd /tmp/0pdd20211201-12-1eyu7d6/polystat/odin && pdd -v -f /tmp/20220118-18130-16hnqtq [1]: + set -e + set -o pipefail + cd /tmp/0pdd20211201-12-1eyu7d6/polystat/odin + pdd -v -f /tmp/20220118-18130-16hnqtq My version is 0.20.6 Ruby version is 2.6.0 at...

Please, copy and paste this stack trace to GitHub:

UserError
set -x && set -e && set -o pipefail && cd /tmp/0pdd20211201-12-1eyu7d6/polystat/odin && pdd -v -f /tmp/20220118-18130-16hnqtq [1]:
+ set -e
+ set -o pipefail
+ cd /tmp/0pdd20211201-12-1eyu7d6/polystat/odin
+ pdd -v -f /tmp/20220118-18130-16hnqtq

My version is 0.20.6
Ruby version is 2.6.0 at x86_64-linux
Reading /tmp/0pdd20211201-12-1eyu7d6/polystat/odin
94 file(s) found, 122 excluded
Reading interop/src/main/scala/org/polystat/odin/interop/java/OdinAnalysisErrorInterop.scala...
Reading interop/src/main/scala/org/polystat/odin/interop/java/EOOdinAnalyzer.scala...
Reading version.sbt...
Reading utils/src/main/scala/org/polystat/odin/utils/text.scala...
Reading utils/src/main/scala/org/polystat/odin/utils/files.scala...
Reading .gitignore...
Reading README.md...
Reading docs/analysis/mutual_recursion_analyzer.md...
Reading analysis/src/main/scala/org/polystat/odin/analysis/EOOdinAnalyzer.scala...
Reading analysis/src/main/scala/org/polystat/odin/analysis/mutualrec/naive/services/MethodAttribute.scala...
Reading analysis/src/main/scala/org/polystat/odin/analysis/mutualrec/naive/services/TopLevelObjects.scala...
Reading analysis/src/main/scala/org/polystat/odin/analysis/mutualrec/naive/services/TopLevelObject.scala...
Reading analysis/src/main/scala/org/polystat/odin/analysis/mutualrec/naive/package.scala...
Reading analysis/src/main/scala/org/polystat/odin/analysis/mutualrec/naive/exceptions.scala...
Reading analysis/src/main/scala/org/polystat/odin/analysis/mutualrec/advanced/Analyzer.scala...
Reading analysis/src/main/scala/org/polystat/odin/analysis/mutualrec/advanced/Program.scala...
Reading analysis/src/main/scala/org/polystat/odin/analysis/mutualrec/advanced/CallGraph.scala...
Reading analysis/src/test/resources/mutualrec/ignored/if_false.eo...
Reading analysis/src/test/resources/mutualrec/ignored/method_closure.eo...
Reading analysis/src/test/resources/mutualrec/with_recursion/nested_eo.eo...
Reading analysis/src/test/resources/mutualrec/with_recursion/mutual_rec_somewhere.eo...
Reading analysis/src/test/resources/mutualrec/with_recursion/nested_objects.eo...
Reading analysis/src/test/resources/mutualrec/with_recursion/realistic.eo...
Reading analysis/src/test/resources/mutualrec/no_recursion/no_cycles.eo...
Reading analysis/src/test/resources/mutualrec/no_recursion/two_methods_recursion.eo...
Reading analysis/src/test/resources/mutualrec/no_recursion/one_method_recursion.eo...
Reading analysis/src/test/resources/mutualrec/no_recursion/method_signature.eo...
Reading analysis/src/test/resources/mutualrec/failing/decoration.eo...
Reading analysis/src/test/scala/org/polystat/odin/analysis/CallGraphTests.scala...
Reading analysis/src/test/scala/org/polystat/odin/analysis/MutualrecTests.scala...
Reading analysis/src/test/scala/org/polystat/odin/analysis/gens/MutualRecursionTestGen.scala...
Reading backends/eolang/src/main/scala/org/polystat/odin/backend/eolang/ToEO.scala...
ERROR: backends/eolang/src/main/scala/org/polystat/odin/backend/eolang/ToEO.scala; puzzle at line #304; TODO found, but puzzle can't be parsed, most probably because TODO is not followed by a puzzle marker, as this page explains: https://github.com/yegor256/pdd#how-to-format
If you can't understand the cause of this issue or you don't know how to fix it, please submit a GitHub issue, we will try to help you: https://github.com/yegor256/pdd/issues. This tool is still in its beta version and we will appreciate your feedback. Here is where you can find more documentation: https://github.com/yegor256/pdd/blob/master/README.md.
Exit code is 1

/app/objects/git_repo.rb:66:in `rescue in block in xml'
/app/objects/git_repo.rb:63:in `block in xml'
/app/vendor/ruby-2.6.0/lib/ruby/2.6.0/tempfile.rb:295:in `open'
/app/objects/git_repo.rb:62:in `xml'
/app/objects/puzzles.rb:36:in `deploy'
/app/objects/job.rb:38:in `proceed'
/app/objects/job_starred.rb:33:in `proceed'
/app/objects/job_recorded.rb:32:in `proceed'
/app/objects/job_emailed.rb:35:in `proceed'
/app/objects/job_commiterrors.rb:36:in `proceed'
/app/objects/job_detached.rb:48:in `exclusive'
/app/objects/job_detached.rb:36:in `block in proceed'
/app/objects/job_detached.rb:36:in `fork'
/app/objects/job_detached.rb:36:in `proceed'
/app/0pdd.rb:357:in `block in <top (required)>'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1675:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1675:in `block in compile!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1013:in `block (3 levels) in route!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1032:in `route_eval'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1013:in `block (2 levels) in route!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1061:in `block in process_route'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1059:in `catch'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1059:in `process_route'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1011:in `block in route!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1008:in `each'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1008:in `route!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1129:in `block in dispatch!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `block in invoke'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `catch'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `invoke'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1124:in `dispatch!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:939:in `block in call!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `block in invoke'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `catch'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `invoke'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:939:in `call!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:929:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/xss_header.rb:18:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/path_traversal.rb:16:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/json_csrf.rb:26:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/base.rb:50:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/base.rb:50:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/frame_options.rb:31:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/logger.rb:17:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/common_logger.rb:38:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:253:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:246:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/head.rb:12:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/method_override.rb:24:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:216:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1991:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1542:in `block in call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1769:in `synchronize'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1542:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/webrick.rb:95:in `service'
/app/vendor/ruby-2.6.0/lib/ruby/2.6.0/webrick/httpserver.rb:140:in `service'
/app/vendor/ruby-2.6.0/lib/ruby/2.6.0/webrick/httpserver.rb:96:in `run'
/app/vendor/ruby-2.6.0/lib/ruby/2.6.0/webrick/server.rb:307:in `block in start_thread'

Please sign in to comment.