Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#57: cage recursion #59

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
99 changes: 99 additions & 0 deletions _posts/2024/03/2024-03-18-cage-recursion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
layout: post
date: 2024-03-18
title: "Recursion in cage"
author: levBagryansky
---

The main difference between `cage` and `memory` is how the object is written.
When dataizing, `memory.write` datazies its argument as well. Whereas
`cage.write` just saves a reference to this object. As a result, the dataization
of the `cage` may turn out to be recursive. Unfortunately, this error is often not obvious.

In this post we will tell you how we solved this problem.

<!--more-->

# A little about `cage`

In [this](https://news.eolang.org/2023-08-04-storing-objects-formed-differently-into-cage.html) post we
raised the topic of `cage`. There we told how to write into `cage`, and what limitations there are -
objects of the same `forma` can be written into one cage.
Here is a simple example of using a `cage`:

```eo
# Cage conatains int.
[] > cage-contains-int
cage 0 > my-int
eq. > @
seq
*
my-int.write 42
my-int
42
```

But you might also accidentally write `cage` inside `cage`, leading to infinite recursion.
```eo
[] > catches-stack-overflow
cage > cge
0.plus
seq > @
*
cge.write
0.plus cge
cge
```
In order to provide everywhere the same `forma` we use `0.plus`.
When `cge` is dataizing it dataizes `0.plus cge`. It takes attribute `plus` from object `0`. `int.plus`
tries to dataize its argument `cge` inside. We have achieved our goal: infinite recursion is obtained.
The first attempt is to detect such cases even in compilation. But generally speaking, the presence
of `cage` inside `cage` is not a mistake, because such a program may well be correct.
Dataization of `cage` can several times pas through the `cage` and only then return some value.
This eo illustrates such scenario (it does not work with latest version but idea of deep `cage` is clear):
```eo
# correct-cage-recursion.
[] > correct-cage-recursion
cage > cge
if.
i.as-bool
TRUE
seq
*
i.write
TRUE
$.cge
memory > i
FALSE
cge > @

```
Copy link
Member Author

Choose a reason for hiding this comment

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

@maxonfjvipon this is more illustrating example, not working code,

Copy link
Member

Choose a reason for hiding this comment

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

@levBagryansky why doesn't it work? Everything seems fine

Copy link
Member Author

Choose a reason for hiding this comment

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

@maxonfjvipon I get an error EOcorrect_cage_recursionTest.works:124->PhDefault.attr:250->PhDefault.attr:248->PhDefault.attr:250 » Ex D="Error at "EOorg.EOeolang.EObytes$EOas_bool#φ" attribute; caused by Can't #copy() package object 'bytes'" here and don't know why

Therefore, it is necessary to detect recursion in runtime somehow. Until recently `EOcage::lambda`
was the following:
```java
@Override
public Phi lambda() {
return this.attr("enclosure").get();
}
```
Don't worry about this piece of code from our source, it's easy to understand: During `cage` dataization,
it returns a reference to the object stored in it- `enclosure`. In order to trace how many times
`cage` dataization uses itself we wrap `enclosure` in the `PhTracedEnclosure`:
```java
@Override
public Phi lambda() {
return new PhTracedEnclosure(this.attr("enclosure").get(), this);
}
```
`PhTracedEnclosure` keeps `enclosure` and `cage` that it traces. It also has something like **static**
`Map<Phi, Integer>`, where it counts how many times `enclosure` was used: It increments the counter of `cage`
before retrieving its attributes and decrements it after successful retrieval. Therefore, in the case of recursion,
the counter doesn't decrease, but only increases. When the counter reaches a certain threshold, an exception is thrown.

The value of such a threshold is called `"EO_MAX_CAGE_RECURSION_DEPTH"`, and it is a java property
which you can set by yourself. We chose `100` to be default value. Then `cage` can be
recursive enough, but it will detect problem before throwing of `StackOverflowError`. You can
set environment variable via `-D` flag.

Keep exploring EO, and remember, there's always more to discover. Stay connected for future updates,
and We look forward to catching up with you soon!