Skip to content

Commit 207d0c7

Browse files
authored
Fix globbed read/import bugs (#449)
* Split MemberNode into (Regular/Shared)MemberNode SharedMemberNode enables generating non-constant object members at run time without generating an unbounded number of Truffle root nodes. * Invert shouldRunTypeCheck to match its name * Introduce VmObjectBuilder Introduce VmObjectBuilder, a uniform way to build `VmObject`s whose `ObjectMember`s are determined at run time. Replace some manual object building code with VmObjectBuilder. Add some assertions to fix IntelliJ warnings. * Improve implementation of globbed read/import nodes - Leverage SharedMemberNode to have a single Truffle root node per globbed read/import expression instead of one root node per resolved glob element. - Remove caching in ReadGlobNode because it only works correctly if glob pattern is a string *constant*. - Remove caching in StaticReadNode (now ReadGlobElementNode/ImportGlobElementNode) because it seems unnecessary and the implementation doesn't look quite right. * Simplify code * Fix ClassCastException when reflecting on globbed import * Fix caching of globbed reads Problem: The result of a globbed read depends not only on the glob pattern but also on the current module URI. However, ResourceManager does not take this into account when caching globbed reads, causing wrong results. Changes: - Cache globbed reads per read expression. This is also how globbed imports are cached, except that import URIs are constant. - Make ReadGlobNode and ImportGlobNode code as similar as possible. - Reduce code duplication by inheriting from AbstractReadNode. - Add some tests.
1 parent d81a123 commit 207d0c7

39 files changed

+721
-500
lines changed

pkl-core/src/main/java/org/pkl/core/ast/MemberNode.java

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,22 @@
2020
import com.oracle.truffle.api.source.SourceSection;
2121
import java.util.function.Function;
2222
import org.pkl.core.ast.member.DefaultPropertyBodyNode;
23-
import org.pkl.core.ast.member.Member;
2423
import org.pkl.core.runtime.VmExceptionBuilder;
2524
import org.pkl.core.runtime.VmLanguage;
2625
import org.pkl.core.runtime.VmUtils;
2726
import org.pkl.core.util.Nullable;
2827

2928
public abstract class MemberNode extends PklRootNode {
30-
protected final Member member;
3129
@Child protected ExpressionNode bodyNode;
3230

3331
protected MemberNode(
34-
@Nullable VmLanguage language,
35-
FrameDescriptor descriptor,
36-
Member member,
37-
ExpressionNode bodyNode) {
32+
@Nullable VmLanguage language, FrameDescriptor descriptor, ExpressionNode bodyNode) {
3833

3934
super(language, descriptor);
40-
this.member = member;
4135
this.bodyNode = bodyNode;
4236
}
4337

44-
@Override
45-
public final SourceSection getSourceSection() {
46-
return member.getSourceSection();
47-
}
48-
49-
public final SourceSection getHeaderSection() {
50-
return member.getHeaderSection();
51-
}
38+
public abstract SourceSection getHeaderSection();
5239

5340
public final SourceSection getBodySection() {
5441
return bodyNode.getSourceSection();
@@ -58,11 +45,6 @@ public final ExpressionNode getBodyNode() {
5845
return bodyNode;
5946
}
6047

61-
@Override
62-
public final String getName() {
63-
return member.getQualifiedName();
64-
}
65-
6648
public final void replaceBody(Function<ExpressionNode, ExpressionNode> replacer) {
6749
bodyNode = insert(replacer.apply(bodyNode));
6850
}
@@ -72,7 +54,7 @@ protected final Object executeBody(VirtualFrame frame) {
7254
}
7355

7456
protected final VmExceptionBuilder exceptionBuilder() {
75-
return new VmExceptionBuilder().withSourceSection(member.getHeaderSection());
57+
return new VmExceptionBuilder().withSourceSection(getHeaderSection());
7658
}
7759

7860
/**
@@ -83,9 +65,9 @@ protected final VmExceptionBuilder exceptionBuilder() {
8365
* org.pkl.core.runtime.VmUtils#SKIP_TYPECHECK_MARKER}. IDEA: might be more appropriate to only
8466
* skip constraints check
8567
*/
86-
protected final boolean shouldRunTypecheck(VirtualFrame frame) {
87-
return frame.getArguments().length == 4
88-
&& frame.getArguments()[3] == VmUtils.SKIP_TYPECHECK_MARKER;
68+
protected final boolean shouldRunTypeCheck(VirtualFrame frame) {
69+
return frame.getArguments().length != 4
70+
|| frame.getArguments()[3] != VmUtils.SKIP_TYPECHECK_MARKER;
8971
}
9072

9173
public boolean isUndefined() {

pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2218,8 +2218,7 @@ public Object visitReadExpr(ReadExprContext ctx) {
22182218
return ReadOrNullNodeGen.create(createSourceSection(ctx), moduleKey, visitExpr(exprCtx));
22192219
}
22202220
assert tokenType == PklLexer.READ_GLOB;
2221-
return ReadGlobNodeGen.create(
2222-
language, createSourceSection(ctx), moduleKey, visitExpr(exprCtx));
2221+
return ReadGlobNodeGen.create(createSourceSection(ctx), moduleKey, visitExpr(exprCtx));
22232222
}
22242223

22252224
@Override
@@ -2660,8 +2659,7 @@ private AbstractImportNode doVisitImport(
26602659
}
26612660
var resolvedUri = resolveImport(importUri, importUriCtx);
26622661
if (isGlobImport) {
2663-
return new ImportGlobNode(
2664-
language, section, moduleInfo.getResolvedModuleKey(), resolvedUri, importUri);
2662+
return new ImportGlobNode(section, moduleInfo.getResolvedModuleKey(), resolvedUri, importUri);
26652663
}
26662664
return new ImportNode(language, section, moduleInfo.getResolvedModuleKey(), resolvedUri);
26672665
}

pkl-core/src/main/java/org/pkl/core/ast/expression/unary/AbstractImportNode.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@
1818
import com.oracle.truffle.api.source.SourceSection;
1919
import java.net.URI;
2020
import org.pkl.core.ast.ExpressionNode;
21+
import org.pkl.core.module.ResolvedModuleKey;
2122

2223
public abstract class AbstractImportNode extends ExpressionNode {
24+
protected final ResolvedModuleKey currentModule;
2325
protected final URI importUri;
2426

25-
AbstractImportNode(SourceSection sourceSection, URI importUri) {
27+
AbstractImportNode(SourceSection sourceSection, ResolvedModuleKey currentModule, URI importUri) {
2628
super(sourceSection);
29+
this.currentModule = currentModule;
2730
this.importUri = importUri;
2831
}
2932

30-
public URI getImportUri() {
33+
public final URI getImportUri() {
3134
return importUri;
3235
}
3336
}

pkl-core/src/main/java/org/pkl/core/ast/expression/unary/AbstractReadNode.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,33 @@
3030
import org.pkl.core.util.Nullable;
3131

3232
public abstract class AbstractReadNode extends UnaryExpressionNode {
33-
private final ModuleKey moduleKey;
33+
protected final ModuleKey currentModule;
3434

35-
protected AbstractReadNode(SourceSection sourceSection, ModuleKey moduleKey) {
35+
protected AbstractReadNode(SourceSection sourceSection, ModuleKey currentModule) {
3636
super(sourceSection);
37-
this.moduleKey = moduleKey;
37+
this.currentModule = currentModule;
3838
}
3939

4040
@TruffleBoundary
41-
protected @Nullable Object doRead(String resourceUri, VmContext context, Node readNode) {
42-
var resolvedUri = resolveResource(moduleKey, resourceUri);
43-
return context.getResourceManager().read(resolvedUri, readNode).orElse(null);
44-
}
45-
46-
private URI resolveResource(ModuleKey moduleKey, String resourceUri) {
47-
URI parsedUri;
41+
protected final URI parseUri(String resourceUri) {
4842
try {
49-
parsedUri = IoUtils.toUri(resourceUri);
43+
return IoUtils.toUri(resourceUri);
5044
} catch (URISyntaxException e) {
5145
throw exceptionBuilder()
5246
.evalError("invalidResourceUri", resourceUri)
5347
.withHint(e.getReason())
5448
.build();
5549
}
50+
}
5651

52+
@TruffleBoundary
53+
protected final @Nullable Object doRead(String resourceUri, VmContext context, Node readNode) {
54+
var resolvedUri = resolveResource(currentModule, resourceUri);
55+
return context.getResourceManager().read(resolvedUri, readNode).orElse(null);
56+
}
57+
58+
private URI resolveResource(ModuleKey moduleKey, String resourceUri) {
59+
var parsedUri = parseUri(resourceUri);
5760
var context = VmContext.get(this);
5861
URI resolvedUri;
5962
try {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.pkl.core.ast.expression.unary;
17+
18+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
19+
import com.oracle.truffle.api.frame.VirtualFrame;
20+
import com.oracle.truffle.api.source.SourceSection;
21+
import java.util.Map;
22+
import org.pkl.core.SecurityManagerException;
23+
import org.pkl.core.ast.ExpressionNode;
24+
import org.pkl.core.http.HttpClientInitException;
25+
import org.pkl.core.module.ResolvedModuleKey;
26+
import org.pkl.core.packages.PackageLoadError;
27+
import org.pkl.core.runtime.VmContext;
28+
import org.pkl.core.runtime.VmLanguage;
29+
import org.pkl.core.runtime.VmObjectLike;
30+
import org.pkl.core.runtime.VmTyped;
31+
import org.pkl.core.runtime.VmUtils;
32+
import org.pkl.core.util.GlobResolver.ResolvedGlobElement;
33+
34+
/** Used by {@link ReadGlobNode}. */
35+
public final class ImportGlobMemberBodyNode extends ExpressionNode {
36+
private final VmLanguage language;
37+
private final ResolvedModuleKey currentModule;
38+
39+
public ImportGlobMemberBodyNode(
40+
SourceSection sourceSection, VmLanguage language, ResolvedModuleKey currentModule) {
41+
super(sourceSection);
42+
this.language = language;
43+
this.currentModule = currentModule;
44+
}
45+
46+
@Override
47+
public Object executeGeneric(VirtualFrame frame) {
48+
var mapping = VmUtils.getObjectReceiver(frame);
49+
var path = (String) VmUtils.getMemberKey(frame);
50+
return importModule(mapping, path);
51+
}
52+
53+
@TruffleBoundary
54+
private VmTyped importModule(VmObjectLike mapping, String path) {
55+
@SuppressWarnings("unchecked")
56+
var globElements = (Map<String, ResolvedGlobElement>) mapping.getExtraStorage();
57+
var importUri = globElements.get(path).getUri();
58+
var context = VmContext.get(this);
59+
try {
60+
context.getSecurityManager().checkImportModule(currentModule.getUri(), importUri);
61+
var moduleToImport = context.getModuleResolver().resolve(importUri, this);
62+
return language.loadModule(moduleToImport, this);
63+
} catch (SecurityManagerException | PackageLoadError | HttpClientInitException e) {
64+
throw exceptionBuilder().withCause(e).build();
65+
}
66+
}
67+
}

pkl-core/src/main/java/org/pkl/core/ast/expression/unary/ImportGlobNode.java

Lines changed: 50 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -17,114 +17,93 @@
1717

1818
import com.oracle.truffle.api.CompilerDirectives;
1919
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
20-
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
2120
import com.oracle.truffle.api.frame.FrameDescriptor;
2221
import com.oracle.truffle.api.frame.VirtualFrame;
2322
import com.oracle.truffle.api.nodes.NodeInfo;
2423
import com.oracle.truffle.api.source.SourceSection;
2524
import java.io.IOException;
2625
import java.net.URI;
27-
import java.util.List;
28-
import org.graalvm.collections.EconomicMap;
2926
import org.pkl.core.SecurityManagerException;
30-
import org.pkl.core.ast.VmModifier;
31-
import org.pkl.core.ast.member.ObjectMember;
32-
import org.pkl.core.ast.member.UntypedObjectMemberNode;
27+
import org.pkl.core.ast.member.SharedMemberNode;
3328
import org.pkl.core.http.HttpClientInitException;
3429
import org.pkl.core.module.ResolvedModuleKey;
3530
import org.pkl.core.packages.PackageLoadError;
36-
import org.pkl.core.runtime.BaseModule;
3731
import org.pkl.core.runtime.VmContext;
3832
import org.pkl.core.runtime.VmLanguage;
3933
import org.pkl.core.runtime.VmMapping;
40-
import org.pkl.core.runtime.VmUtils;
41-
import org.pkl.core.util.EconomicMaps;
34+
import org.pkl.core.runtime.VmObjectBuilder;
4235
import org.pkl.core.util.GlobResolver;
4336
import org.pkl.core.util.GlobResolver.InvalidGlobPatternException;
44-
import org.pkl.core.util.GlobResolver.ResolvedGlobElement;
4537
import org.pkl.core.util.LateInit;
4638

4739
@NodeInfo(shortName = "import*")
4840
public class ImportGlobNode extends AbstractImportNode {
49-
private final VmLanguage language;
50-
51-
private final ResolvedModuleKey currentModule;
52-
5341
private final String globPattern;
54-
55-
@CompilationFinal @LateInit private VmMapping importedMapping;
42+
@Child @LateInit private SharedMemberNode memberNode;
43+
@CompilationFinal @LateInit private VmMapping cachedResult;
5644

5745
public ImportGlobNode(
58-
VmLanguage language,
5946
SourceSection sourceSection,
6047
ResolvedModuleKey currentModule,
6148
URI importUri,
6249
String globPattern) {
63-
super(sourceSection, importUri);
64-
this.language = language;
65-
this.currentModule = currentModule;
50+
super(sourceSection, currentModule, importUri);
6651
this.globPattern = globPattern;
6752
}
6853

69-
@TruffleBoundary
70-
private EconomicMap<Object, ObjectMember> buildMembers(
71-
FrameDescriptor frameDescriptor, List<ResolvedGlobElement> uris) {
72-
var members = EconomicMaps.<Object, ObjectMember>create();
73-
for (var entry : uris) {
74-
var readNode =
75-
new ImportNode(
76-
language, VmUtils.unavailableSourceSection(), currentModule, entry.getUri());
77-
var member =
78-
new ObjectMember(
79-
VmUtils.unavailableSourceSection(),
80-
VmUtils.unavailableSourceSection(),
81-
VmModifier.ENTRY,
82-
null,
83-
"");
84-
var memberNode = new UntypedObjectMemberNode(language, frameDescriptor, member, readNode);
85-
member.initMemberNode(memberNode);
86-
EconomicMaps.put(members, entry.getPath(), member);
54+
private SharedMemberNode getMemberNode() {
55+
if (memberNode == null) {
56+
CompilerDirectives.transferToInterpreterAndInvalidate();
57+
var language = VmLanguage.get(this);
58+
memberNode =
59+
new SharedMemberNode(
60+
sourceSection,
61+
sourceSection,
62+
"",
63+
language,
64+
new FrameDescriptor(),
65+
new ImportGlobMemberBodyNode(sourceSection, language, currentModule));
8766
}
88-
return members;
67+
return memberNode;
8968
}
9069

9170
@Override
9271
public Object executeGeneric(VirtualFrame frame) {
93-
if (importedMapping == null) {
94-
CompilerDirectives.transferToInterpreterAndInvalidate();
95-
var context = VmContext.get(this);
96-
try {
97-
var moduleKey = context.getModuleResolver().resolve(importUri);
98-
var securityManager = VmContext.get(this).getSecurityManager();
99-
if (!moduleKey.isGlobbable()) {
100-
throw exceptionBuilder()
101-
.evalError("cannotGlobUri", importUri, importUri.getScheme())
102-
.build();
103-
}
104-
var uris =
105-
GlobResolver.resolveGlob(
106-
securityManager,
107-
moduleKey,
108-
currentModule.getOriginal(),
109-
currentModule.getUri(),
110-
globPattern);
111-
var members = buildMembers(frame.getFrameDescriptor(), uris);
112-
importedMapping =
113-
new VmMapping(
114-
frame.materialize(), BaseModule.getMappingClass().getPrototype(), members);
115-
} catch (IOException e) {
116-
throw exceptionBuilder().evalError("ioErrorResolvingGlob", importUri).withCause(e).build();
117-
} catch (SecurityManagerException | HttpClientInitException e) {
118-
throw exceptionBuilder().withCause(e).build();
119-
} catch (PackageLoadError e) {
120-
throw exceptionBuilder().adhocEvalError(e.getMessage()).build();
121-
} catch (InvalidGlobPatternException e) {
72+
if (cachedResult != null) return cachedResult;
73+
74+
CompilerDirectives.transferToInterpreterAndInvalidate();
75+
var context = VmContext.get(this);
76+
try {
77+
var moduleKey = context.getModuleResolver().resolve(importUri);
78+
if (!moduleKey.isGlobbable()) {
12279
throw exceptionBuilder()
123-
.evalError("invalidGlobPattern", globPattern)
124-
.withHint(e.getMessage())
80+
.evalError("cannotGlobUri", importUri, importUri.getScheme())
12581
.build();
12682
}
83+
var resolvedElements =
84+
GlobResolver.resolveGlob(
85+
context.getSecurityManager(),
86+
moduleKey,
87+
currentModule.getOriginal(),
88+
currentModule.getUri(),
89+
globPattern);
90+
var builder = new VmObjectBuilder(resolvedElements.size());
91+
for (var entry : resolvedElements.entrySet()) {
92+
builder.addEntry(entry.getKey(), getMemberNode());
93+
}
94+
cachedResult = builder.toMapping(resolvedElements);
95+
return cachedResult;
96+
} catch (IOException e) {
97+
throw exceptionBuilder().evalError("ioErrorResolvingGlob", importUri).withCause(e).build();
98+
} catch (SecurityManagerException | HttpClientInitException e) {
99+
throw exceptionBuilder().withCause(e).build();
100+
} catch (PackageLoadError e) {
101+
throw exceptionBuilder().adhocEvalError(e.getMessage()).build();
102+
} catch (InvalidGlobPatternException e) {
103+
throw exceptionBuilder()
104+
.evalError("invalidGlobPattern", globPattern)
105+
.withHint(e.getMessage())
106+
.build();
127107
}
128-
return importedMapping;
129108
}
130109
}

0 commit comments

Comments
 (0)