diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/TestBase.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/TestBase.java index 77af80b0fa2..96bd3a42df8 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/TestBase.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/TestBase.java @@ -30,6 +30,7 @@ import org.enso.pkg.QualifiedName; import org.enso.polyglot.PolyglotContext; import org.enso.polyglot.RuntimeOptions; import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Context.Builder; import org.graalvm.polyglot.Language; import org.graalvm.polyglot.Source; import org.graalvm.polyglot.Value; @@ -227,14 +228,16 @@ prefer-local-libraries: true * Tests running the project located in the given {@code projDir}. Is equal to running {@code enso * --run }. * + * @param ctxBuilder A context builder that might be initialized with some specific options. * @param projDir Root directory of the project. * @param resultConsumer Any action that is to be evaluated on the result of running the {@code * main} method */ - protected void testProjectRun(Path projDir, Consumer resultConsumer) { + protected void testProjectRun( + Context.Builder ctxBuilder, Path projDir, Consumer resultConsumer) { assert projDir.toFile().exists() && projDir.toFile().isDirectory(); try (var ctx = - defaultContextBuilder() + ctxBuilder .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) .option(RuntimeOptions.STRICT_ERRORS, "true") .option(RuntimeOptions.DISABLE_IR_CACHES, "true") @@ -249,6 +252,17 @@ prefer-local-libraries: true } } + /** + * Just a wrapper for {@link TestBase#testProjectRun(Builder, Path, Consumer)}. + * + * @param projDir Root directory of the project. + * @param resultConsumer Any action that is to be evaluated on the result of running the {@code + * main} method + */ + protected void testProjectRun(Path projDir, Consumer resultConsumer) { + testProjectRun(defaultContextBuilder(), projDir, resultConsumer); + } + /** A simple structure corresponding to an Enso module. */ public record SourceModule(QualifiedName name, String code) {} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/PrivateAccessTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/privateaccess/PrivateAccessTest.java similarity index 80% rename from engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/PrivateAccessTest.java rename to engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/privateaccess/PrivateAccessTest.java index 089344fcf8a..602c41e0a87 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/PrivateAccessTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/privateaccess/PrivateAccessTest.java @@ -1,4 +1,4 @@ -package org.enso.interpreter.test; +package org.enso.interpreter.test.privateaccess; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; @@ -10,6 +10,7 @@ import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; +import org.enso.interpreter.test.TestBase; import org.enso.interpreter.util.ScalaConversions; import org.enso.polyglot.PolyglotContext; import org.enso.polyglot.RuntimeOptions; @@ -47,20 +48,13 @@ public class PrivateAccessTest extends TestBase { main = My_Type.Cons 42 """; var projDir = createProject("My_Project", mainSrc, tempFolder); - var mainSrcPath = projDir.resolve("src").resolve("Main.enso"); - try (var ctx = - defaultContextBuilder() - .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) - .build()) { - var polyCtx = new PolyglotContext(ctx); - var mainMod = polyCtx.evalModule(mainSrcPath.toFile()); - var assocType = mainMod.getAssociatedType(); - var mainMethod = mainMod.getMethod(assocType, "main").get(); - var res = mainMethod.execute(); - assertThat(res.hasMember("data"), is(false)); - assertThat(res.canInvokeMember("data"), is(false)); - assertThat(res.getMember("data"), is(nullValue())); - } + testProjectRun( + projDir, + res -> { + assertThat(res.hasMember("data"), is(false)); + assertThat(res.canInvokeMember("data"), is(false)); + assertThat(res.getMember("data"), is(nullValue())); + }); } @Test @@ -127,19 +121,12 @@ public class PrivateAccessTest extends TestBase { _ -> 0 """; var projDir = createProject("My_Project", mainSrc, tempFolder); - var mainSrcPath = projDir.resolve("src").resolve("Main.enso"); - try (var ctx = - defaultContextBuilder() - .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) - .build()) { - var polyCtx = new PolyglotContext(ctx); - var mainMod = polyCtx.evalModule(mainSrcPath.toFile()); - var assocType = mainMod.getAssociatedType(); - var mainMethod = mainMod.getMethod(assocType, "main").get(); - var res = mainMethod.execute(); - assertThat(res.isNumber(), is(true)); - assertThat(res.asInt(), is(42)); - } + testProjectRun( + projDir, + res -> { + assertThat(res.isNumber(), is(true)); + assertThat(res.asInt(), is(42)); + }); } /** Tests that pattern matching on private constructors fails in compilation. */ diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/privateaccess/PrivateCheckDisabledTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/privateaccess/PrivateCheckDisabledTest.java new file mode 100644 index 00000000000..e97ce52a59f --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/privateaccess/PrivateCheckDisabledTest.java @@ -0,0 +1,40 @@ +package org.enso.interpreter.test.privateaccess; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.IOException; +import org.enso.interpreter.test.TestBase; +import org.enso.polyglot.RuntimeOptions; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class PrivateCheckDisabledTest extends TestBase { + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void privateCtorCanBeAccessedWhenPrivateCheckIsDisabled() throws IOException { + var libSrc = """ + type T + private Cons data + """; + createProject("Lib", libSrc, tempFolder); + var mainSrc = + """ + from local.Lib import T + main = + obj = T.Cons 42 + obj.data + """; + var mainDir = createProject("Main", mainSrc, tempFolder); + var ctxBuilder = defaultContextBuilder().option(RuntimeOptions.DISABLE_PRIVATE_CHECK, "true"); + testProjectRun( + ctxBuilder, + mainDir, + res -> { + assertThat(res.isNumber(), is(true)); + assertThat(res.asInt(), is(42)); + }); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/InvokeFunctionNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/InvokeFunctionNode.java index 07dfa393956..1d15d15f9c7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/InvokeFunctionNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/InvokeFunctionNode.java @@ -1,5 +1,6 @@ package org.enso.interpreter.node.callable.dispatch; +import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.TruffleFile; import com.oracle.truffle.api.dsl.Cached; @@ -113,6 +114,16 @@ public abstract class InvokeFunctionNode extends BaseNode { return new PanicException(err, this); } + private void ensureFunctionIsAccessible(Function function, FunctionSchema functionSchema) { + var isPrivateCheckDisabled = getContext().isPrivateCheckDisabled(); + CompilerAsserts.compilationConstant(isPrivateCheckDisabled); + if (!isPrivateCheckDisabled + && functionSchema.isProjectPrivate() + && !isInSameProject(function)) { + throw makePrivateAccessPanic(function); + } + } + @Specialization( guards = {"!getContext().isInlineCachingDisabled()", "function.getSchema() == cachedSchema"}, limit = Constants.CacheSizes.ARGUMENT_SORTER_NODE) @@ -130,9 +141,7 @@ public abstract class InvokeFunctionNode extends BaseNode { "build(argumentMapping, getDefaultsExecutionMode(), getArgumentsExecutionMode()," + " getTailStatus())") CurryNode curryNode) { - if (cachedSchema.isProjectPrivate() && !isInSameProject(function)) { - throw makePrivateAccessPanic(function); - } + ensureFunctionIsAccessible(function, cachedSchema); ArgumentSorterNode.MappedArguments mappedArguments = mappingNode.execute(callerFrame, function, state, arguments); @@ -174,9 +183,7 @@ public abstract class InvokeFunctionNode extends BaseNode { Object[] arguments, @Cached IndirectArgumentSorterNode mappingNode, @Cached IndirectCurryNode curryNode) { - if (function.getSchema().isProjectPrivate() && !isInSameProject(function)) { - throw makePrivateAccessPanic(function); - } + ensureFunctionIsAccessible(function, function.getSchema()); CallArgumentInfo.ArgumentMapping argumentMapping = CallArgumentInfo.ArgumentMappingBuilder.generate(function.getSchema(), getSchema());