diff --git a/eo-runtime/src/main/java/EOorg/EOeolang/EOcage.java b/eo-runtime/src/main/java/EOorg/EOeolang/EOcage.java index a55384274d..3a7d841d6b 100644 --- a/eo-runtime/src/main/java/EOorg/EOeolang/EOcage.java +++ b/eo-runtime/src/main/java/EOorg/EOeolang/EOcage.java @@ -34,6 +34,7 @@ import org.eolang.Attr; import org.eolang.Data; import org.eolang.PhDefault; +import org.eolang.PhTracedEnclosure; import org.eolang.Phi; import org.eolang.Versionized; import org.eolang.Volatile; @@ -62,7 +63,7 @@ public EOcage(final Phi sigma) { @Override public Phi lambda() { - return this.attr("enclosure").get(); + return new PhTracedEnclosure(this.attr("enclosure").get(), this.hashCode()); } /** @@ -108,4 +109,5 @@ public Phi lambda() { return new Data.ToPhi(true); } } + } diff --git a/eo-runtime/src/main/java/org/eolang/PhTracedEnclosure.java b/eo-runtime/src/main/java/org/eolang/PhTracedEnclosure.java new file mode 100644 index 0000000000..cd96fd4436 --- /dev/null +++ b/eo-runtime/src/main/java/org/eolang/PhTracedEnclosure.java @@ -0,0 +1,147 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2024 Objectionary.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.eolang; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Supplier; + +/** + * Class to trace if the cage got into recursion during the dataization. + * @since 0.36 + * @todo #2836:60min Add a new parameter of recursion depth. This parameter + * should be set by user via pom.xml. We can make DATAIZING_CAGES a + * Map and count how many times the cage was met. + * @todo #2836:60min Make the class thread safe. It has private static + * field which can be accessed from differ thread and is not thread safe. + * Needs to synchronize this field. + */ +@Versionized +public final class PhTracedEnclosure implements Phi { + + /** + * Cages that are currently dataizing. If one cage is datazing and + * it needs to be dataized inside current dataizing, the cage will be here. + */ + private static final Set DATAIZING_CAGES = new HashSet<>(); + + /** + * Enclosure. + */ + private final Phi enclosure; + + /** + * Vertex of cage where the {@link PhTracedEnclosure#enclosure} + * was retrieved. + */ + private final int cage; + + /** + * Ctor. + * @param enclosure Enclosure. + * @param cage Vertex of source cage. + */ + public PhTracedEnclosure(final Phi enclosure, final int cage) { + this.enclosure = enclosure; + this.cage = cage; + } + + @Override + public Phi copy() { + return new PhTracedEnclosure(this.enclosure.copy(), this.cage); + } + + @Override + public String locator() { + return this.enclosure.locator(); + } + + @Override + public String forma() { + return this.enclosure.forma(); + } + + @Override + public String φTerm() { + return this.enclosure.φTerm(); + } + + @Override + public Attr attr(final int pos) { + return new PhTracedEnclosure.TracingWhileGetting( + () -> this.enclosure.attr(pos) + ).get(); + } + + @Override + public Attr attr(final String name) { + return new PhTracedEnclosure.TracingWhileGetting( + () -> this.enclosure.attr(name) + ).get(); + } + + @Override + public int hashCode() { + return this.enclosure.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + return obj instanceof Phi && this.hashCode() == obj.hashCode(); + } + + /** + * Supplier that traces the cage while gets. + * @since 0.36 + */ + private final class TracingWhileGetting implements Supplier { + + /** + * Supplies the {@link Attr}. + */ + private final Supplier attr; + + /** + * Ctor. + * @param attr Supplier of the {@link Attr}. + */ + private TracingWhileGetting(final Supplier attr) { + this.attr = attr; + } + + @Override + public Attr get() { + if (PhTracedEnclosure.DATAIZING_CAGES.contains(PhTracedEnclosure.this.cage)) { + throw new ExFailure( + "The cage %s is already dataizing", + PhTracedEnclosure.this.cage + ); + } + PhTracedEnclosure.DATAIZING_CAGES.add(PhTracedEnclosure.this.cage); + final Attr ret = this.attr.get(); + PhTracedEnclosure.DATAIZING_CAGES.remove(PhTracedEnclosure.this.cage); + return ret; + } + } +} diff --git a/eo-runtime/src/test/eo/org/eolang/cage-tests.eo b/eo-runtime/src/test/eo/org/eolang/cage-tests.eo index e81ed88dd2..13d265e129 100644 --- a/eo-runtime/src/test/eo/org/eolang/cage-tests.eo +++ b/eo-runtime/src/test/eo/org/eolang/cage-tests.eo @@ -200,3 +200,22 @@ nop cge.eq 10 TRUE + +# Check that cage stack leads to error. Just prints the error +# message if passes. +[] > catches-stack-overflow + cage > cge + 0.plus + try > @ + [] + seq > @ + * + cge.write + 0.plus cge + cge + FALSE + [stack-overflow-message] + QQ.io.stdout > @ + stack-overflow-message + [] + FALSE > @ diff --git a/eo-runtime/src/test/java/EOorg/EOeolang/EOcageTest.java b/eo-runtime/src/test/java/EOorg/EOeolang/EOcageTest.java index 2e2f18b91d..2bac2c0d6d 100644 --- a/eo-runtime/src/test/java/EOorg/EOeolang/EOcageTest.java +++ b/eo-runtime/src/test/java/EOorg/EOeolang/EOcageTest.java @@ -30,6 +30,7 @@ import org.eolang.AtFree; import org.eolang.Data; import org.eolang.Dataized; +import org.eolang.ExAbstract; import org.eolang.PhCopy; import org.eolang.PhDefault; import org.eolang.PhMethod; @@ -170,9 +171,9 @@ void evaluatesLazily() { EOcageTest.writeTo(first, new Data.ToPhi(3L)); final Phi second = new EOcage(Phi.Φ); EOcageTest.writeTo(second, new Data.ToPhi(5L)); - final Phi cage = new EOcage(Phi.Φ); + final Phi sum = new EOcage(Phi.Φ); EOcageTest.writeTo( - cage, + sum, new PhWith( new PhCopy(new PhMethod(first, "plus")), 0, second @@ -181,7 +182,7 @@ void evaluatesLazily() { EOcageTest.writeTo(first, new Data.ToPhi(1L)); EOcageTest.writeTo(second, new Data.ToPhi(9L)); MatcherAssert.assertThat( - new Dataized(cage).take(Long.class), + new Dataized(sum).take(Long.class), Matchers.equalTo(10L) ); } @@ -255,6 +256,17 @@ void writesBoundedCopyOfTheSameBase() { ); } + @Test + void throwsExceptionIfRecursion() { + final Phi cage = new EOcage(Phi.Φ); + writeTo(cage, cage); + Assertions.assertThrows( + ExAbstract.class, + new Dataized(cage)::take, + "We expect the exception to be thrown since we have recursion here" + ); + } + private static void writeTo(final Phi cage, final Phi obj) { new Dataized( new PhWith(