package com.oracle.truffle.js.parser;
import static com.oracle.truffle.js.lang.JavaScriptLanguage.MODULE_MIME_TYPE;
import static com.oracle.truffle.js.lang.JavaScriptLanguage.MODULE_SOURCE_NAME_SUFFIX;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import com.oracle.js.parser.ir.Expression;
import com.oracle.js.parser.ir.Module;
import com.oracle.js.parser.ir.Module.ExportEntry;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.NodeFactory;
import com.oracle.truffle.js.nodes.ScriptNode;
import com.oracle.truffle.js.nodes.access.PropertyGetNode;
import com.oracle.truffle.js.nodes.access.ScopeFrameNode;
import com.oracle.truffle.js.nodes.arguments.AccessIndexedArgumentNode;
import com.oracle.truffle.js.nodes.control.TryCatchNode;
import com.oracle.truffle.js.nodes.function.EvalNode;
import com.oracle.truffle.js.nodes.function.FunctionRootNode;
import com.oracle.truffle.js.nodes.function.JSBuiltin;
import com.oracle.truffle.js.nodes.promise.NewPromiseCapabilityNode;
import com.oracle.truffle.js.nodes.promise.PerformPromiseThenNode;
import com.oracle.truffle.js.parser.date.DateParser;
import com.oracle.truffle.js.parser.env.DebugEnvironment;
import com.oracle.truffle.js.parser.env.Environment;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.GraalJSException;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSException;
import com.oracle.truffle.js.runtime.JSFrameUtil;
import com.oracle.truffle.js.runtime.JSParserOptions;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.JavaScriptRootNode;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSModuleNamespace;
import com.oracle.truffle.js.runtime.builtins.JSPromise;
import com.oracle.truffle.js.runtime.objects.ExportResolution;
import com.oracle.truffle.js.runtime.objects.JSModuleLoader;
import com.oracle.truffle.js.runtime.objects.JSModuleRecord;
import com.oracle.truffle.js.runtime.objects.JSModuleRecord.Status;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.JSObjectUtil;
import com.oracle.truffle.js.runtime.objects.PromiseCapabilityRecord;
import com.oracle.truffle.js.runtime.objects.ScriptOrModule;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.Pair;
public final class GraalJSEvaluator implements JSParser {
private static final HiddenKey STORE_MODULE_KEY = new HiddenKey("store-module-key");
@Override
public Object evaluate(JSRealm realm, Node lastNode, Source source) {
Object thisObj = realm.getGlobalObject();
return doEvaluate(realm, lastNode, thisObj, JSFrameUtil.NULL_MATERIALIZED_FRAME, source, false, null);
}
@Override
public ScriptNode parseFunction(JSContext context, String parameterList, String body, boolean generatorFunction, boolean asyncFunction, String sourceName) {
String wrappedBody = JSRuntime.LINE_SEPARATOR + body + JSRuntime.LINE_SEPARATOR;
try {
GraalJSParserHelper.checkFunctionSyntax(context, context.getParserOptions(), parameterList, wrappedBody, generatorFunction, asyncFunction, sourceName);
} catch (com.oracle.js.parser.ParserException e) {
e.setLineNumber(e.getLineNumber() - 1);
throw parserToJSError(null, e, context);
}
StringBuilder code = new StringBuilder();
if (asyncFunction) {
code.append("(async function");
} else {
code.append("(function");
}
if (generatorFunction) {
code.append("*");
}
if (context.getEcmaScriptVersion() >= 6) {
code.append(" anonymous");
}
if (context.isOptionNashornCompatibilityMode()) {
code.append(' ');
}
code.append('(');
code.append(parameterList);
code.append(JSRuntime.LINE_SEPARATOR);
code.append(") {");
code.append(wrappedBody);
code.append("})");
Source source = Source.newBuilder(JavaScriptLanguage.ID, code.toString(), sourceName).build();
return parseEval(context, null, source, false, null);
}
@TruffleBoundary(transferToInterpreterOnException = false)
@Override
public Object evaluate(JSRealm realm, Node lastNode, Source source, MaterializedFrame frame, Object thisObj, Object evalEnv) {
assert frame != null;
DirectEvalContext directEval = (DirectEvalContext) evalEnv;
return doEvaluate(realm, lastNode, thisObj, frame, source, directEval.env.isStrictMode(), directEval);
}
private static JavaScriptNode parseInlineScript(JSContext context, Source source, Environment env, boolean isStrict) {
ScriptNode script = JavaScriptTranslator.translateInlineScript(NodeFactory.getInstance(context), context, env, source, isStrict);
RootCallTarget callTarget = script.getCallTarget();
JSFunctionData functionData = script.getFunctionData();
return new JavaScriptNode() {
@Child DirectCallNode callNode = DirectCallNode.create(callTarget);
@Override
public Object execute(VirtualFrame frame) {
DynamicObject closure = JSFunction.create(context.getRealm(), functionData, frame.materialize());
return callNode.call(JSArguments.createZeroArg(JSFrameUtil.getThisObj(frame), closure));
}
};
}
@TruffleBoundary
private static Object doEvaluate(JSRealm realm, Node lastNode, Object thisObj, MaterializedFrame materializedFrame, Source source, boolean isStrict, DirectEvalContext directEval) {
JSContext context = realm.getContext();
ScriptNode scriptNode = parseEval(context, lastNode, source, isStrict, directEval);
return runParsed(scriptNode, realm, thisObj, materializedFrame);
}
private static Object runParsed(ScriptNode scriptNode, JSRealm realm, Object thisObj, MaterializedFrame materializedFrame) {
DynamicObject functionObj = JSFunction.create(realm, scriptNode.getFunctionData(), materializedFrame);
return scriptNode.run(JSArguments.createZeroArg(thisObj, functionObj));
}
private static ScriptNode parseEval(JSContext context, Node lastNode, Source source, boolean isStrict, DirectEvalContext directEval) {
context.checkEvalAllowed();
NodeFactory nodeFactory = NodeFactory.getInstance(context);
try {
return JavaScriptTranslator.translateEvalScript(nodeFactory, context, source, isStrict, directEval);
} catch (com.oracle.js.parser.ParserException e) {
throw parserToJSError(lastNode, e, context);
}
}
@TruffleBoundary
private static JSException parserToJSError(Node lastNode, com.oracle.js.parser.ParserException e, JSContext context) {
String message = e.getMessage().replace("\r\n", "\n");
if (e.getErrorType() == com.oracle.js.parser.JSErrorType.ReferenceError) {
return Errors.createReferenceError(message, e, lastNode);
}
assert e.getErrorType() == com.oracle.js.parser.JSErrorType.SyntaxError;
if (context.isOptionNashornCompatibilityMode() && lastNode instanceof EvalNode) {
SourceSection sourceSection = lastNode.getSourceSection();
String name = sourceSection.getSource().getName();
int lineNumber = sourceSection.getStartLine();
int columnNumber = sourceSection.getStartColumn() - 1;
message = name + '#' + lineNumber + ':' + columnNumber + message;
}
return Errors.createSyntaxError(message, e, lastNode);
}
@TruffleBoundary
@Override
public ScriptNode evalCompile(JSContext context, String sourceCode, String name) {
try {
context.checkEvalAllowed();
return JavaScriptTranslator.translateScript(NodeFactory.getInstance(context), context, Source.newBuilder(JavaScriptLanguage.ID, sourceCode, name).build(), false, "", "");
} catch (com.oracle.js.parser.ParserException e) {
throw Errors.createSyntaxError(e.getMessage());
}
}
@Override
public ScriptNode parseScript(JSContext context, Source source, String prolog, String epilog, String[] argumentNames) {
String mimeType = source.getMimeType();
if (MODULE_MIME_TYPE.equals(mimeType) || (mimeType == null && source.getName().endsWith(MODULE_SOURCE_NAME_SUFFIX))) {
return fakeScriptForModule(context, source);
}
try {
return JavaScriptTranslator.translateScript(NodeFactory.getInstance(context), context, source, context.getParserOptions().isStrict(), prolog, epilog, argumentNames);
} catch (com.oracle.js.parser.ParserException e) {
throw Errors.createSyntaxError(e.getMessage());
}
}
private ScriptNode fakeScriptForModule(JSContext context, Source source) {
RootNode rootNode = new JavaScriptRootNode(context.getLanguage(), JSBuiltin.createSourceSection(), null) {
@Child private PerformPromiseThenNode performPromiseThenNode = PerformPromiseThenNode.create(context);
@Override
public Object execute(VirtualFrame frame) {
JSRealm realm = JSFunction.getRealm(JSFrameUtil.getFunctionObject(frame));
return evalModule(realm);
}
@TruffleBoundary
private Object evalModule(JSRealm realm) {
JSModuleRecord moduleRecord = realm.getModuleLoader().loadModule(source);
moduleInstantiation(realm, moduleRecord);
Object promise = moduleEvaluation(realm, moduleRecord);
if (context.isOptionTopLevelAwait() && JSPromise.isJSPromise(promise)) {
DynamicObject onRejected = createTopLevelAwaitReject(context);
DynamicObject onAccepted = createTopLevelAwaitResolve(context);
performPromiseThenNode.execute((DynamicObject) promise, onAccepted, onRejected, null);
}
return promise;
}
};
JSFunctionData functionData = JSFunctionData.createCallOnly(context, Truffle.getRuntime().createCallTarget(rootNode), 0, "");
return ScriptNode.fromFunctionData(context, functionData);
}
private static DynamicObject createTopLevelAwaitReject(JSContext context) {
JSFunctionData functionData = context.getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.TopLevelAwaitReject, (c) -> createTopLevelAwaitRejectImpl(c));
return JSFunction.create(context.getRealm(), functionData);
}
private static JSFunctionData createTopLevelAwaitRejectImpl(JSContext context) {
class TopLevelAwaitRejectedRootNode extends JavaScriptRootNode {
@Child private JavaScriptNode argumentNode = AccessIndexedArgumentNode.create(0);
@Override
public Object execute(VirtualFrame frame) {
Object error = argumentNode.execute(frame);
throw JSRuntime.getException(error);
}
}
CallTarget callTarget = Truffle.getRuntime().createCallTarget(new TopLevelAwaitRejectedRootNode());
return JSFunctionData.createCallOnly(context, callTarget, 1, "");
}
private static DynamicObject createTopLevelAwaitResolve(JSContext context) {
JSFunctionData functionData = context.getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.TopLevelAwaitResolve, (c) -> createTopLevelAwaitResolveImpl(c));
return JSFunction.create(context.getRealm(), functionData);
}
private static JSFunctionData createTopLevelAwaitResolveImpl(JSContext context) {
class TopLevelAwaitFulfilledRootNode extends JavaScriptRootNode {
@Override
public Object execute(VirtualFrame frame) {
return Undefined.instance;
}
}
CallTarget callTarget = Truffle.getRuntime().createCallTarget(new TopLevelAwaitFulfilledRootNode());
return JSFunctionData.createCallOnly(context, callTarget, 1, "");
}
@Override
public ScriptNode parseScript(JSContext context, String sourceCode) {
try {
return JavaScriptTranslator.translateScript(NodeFactory.getInstance(context), context, Source.newBuilder(JavaScriptLanguage.ID, sourceCode, "<unknown>").build(), false, "", "");
} catch (com.oracle.js.parser.ParserException e) {
throw Errors.createSyntaxError(e.getMessage());
}
}
@TruffleBoundary
@Override
public Integer[] parseDate(JSRealm realm, String date) {
DateParser dateParser = new DateParser(realm, date);
return dateParser.parse() ? dateParser.getDateFields() : null;
}
@Override
public String parseToJSON(JSContext context, String code, String name, boolean includeLoc) {
return GraalJSParserHelper.parseToJSON(code, name, includeLoc, context.getParserOptions());
}
@Override
public Object getDefaultNodeFactory() {
return NodeFactory.getDefaultInstance();
}
public static Supplier<ScriptNode> internalParseForTiming(JSContext context, Source source) {
com.oracle.js.parser.ir.FunctionNode ast = GraalJSParserHelper.parseScript(context, source, new JSParserOptions());
return () -> JavaScriptTranslator.translateFunction(NodeFactory.getInstance(context), context, null, source, 0, false, ast);
}
@TruffleBoundary
@Override
public JSModuleRecord parseModule(JSContext context, Source source, JSModuleLoader moduleLoader) {
try {
return JavaScriptTranslator.translateModule(NodeFactory.getInstance(context), context, source, moduleLoader);
} catch (com.oracle.js.parser.ParserException e) {
throw Errors.createSyntaxError(e.getMessage(), e, null);
}
}
@TruffleBoundary
@Override
public JSModuleRecord hostResolveImportedModule(JSContext context, ScriptOrModule referrer, String specifier) {
JSModuleLoader moduleLoader = referrer instanceof JSModuleRecord ? ((JSModuleRecord) referrer).getModuleLoader() : context.getRealm().getModuleLoader();
return moduleLoader.resolveImportedModule(referrer, specifier);
}
private static JSModuleRecord hostResolveImportedModule(JSModuleRecord referencingModule, String specifier) {
return referencingModule.getModuleLoader().resolveImportedModule(referencingModule, specifier);
}
Collection<String> getExportedNames(JSModuleRecord moduleRecord) {
return getExportedNames(moduleRecord, new HashSet<>());
}
private Collection<String> getExportedNames(JSModuleRecord moduleRecord, Set<JSModuleRecord> exportStarSet) {
if (exportStarSet.contains(moduleRecord)) {
return Collections.emptySortedSet();
}
exportStarSet.add(moduleRecord);
Collection<String> exportedNames = new HashSet<>();
Module module = (Module) moduleRecord.getModule();
for (ExportEntry exportEntry : module.getLocalExportEntries()) {
exportedNames.add(exportEntry.getExportName());
}
for (ExportEntry exportEntry : module.getIndirectExportEntries()) {
exportedNames.add(exportEntry.getExportName());
}
for (ExportEntry exportEntry : module.getStarExportEntries()) {
JSModuleRecord requestedModule = hostResolveImportedModule(moduleRecord, exportEntry.getModuleRequest());
Collection<String> starNames = getExportedNames(requestedModule, exportStarSet);
for (String starName : starNames) {
if (!starName.equals(Module.DEFAULT_NAME)) {
if (!exportedNames.contains(starName)) {
exportedNames.add(starName);
}
}
}
}
return exportedNames;
}
@TruffleBoundary
@Override
public ExportResolution resolveExport(JSModuleRecord referencingModule, String exportName) {
return resolveExport(referencingModule, exportName, new HashSet<>());
}
private ExportResolution resolveExport(JSModuleRecord referencingModule, String exportName, Set<Pair<JSModuleRecord, String>> resolveSet) {
Pair<JSModuleRecord, String> resolved = new Pair<>(referencingModule, exportName);
if (resolveSet.contains(resolved)) {
return ExportResolution.notFound();
}
resolveSet.add(resolved);
Module module = (Module) referencingModule.getModule();
for (ExportEntry exportEntry : module.getLocalExportEntries()) {
if (exportEntry.getExportName().equals(exportName)) {
return ExportResolution.resolved(referencingModule, exportEntry.getLocalName());
}
}
for (ExportEntry exportEntry : module.getIndirectExportEntries()) {
if (exportEntry.getExportName().equals(exportName)) {
JSModuleRecord importedModule = hostResolveImportedModule(referencingModule, exportEntry.getModuleRequest());
if (exportEntry.getImportName().equals(Module.STAR_NAME)) {
return ExportResolution.resolved(importedModule, Module.NAMESPACE_EXPORT_BINDING_NAME);
} else {
return resolveExport(importedModule, exportEntry.getImportName(), resolveSet);
}
}
}
if (exportName.equals(Module.DEFAULT_NAME)) {
return ExportResolution.notFound();
}
ExportResolution starResolution = ExportResolution.notFound();
for (ExportEntry exportEntry : module.getStarExportEntries()) {
JSModuleRecord importedModule = hostResolveImportedModule(referencingModule, exportEntry.getModuleRequest());
ExportResolution resolution = resolveExport(importedModule, exportName, resolveSet);
if (resolution.isAmbiguous()) {
return resolution;
}
if (!resolution.isNull()) {
if (starResolution.isNull()) {
starResolution = resolution;
} else {
if (!resolution.equals(starResolution)) {
return ExportResolution.ambiguous();
}
}
}
}
return starResolution;
}
@TruffleBoundary
@Override
public DynamicObject getModuleNamespace(JSModuleRecord moduleRecord) {
if (moduleRecord.getNamespace() != null) {
return moduleRecord.getNamespace();
}
assert moduleRecord.getStatus() != Status.Unlinked;
Collection<String> exportedNames = getExportedNames(moduleRecord);
List<Pair<String, ExportResolution>> unambiguousNames = new ArrayList<>();
for (String exportedName : exportedNames) {
ExportResolution resolution = resolveExport(moduleRecord, exportedName);
if (resolution.isNull()) {
throw Errors.createSyntaxError("Could not resolve export");
} else if (!resolution.isAmbiguous()) {
unambiguousNames.add(new Pair<>(exportedName, resolution));
}
}
Map<String, ExportResolution> sortedNames = new LinkedHashMap<>();
unambiguousNames.stream().sorted(Comparator.comparing(Pair::getFirst)).forEachOrdered(p -> sortedNames.put(p.getFirst(), p.getSecond()));
DynamicObject namespace = JSModuleNamespace.create(moduleRecord.getContext(), moduleRecord, sortedNames);
moduleRecord.setNamespace(namespace);
return namespace;
}
@TruffleBoundary
@Override
public void moduleInstantiation(JSRealm realm, JSModuleRecord moduleRecord) {
assert moduleRecord.getStatus() != Status.Linking && moduleRecord.getStatus() != Status.Evaluating;
Deque<JSModuleRecord> stack = new ArrayDeque<>(4);
try {
innerModuleInstantiation(realm, moduleRecord, stack, 0);
} catch (GraalJSException e) {
for (JSModuleRecord m : stack) {
assert m.getStatus() == Status.Linking;
m.setUninstantiated();
}
assert moduleRecord.getStatus() == Status.Unlinked;
throw e;
}
assert moduleRecord.getStatus() == Status.Linked || moduleRecord.getStatus() == Status.Evaluated;
assert stack.isEmpty();
}
private int innerModuleInstantiation(JSRealm realm, JSModuleRecord moduleRecord, Deque<JSModuleRecord> stack, int index0) {
int index = index0;
if (moduleRecord.getStatus() == Status.Linking || moduleRecord.getStatus() == Status.Linked || moduleRecord.getStatus() == Status.Evaluated) {
return index;
}
assert moduleRecord.getStatus() == Status.Unlinked;
moduleRecord.setStatus(Status.Linking);
moduleRecord.setDFSIndex(index);
moduleRecord.setDFSAncestorIndex(index);
index++;
stack.push(moduleRecord);
Module module = (Module) moduleRecord.getModule();
for (String requestedModule : module.getRequestedModules()) {
JSModuleRecord requiredModule = hostResolveImportedModule(moduleRecord, requestedModule);
index = innerModuleInstantiation(realm, requiredModule, stack, index);
assert requiredModule.getStatus() == Status.Linking || requiredModule.getStatus() == Status.Linked ||
requiredModule.getStatus() == Status.Evaluated : requiredModule.getStatus();
assert (requiredModule.getStatus() == Status.Linking) == stack.contains(requiredModule);
if (requiredModule.getStatus() == Status.Linking) {
moduleRecord.setDFSAncestorIndex(Math.min(moduleRecord.getDFSAncestorIndex(), requiredModule.getDFSAncestorIndex()));
}
}
moduleInitializeEnvironment(realm, moduleRecord);
assert occursExactlyOnce(moduleRecord, stack);
assert moduleRecord.getDFSAncestorIndex() <= moduleRecord.getDFSIndex();
if (moduleRecord.getDFSAncestorIndex() == moduleRecord.getDFSIndex()) {
while (true) {
JSModuleRecord requiredModule = stack.pop();
requiredModule.setStatus(Status.Linked);
if (requiredModule.equals(moduleRecord)) {
break;
}
}
}
return index;
}
private void moduleInitializeEnvironment(JSRealm realm, JSModuleRecord moduleRecord) {
assert moduleRecord.getStatus() == Status.Linking;
Module module = (Module) moduleRecord.getModule();
for (ExportEntry exportEntry : module.getIndirectExportEntries()) {
ExportResolution resolution = resolveExport(moduleRecord, exportEntry.getExportName());
if (resolution.isNull() || resolution.isAmbiguous()) {
throw Errors.createSyntaxError("Could not resolve indirect export entry");
}
}
moduleExecution(realm, moduleRecord, null);
}
@TruffleBoundary
@Override
public Object moduleEvaluation(JSRealm realm, JSModuleRecord moduleRecord) {
JSModuleRecord module = moduleRecord;
Deque<JSModuleRecord> stack = new ArrayDeque<>(4);
if (realm.getContext().isOptionTopLevelAwait()) {
assert module.getStatus() == Status.Linked || module.getStatus() == Status.Evaluated;
if (module.getStatus() == Status.Evaluated) {
module = getAsyncCycleRoot(module);
}
if (module.getTopLevelCapability() != null) {
return module.getTopLevelCapability().getPromise();
}
PromiseCapabilityRecord capability = NewPromiseCapabilityNode.createDefault(realm);
module.setTopLevelCapability(capability);
try {
innerModuleEvaluation(realm, module, stack, 0);
assert module.getStatus() == Status.Evaluated;
assert module.getEvaluationError() == null;
if (!module.isAsyncEvaluating()) {
JSFunction.call(JSArguments.create(Undefined.instance, capability.getResolve(), Undefined.instance));
}
assert stack.isEmpty();
} catch (Throwable e) {
if (TryCatchNode.shouldCatch(e)) {
for (JSModuleRecord m : stack) {
assert m.getStatus() == Status.Evaluating;
m.setStatus(Status.Evaluated);
m.setEvaluationError(e);
}
assert module.getStatus() == Status.Evaluated && module.getEvaluationError() == e;
throw e;
} else {
throw e;
}
}
return capability.getPromise();
} else {
try {
innerModuleEvaluation(realm, module, stack, 0);
} catch (Throwable e) {
if (TryCatchNode.shouldCatch(e)) {
for (JSModuleRecord m : stack) {
assert m.getStatus() == Status.Evaluating;
m.setStatus(Status.Evaluated);
m.setEvaluationError(e);
}
assert module.getStatus() == Status.Evaluated && module.getEvaluationError() == e;
}
throw e;
}
assert module.getStatus() == Status.Evaluated && module.getEvaluationError() == null;
assert stack.isEmpty();
Object result = module.getExecutionResult();
return result == null ? Undefined.instance : result;
}
}
@TruffleBoundary
private static JSModuleRecord getAsyncCycleRoot(JSModuleRecord moduleRecord) {
JSModuleRecord module = moduleRecord;
assert module.getStatus() == Status.Evaluated;
if (module.getAsyncParentModules().size() == 0) {
return module;
}
while (module.getDFSIndex() > module.getDFSAncestorIndex()) {
assert module.getAsyncParentModules().size() != 0;
JSModuleRecord nextCycleModule = module.getAsyncParentModules().remove(0);
assert nextCycleModule.getDFSAncestorIndex() <= module.getDFSAncestorIndex();
module = nextCycleModule;
}
assert module.getDFSIndex() == module.getDFSAncestorIndex();
return module;
}
@TruffleBoundary
private int innerModuleEvaluation(JSRealm realm, JSModuleRecord moduleRecord, Deque<JSModuleRecord> stack, int index0) {
int index = index0;
if (moduleRecord.getStatus() == Status.Evaluated) {
if (moduleRecord.getEvaluationError() == null) {
return index;
} else {
throw JSRuntime.rethrow(moduleRecord.getEvaluationError());
}
}
if (moduleRecord.getStatus() == Status.Evaluating) {
return index;
}
assert moduleRecord.getStatus() == Status.Linked;
moduleRecord.setStatus(Status.Evaluating);
moduleRecord.setDFSIndex(index);
moduleRecord.setDFSAncestorIndex(index);
moduleRecord.setPendingAsyncDependencies(0);
moduleRecord.initAsyncParentModules();
index++;
stack.push(moduleRecord);
Module module = (Module) moduleRecord.getModule();
for (String requestedModule : module.getRequestedModules()) {
JSModuleRecord requiredModule = hostResolveImportedModule(moduleRecord, requestedModule);
index = innerModuleEvaluation(realm, requiredModule, stack, index);
assert requiredModule.getStatus() == Status.Evaluating || requiredModule.getStatus() == Status.Evaluated : requiredModule.getStatus();
assert (requiredModule.getStatus() == Status.Evaluating) == stack.contains(requiredModule);
if (requiredModule.getStatus() == Status.Evaluating) {
moduleRecord.setDFSAncestorIndex(Math.min(moduleRecord.getDFSAncestorIndex(), requiredModule.getDFSAncestorIndex()));
} else {
requiredModule = getAsyncCycleRoot(requiredModule);
assert requiredModule.getStatus() == Status.Evaluated;
if (requiredModule.getEvaluationError() != null) {
throw JSRuntime.rethrow(moduleRecord.getEvaluationError());
}
}
if (requiredModule.isAsyncEvaluating()) {
moduleRecord.incPendingAsyncDependencies();
requiredModule.appendAsyncParentModules(moduleRecord);
}
}
if (moduleRecord.getPendingAsyncDependencies() > 0) {
moduleRecord.setAsyncEvaluating(true);
} else if (moduleRecord.isTopLevelAsync()) {
moduleAsyncExecution(realm, moduleRecord);
} else {
Object result = moduleExecution(realm, moduleRecord, null);
moduleRecord.setExecutionResult(result);
}
assert occursExactlyOnce(moduleRecord, stack);
assert moduleRecord.getDFSAncestorIndex() <= moduleRecord.getDFSIndex();
if (moduleRecord.getDFSAncestorIndex() == moduleRecord.getDFSIndex()) {
while (true) {
JSModuleRecord requiredModule = stack.pop();
requiredModule.setStatus(Status.Evaluated);
if (requiredModule.equals(moduleRecord)) {
break;
}
}
}
return index;
}
@TruffleBoundary
private static void moduleAsyncExecution(JSRealm realm, JSModuleRecord module) {
assert module.getStatus() == Status.Evaluating || module.getStatus() == Status.Evaluated;
assert module.isTopLevelAsync();
module.setAsyncEvaluating(true);
PromiseCapabilityRecord capability = NewPromiseCapabilityNode.createDefault(realm);
DynamicObject onFulfilled = createCallAsyncModuleFulfilled(realm.getContext(), module);
DynamicObject onRejected = createCallAsyncModuleRejected(realm.getContext(), module);
Object then = JSObject.get(capability.getPromise(), "then");
JSFunction.call(JSArguments.create(capability.getPromise(), then, onFulfilled, onRejected));
moduleExecution(realm, module, capability);
}
@TruffleBoundary
private static DynamicObject createCallAsyncModuleFulfilled(JSContext context, JSModuleRecord module) {
JSFunctionData functionData = context.getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.AsyncModuleExecutionFulfilled, (c) -> createCallAsyncModuleFulfilledImpl(c));
DynamicObject function = JSFunction.create(context.getRealm(), functionData);
JSObjectUtil.putHiddenProperty(function, STORE_MODULE_KEY, module);
return function;
}
private static JSFunctionData createCallAsyncModuleFulfilledImpl(JSContext context) {
class AsyncModuleFulfilledRoot extends JavaScriptRootNode {
@Child private JavaScriptNode argumentNode = AccessIndexedArgumentNode.create(0);
@Child private PropertyGetNode getModule = PropertyGetNode.createGetHidden(STORE_MODULE_KEY, context);
@Override
public Object execute(VirtualFrame frame) {
Object dynamicImportResolutionResult = argumentNode.execute(frame);
Object module = getModule.getValue(JSArguments.getFunctionObject(frame.getArguments()));
return asyncModuleExecutionFulfilled(context.getRealm(), (JSModuleRecord) module, dynamicImportResolutionResult);
}
}
CallTarget callTarget = Truffle.getRuntime().createCallTarget(new AsyncModuleFulfilledRoot());
return JSFunctionData.createCallOnly(context, callTarget, 1, "");
}
@TruffleBoundary
private static DynamicObject createCallAsyncModuleRejected(JSContext context, JSModuleRecord module) {
JSFunctionData functionData = context.getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.AsyncModuleExecutionRejected, (c) -> createCallAsyncModuleRejectedImpl(c));
DynamicObject function = JSFunction.create(context.getRealm(), functionData);
JSObjectUtil.putHiddenProperty(function, STORE_MODULE_KEY, module);
return function;
}
private static JSFunctionData createCallAsyncModuleRejectedImpl(JSContext context) {
class AsyncModuleExecutionRejectedRoot extends JavaScriptRootNode {
@Child private PropertyGetNode getModule = PropertyGetNode.createGetHidden(STORE_MODULE_KEY, context);
@Child private PropertyGetNode getRejectionError = PropertyGetNode.createGetHidden(JSPromise.PROMISE_RESULT, context);
@Override
public Object execute(VirtualFrame frame) {
JSModuleRecord module = (JSModuleRecord) getModule.getValue(JSArguments.getFunctionObject(frame.getArguments()));
Object resolvedPromise = module.getExecutionContinuation();
assert JSPromise.isJSPromise(resolvedPromise);
assert JSPromise.isRejected((DynamicObject) resolvedPromise);
Object reaction = getRejectionError.getValue(resolvedPromise);
return asyncModuleExecutionRejected(context.getRealm(), module, reaction);
}
}
CallTarget callTarget = Truffle.getRuntime().createCallTarget(new AsyncModuleExecutionRejectedRoot());
return JSFunctionData.createCallOnly(context, callTarget, 1, "");
}
@TruffleBoundary
private static Object asyncModuleExecutionFulfilled(JSRealm realm, JSModuleRecord module, Object dynamicImportResolutionResult) {
assert module.getStatus() == Status.Evaluated;
if (!module.isAsyncEvaluating()) {
assert module.getEvaluationError() != null;
return Undefined.instance;
}
assert module.getEvaluationError() == null;
module.setAsyncEvaluating(false);
for (JSModuleRecord m : module.getAsyncParentModules()) {
if (module.getDFSIndex() != module.getDFSAncestorIndex()) {
assert m.getDFSAncestorIndex() <= module.getDFSAncestorIndex();
}
m.decPendingAsyncDependencies();
if (m.getPendingAsyncDependencies() == 0 && m.getEvaluationError() == null) {
assert m.isAsyncEvaluating();
JSModuleRecord cycleRoot = getAsyncCycleRoot(m);
if (cycleRoot.getEvaluationError() != null) {
return Undefined.instance;
}
if (m.isTopLevelAsync()) {
moduleAsyncExecution(realm, m);
} else {
try {
moduleExecution(realm, m, null);
asyncModuleExecutionFulfilled(realm, m, dynamicImportResolutionResult);
} catch (Exception e) {
asyncModuleExecutionRejected(realm, m, e);
}
}
}
}
if (module.getTopLevelCapability() != null) {
assert module.getDFSIndex() == module.getDFSAncestorIndex();
JSFunction.call(JSArguments.create(Undefined.instance, module.getTopLevelCapability().getResolve(), dynamicImportResolutionResult));
}
return Undefined.instance;
}
@TruffleBoundary
private static Object asyncModuleExecutionRejected(JSRealm realm, JSModuleRecord module, Object error) {
assert error != null : "Cannot reject a module creation with null error";
assert module.getStatus() == Status.Evaluated;
if (!module.isAsyncEvaluating()) {
assert module.getEvaluationError() != null;
return Undefined.instance;
}
assert module.getEvaluationError() == null;
module.setEvaluationError(JSRuntime.getException(error));
module.setAsyncEvaluating(false);
for (JSModuleRecord m : module.getAsyncParentModules()) {
if (module.getDFSIndex() != module.getDFSAncestorIndex()) {
assert m.getDFSAncestorIndex() == module.getDFSAncestorIndex();
}
asyncModuleExecutionRejected(realm, m, error);
}
if (module.getTopLevelCapability() != null) {
assert module.getDFSIndex() == module.getDFSAncestorIndex();
JSFunction.call((DynamicObject) module.getTopLevelCapability().getReject(), Undefined.instance, new Object[]{error});
}
return Undefined.instance;
}
private static Object moduleExecution(JSRealm realm, JSModuleRecord moduleRecord, PromiseCapabilityRecord capability) {
if (!moduleRecord.isTopLevelAsync()) {
assert capability == null;
return JSFunction.call(JSArguments.create(Undefined.instance, JSFunction.create(realm, moduleRecord.getFunctionData()), moduleRecord));
} else {
Object asyncFunctionResultPromise = JSFunction.call(JSArguments.create(Undefined.instance, JSFunction.create(realm, moduleRecord.getFunctionData()), moduleRecord, capability));
moduleRecord.setExecutionContinuation(asyncFunctionResultPromise);
return asyncFunctionResultPromise;
}
}
private static boolean occursExactlyOnce(JSModuleRecord moduleRecord, Collection<JSModuleRecord> stack) {
return stack.stream().filter(moduleRecord::equals).count() == 1;
}
@Override
public ScriptNode parseScript(JSContext context, Source source, ByteBuffer binary) {
return ScriptNode.fromFunctionRoot(context, (FunctionRootNode) new BinarySnapshotProvider(binary).apply(NodeFactory.getInstance(context), context, source));
}
@Override
public ScriptNode parseScript(JSContext context, Source source, SnapshotProvider snapshotProvider) {
return ScriptNode.fromFunctionRoot(context, (FunctionRootNode) snapshotProvider.apply(NodeFactory.getInstance(context), context, source));
}
@Override
public JavaScriptNode parseInlineScript(JSContext context, Source source, MaterializedFrame lexicalContextFrame, boolean isStrict) {
Environment env = assembleDebugEnvironment(context, lexicalContextFrame);
return parseInlineScript(context, source, env, isStrict);
}
private static Environment assembleDebugEnvironment(JSContext context, MaterializedFrame lexicalContextFrame) {
Environment env = null;
ArrayList<FrameDescriptor> frameDescriptors = new ArrayList<>();
Frame frame = lexicalContextFrame;
while (frame != null && frame != JSFrameUtil.NULL_MATERIALIZED_FRAME) {
assert isJSArgumentsArray(frame.getArguments());
FrameSlot parentSlot;
while ((parentSlot = frame.getFrameDescriptor().findFrameSlot(ScopeFrameNode.PARENT_SCOPE_IDENTIFIER)) != null) {
frameDescriptors.add(frame.getFrameDescriptor());
frame = (Frame) FrameUtil.getObjectSafe(frame, parentSlot);
}
frameDescriptors.add(frame.getFrameDescriptor());
frame = JSArguments.getEnclosingFrame(frame.getArguments());
}
for (int i = frameDescriptors.size() - 1; i >= 0; i--) {
env = new DebugEnvironment(env, NodeFactory.getInstance(context), context, frameDescriptors.get(i));
}
return env;
}
private static boolean isJSArgumentsArray(Object[] arguments) {
return arguments != null && arguments.length >= JSArguments.RUNTIME_ARGUMENT_COUNT && JSFunction.isJSFunction(JSArguments.getFunctionObject(arguments));
}
@Override
public Expression parseExpression(JSContext context, String sourceString) {
return GraalJSParserHelper.parseExpression(context, Source.newBuilder(JavaScriptLanguage.ID, sourceString, "<unknown>").build(), context.getParserOptions());
}
}