Skip to content

Commit 975df75

Browse files
committed
[GR-54294] I/O permissions are required even though the module is already evaled.
PullRequest: js/3164
2 parents 098b0b7 + ee0a1d8 commit 975df75

File tree

3 files changed

+106
-30
lines changed

3 files changed

+106
-30
lines changed

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/ModuleTest.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@
4242

4343
import static org.junit.Assert.assertEquals;
4444
import static org.junit.Assert.assertTrue;
45+
import static org.junit.Assert.fail;
4546

47+
import java.io.ByteArrayOutputStream;
48+
import java.io.IOException;
4649
import java.util.Map;
4750

4851
import org.graalvm.polyglot.Context;
52+
import org.graalvm.polyglot.PolyglotException;
4953
import org.graalvm.polyglot.Source;
5054
import org.graalvm.polyglot.Value;
5155
import org.graalvm.polyglot.io.IOAccess;
@@ -108,4 +112,50 @@ public void testLiteralSource() {
108112
}
109113
}
110114

115+
@Test
116+
public void testDynamicImportFromVirtualFileSystem() throws IOException {
117+
Map<String, String> modules = Map.of("other.mjs", "export const answer = 42;");
118+
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
119+
Context context = JSTest.newContextBuilder().allowIO(IOAccess.newBuilder().fileSystem(new MockFileSystem(modules)).build()).out(out).build()) {
120+
context.eval(Source.newBuilder("js", "import('other.mjs').then(({answer}) => console.log(answer));", "test1.mjs").buildLiteral());
121+
assertEquals("42", out.toString().trim());
122+
}
123+
}
124+
125+
@Test
126+
public void testLoadFromVirtualFileSystem() {
127+
Map<String, String> modules = Map.of("other.js", "var answer = 42;");
128+
try (Context context = JSTest.newContextBuilder().allowIO(IOAccess.newBuilder().fileSystem(new MockFileSystem(modules)).build()).build()) {
129+
Value result = context.eval(Source.newBuilder("js", "load('other.js'); answer;", "test1.mjs").buildLiteral());
130+
assertTrue(result.isNumber());
131+
assertEquals(42, result.asInt());
132+
}
133+
}
134+
135+
@Test
136+
public void testImportWithoutIOPermission() {
137+
try (Context context = JSTest.newContextBuilder().allowIO(IOAccess.NONE).build()) {
138+
context.eval(Source.newBuilder("js", "export default 42;", "other.mjs").buildLiteral());
139+
Value result = context.eval(Source.newBuilder("js", "import answer from 'other.mjs'; answer;", "main.mjs").buildLiteral());
140+
assertTrue(result.isNumber());
141+
assertEquals(42, result.asInt());
142+
143+
try {
144+
context.eval(Source.newBuilder("js", "import 'non-existent.mjs';", "error.mjs").buildLiteral());
145+
fail("should have thrown");
146+
} catch (PolyglotException expected) {
147+
}
148+
}
149+
}
150+
151+
@Test
152+
public void testDynamicImportWithoutIOPermission() throws IOException {
153+
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
154+
Context context = JSTest.newContextBuilder().allowIO(IOAccess.NONE).out(out).build()) {
155+
context.eval(Source.newBuilder("js", "export const answer = 42;", "other.mjs").buildLiteral());
156+
context.eval(Source.newBuilder("js", "import('other.mjs').then(({answer}) => console.log(answer));", "test1.js").buildLiteral());
157+
context.eval(Source.newBuilder("js", "import('other.mjs').then(({answer}) => console.log(answer + 1));", "test1.mjs").buildLiteral());
158+
assertEquals("42\n43", out.toString().trim());
159+
}
160+
}
111161
}

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/polyglot/MockFileSystem.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -102,20 +102,32 @@ public Path toRealPath(Path path, LinkOption... linkOptions) throws IOException
102102

103103
@Override
104104
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
105-
throw new UnsupportedOperationException();
105+
if (!modules.containsKey(getFileNameFromPath(path))) {
106+
throw new NoSuchFileException(path.toString());
107+
}
108+
int index = attributes.indexOf(':');
109+
String viewName = index < 0 ? "basic" : attributes.substring(0, index);
110+
if (viewName.equals("basic")) {
111+
return Map.of("isRegularFile", true, "isDirectory", false);
112+
}
113+
throw new UnsupportedOperationException(attributes);
106114
}
107115

108116
@Override
109117
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
110-
Path fileNamePath = path.getFileName();
111-
String fileName = (fileNamePath == null) ? null : fileNamePath.toString();
118+
String fileName = getFileNameFromPath(path);
112119
if (modules.containsKey(fileName)) {
113120
return new ReadOnlySeekableByteArrayChannel(modules.get(fileName).getBytes(StandardCharsets.UTF_8));
114121
} else {
115122
throw new NoSuchFileException(path.toString());
116123
}
117124
}
118125

126+
private static String getFileNameFromPath(Path path) {
127+
Path fileNamePath = path.getFileName();
128+
return (fileNamePath == null) ? null : fileNamePath.toString();
129+
}
130+
119131
@Override
120132
public void checkAccess(Path path, Set<? extends AccessMode> modes, LinkOption... linkOptions) throws IOException {
121133
}

graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/objects/DefaultESModuleLoader.java

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151

5252
import com.oracle.js.parser.ir.Module.ModuleRequest;
5353
import com.oracle.truffle.api.TruffleFile;
54+
import com.oracle.truffle.api.TruffleLanguage;
5455
import com.oracle.truffle.api.source.Source;
5556
import com.oracle.truffle.api.strings.TruffleString;
5657
import com.oracle.truffle.js.lang.JavaScriptLanguage;
@@ -111,24 +112,25 @@ public JSModuleRecord resolveImportedModule(ScriptOrModule referrer, ModuleReque
111112
URI maybeUri = asURI(specifier);
112113

113114
TruffleString maybeCustomPath = realm.getCustomEsmPathMapping(refPath == null ? null : Strings.fromJavaString(refPath), specifierTS);
115+
TruffleLanguage.Env env = realm.getEnv();
114116
if (maybeCustomPath != null) {
115117
canonicalPath = maybeCustomPath.toJavaStringUncached();
116-
moduleFile = realm.getEnv().getPublicTruffleFile(canonicalPath).getCanonicalFile();
118+
moduleFile = getCanonicalFileIfExists(env.getPublicTruffleFile(canonicalPath), env);
117119
} else {
118120
if (refPath == null) {
119121
if (maybeUri != null) {
120-
moduleFile = realm.getEnv().getPublicTruffleFile(maybeUri);
122+
moduleFile = env.getPublicTruffleFile(maybeUri);
121123
} else {
122-
moduleFile = realm.getEnv().getPublicTruffleFile(specifier);
124+
moduleFile = env.getPublicTruffleFile(specifier);
123125
}
124126
} else {
125-
TruffleFile refFile = realm.getEnv().getPublicTruffleFile(refPath);
127+
TruffleFile refFile = env.getPublicTruffleFile(refPath);
126128
if (maybeUri != null) {
127-
String uriFile = realm.getEnv().getPublicTruffleFile(maybeUri).getCanonicalFile().getPath();
129+
String uriFile = env.getPublicTruffleFile(maybeUri).getCanonicalFile().getPath();
128130
moduleFile = refFile.resolveSibling(uriFile);
129131
} else {
130-
if (bareSpecifierDirectLookup(specifier)) {
131-
moduleFile = realm.getEnv().getPublicTruffleFile(specifier);
132+
if (!env.isFileIOAllowed() || bareSpecifierDirectLookup(specifier)) {
133+
moduleFile = env.getPublicTruffleFile(specifier);
132134
} else {
133135
moduleFile = refFile.resolveSibling(specifier);
134136
}
@@ -185,27 +187,20 @@ private boolean bareSpecifierDirectLookup(String specifier) {
185187
}
186188

187189
protected JSModuleRecord loadModuleFromUrl(ScriptOrModule referrer, ModuleRequest moduleRequest, TruffleFile maybeModuleFile, String maybeCanonicalPath) throws IOException {
188-
JSModuleRecord existingModule;
189-
TruffleFile moduleFile;
190+
TruffleFile moduleFile = maybeModuleFile;
190191
String canonicalPath;
192+
TruffleLanguage.Env env = realm.getEnv();
191193
if (maybeCanonicalPath == null) {
192-
if (!maybeModuleFile.exists()) {
193-
// check whether the moduleFile was loaded already (as literal source)
194-
// before trying to invoke getCanonicalFile() (which would fail)
195-
canonicalPath = maybeModuleFile.getPath();
196-
existingModule = moduleMap.get(canonicalPath);
197-
if (existingModule != null) {
198-
return existingModule;
199-
}
200-
}
201-
moduleFile = maybeModuleFile.getCanonicalFile();
202-
canonicalPath = moduleFile.getPath();
194+
/*
195+
* We can only canonicalize the path if I/O is allowed and the file exists; otherwise,
196+
* the lookup may still succeed if the module was loaded already (as literal source).
197+
*/
198+
canonicalPath = getCanonicalFileIfExists(moduleFile, env).getPath();
203199
} else {
204-
moduleFile = maybeModuleFile;
205200
canonicalPath = maybeCanonicalPath;
206201
}
207202

208-
existingModule = moduleMap.get(canonicalPath);
203+
JSModuleRecord existingModule = moduleMap.get(canonicalPath);
209204
if (existingModule != null) {
210205
return existingModule;
211206
}
@@ -267,13 +262,20 @@ private String getCanonicalPath(Source source) {
267262
canonicalPath = source.getName();
268263
} else {
269264
try {
270-
if (realm.getEnv().getFileNameSeparator().equals("\\") && path.startsWith("/")) {
265+
TruffleLanguage.Env env = realm.getEnv();
266+
if (env.getFileNameSeparator().equals("\\") && path.startsWith("/")) {
271267
// on Windows, remove first "/" from /c:/test/dir/ style paths
272268
path = path.substring(1);
273269
}
274-
TruffleFile moduleFile = realm.getEnv().getPublicTruffleFile(path);
275-
if (moduleFile.exists()) {
276-
canonicalPath = moduleFile.getCanonicalFile().getPath();
270+
TruffleFile moduleFile = env.getPublicTruffleFile(path);
271+
if (env.isFileIOAllowed() && moduleFile.exists()) {
272+
try {
273+
canonicalPath = moduleFile.getCanonicalFile().getPath();
274+
} catch (NoSuchFileException ex) {
275+
// The file may have been deleted between exists() and getCanonicalFile().
276+
// We handle this race condition as if the file did not exist.
277+
canonicalPath = path;
278+
}
277279
} else {
278280
// Source with a non-existing path but with a content.
279281
canonicalPath = path;
@@ -284,4 +286,16 @@ private String getCanonicalPath(Source source) {
284286
}
285287
return canonicalPath;
286288
}
289+
290+
private static TruffleFile getCanonicalFileIfExists(TruffleFile file, TruffleLanguage.Env env) throws IOException {
291+
if (env.isFileIOAllowed() && file.exists()) {
292+
try {
293+
return file.getCanonicalFile();
294+
} catch (NoSuchFileException ex) {
295+
// The file may have been deleted between exists() and getCanonicalFile().
296+
// We handle this race condition as if the file did not exist.
297+
}
298+
}
299+
return file;
300+
}
287301
}

0 commit comments

Comments
 (0)