All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.js.parser.GraalJSEvaluator Maven / Gradle / Ivy

/*
 * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must 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 NONINFRINGEMENT. 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 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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
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.js.parser.ir.Module.ModuleRequest;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.NodeLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
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.HiddenKey;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.nodes.JSFrameDescriptor;
import com.oracle.truffle.js.nodes.JSFrameSlot;
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.PropertySetNode;
import com.oracle.truffle.js.nodes.arguments.AccessIndexedArgumentNode;
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.JSConfig;
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.Strings;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSFunctionObject;
import com.oracle.truffle.js.runtime.builtins.JSModuleNamespace;
import com.oracle.truffle.js.runtime.builtins.JSModuleNamespaceObject;
import com.oracle.truffle.js.runtime.builtins.JSPromiseObject;
import com.oracle.truffle.js.runtime.objects.ExportResolution;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.JSModuleData;
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;

/**
 * This is the main external entry into the GraalJS parser.
 *
 * The GraalJS parser is derived from the Oracle Nashorn JavaScript parser
 * (http://openjdk.java.net/projects/nashorn/).
 *
 */
public final class GraalJSEvaluator implements JSParser {

    private static final HiddenKey STORE_MODULE_KEY = new HiddenKey("store-module-key");

    /**
     * Evaluate indirect eval.
     */
    @Override
    public ScriptNode parseEval(JSContext context, Node lastNode, Source source, ScriptOrModule activeScriptOrModule) {
        return parseEval(context, lastNode, source, false, null, activeScriptOrModule);
    }

    /**
     * Evaluate Function(parameterList, body).
     */
    @TruffleBoundary(transferToInterpreterOnException = false)
    @Override
    public ScriptNode parseFunction(JSContext context, String parameterList, String body, boolean generatorFunction, boolean asyncFunction, String sourceName, ScriptOrModule activeScriptOrModule) {
        String wrappedBody = "\n" + body + "\n";
        try {
            GraalJSParserHelper.checkFunctionSyntax(context, context.getParserOptions(), parameterList, wrappedBody, generatorFunction, asyncFunction, sourceName);
        } catch (com.oracle.js.parser.ParserException e) {
            e.setLineNumber(e.getLineNumber() - 1); // undo the shift caused by the wrapping
            throw parserToJSError(null, e, context);
        }
        StringBuilder code = new StringBuilder();
        if (asyncFunction) {
            code.append("(async function");
        } else {
            code.append("(function");
        }
        if (generatorFunction) {
            code.append("*");
        }
        code.append(' ');
        boolean nashornCompat = context.getEcmaScriptVersion() == 5 && context.isOptionNashornCompatibilityMode();
        if (!nashornCompat) {
            code.append("anonymous");
        }
        code.append('(');
        code.append(parameterList);
        if (!nashornCompat) {
            code.append('\n');
        }
        code.append(") {");
        code.append(wrappedBody);
        code.append("})");
        Source source = Source.newBuilder(JavaScriptLanguage.ID, code.toString(), sourceName).cached(false).build();
        return parseEval(context, null, source, false, null, activeScriptOrModule);
    }

    /**
     * Evaluate direct eval.
     */
    @TruffleBoundary(transferToInterpreterOnException = false)
    @Override
    public ScriptNode parseDirectEval(JSContext context, Node lastNode, Source source, Object evalEnv) {
        DirectEvalContext directEval = (DirectEvalContext) evalEnv;
        return parseEval(context, lastNode, source, directEval.env.isStrictMode(), directEval, directEval.activeScriptOrModule);
    }

    @TruffleBoundary(transferToInterpreterOnException = false)
    private static ScriptNode parseEval(JSContext context, Node lastNode, Source source, boolean isStrict, DirectEvalContext directEval, ScriptOrModule activeScriptOrModule) {
        context.checkEvalAllowed();
        NodeFactory nodeFactory = NodeFactory.getInstance(context);
        try {
            return JavaScriptTranslator.translateEvalScript(nodeFactory, context, source, isStrict, directEval, activeScriptOrModule);
        } catch (com.oracle.js.parser.ParserException e) {
            throw parserToJSError(lastNode, e, context);
        }
    }

    private static JSException parserToJSError(Node lastNode, com.oracle.js.parser.ParserException e, JSContext context) {
        CompilerAsserts.neverPartOfCompilation();
        String message;
        if (context.isOptionV8CompatibilityMode()) {
            message = e.getRawMessage();
        } else {
            message = e.getMessage();
        }
        message = message.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, context);
        }
    }

    // JSParser methods below

    @TruffleBoundary
    @Override
    public ScriptNode parseScript(JSContext context, Source source, String prolog, String epilog, boolean isStrict, List argumentNames) {
        if (isModuleSource(source)) {
            return fakeScriptForModule(context, source);
        }
        try {
            return JavaScriptTranslator.translateScript(NodeFactory.getInstance(context), context, source, isStrict, prolog, epilog, argumentNames);
        } catch (com.oracle.js.parser.ParserException e) {
            throw Errors.createSyntaxError(e, context);
        }
    }

    private static boolean isModuleSource(Source source) {
        String mimeType = source.getMimeType();
        return MODULE_MIME_TYPE.equals(mimeType) || (mimeType == null && source.getName().endsWith(MODULE_SOURCE_NAME_SUFFIX));
    }

    private ScriptNode fakeScriptForModule(JSContext context, Source source) {
        JSModuleData parsedModule = parseModule(context, source);
        RootNode rootNode = new ModuleScriptRoot(context, parsedModule, source);
        JSFunctionData functionData = JSFunctionData.createCallOnly(context, rootNode.getCallTarget(), 0, Strings.EMPTY_STRING);
        return ScriptNode.fromFunctionData(functionData);
    }

    private final class ModuleScriptRoot extends JavaScriptRootNode {
        private final JSContext context;
        private final JSModuleData parsedModule;
        private final Source source;
        @Child private PerformPromiseThenNode performPromiseThenNode;

        private ModuleScriptRoot(JSContext context, JSModuleData parsedModule, Source source) {
            super(context.getLanguage(), JSBuiltin.createSourceSection(), null);
            this.context = context;
            this.parsedModule = parsedModule;
            this.source = source;
            this.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, parsedModule);
            moduleLinking(realm, moduleRecord);
            Object promise = moduleEvaluation(realm, moduleRecord);
            boolean isAsync = context.isOptionTopLevelAwait() && moduleRecord.isAsyncEvaluation();
            if (isAsync) {
                JSFunctionObject onRejected = createTopLevelAwaitReject(context, realm);
                JSFunctionObject onAccepted = createTopLevelAwaitResolve(context, realm);
                // Non-standard: throw error from onRejected handler.
                performPromiseThenNode.execute((JSPromiseObject) promise, onAccepted, onRejected, null);
            }
            if (context.getLanguageOptions().esmEvalReturnsExports()) {
                JSDynamicObject moduleNamespace = getModuleNamespace(moduleRecord);
                assert moduleNamespace != null;
                return moduleNamespace;
            } else if (isAsync) {
                return promise;
            } else {
                return moduleRecord.getExecutionResultOrThrow();
            }
        }

        JSModuleData getModuleData() {
            return parsedModule;
        }
    }

    private static JSFunctionObject createTopLevelAwaitReject(JSContext context, JSRealm realm) {
        JSFunctionData functionData = context.getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.TopLevelAwaitReject, (c) -> createTopLevelAwaitRejectImpl(c));
        return JSFunction.create(realm, 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);
            }
        }
        return JSFunctionData.createCallOnly(context, new TopLevelAwaitRejectedRootNode().getCallTarget(), 1, Strings.EMPTY_STRING);
    }

    private static JSFunctionObject createTopLevelAwaitResolve(JSContext context, JSRealm realm) {
        JSFunctionData functionData = context.getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.TopLevelAwaitResolve, (c) -> createTopLevelAwaitResolveImpl(c));
        return JSFunction.create(realm, functionData);
    }

    private static JSFunctionData createTopLevelAwaitResolveImpl(JSContext context) {
        class TopLevelAwaitFulfilledRootNode extends JavaScriptRootNode {

            @Override
            public Object execute(VirtualFrame frame) {
                return Undefined.instance;
            }
        }
        return JSFunctionData.createCallOnly(context, new TopLevelAwaitFulfilledRootNode().getCallTarget(), 1, Strings.EMPTY_STRING);
    }

    @Override
    public ScriptNode parseScript(JSContext context, String sourceCode) {
        try {
            return JavaScriptTranslator.translateScript(NodeFactory.getInstance(context), context, Source.newBuilder(JavaScriptLanguage.ID, sourceCode, "").build(), false, "", "");
        } catch (com.oracle.js.parser.ParserException e) {
            throw Errors.createSyntaxError(e, context);
        }
    }

    @TruffleBoundary
    @Override
    public Integer[] parseDate(JSRealm realm, String date, boolean extraLenient) {
        DateParser dateParser = new DateParser(realm, date, extraLenient);
        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);
    }

    @Override
    public Object getDefaultNodeFactory() {
        return NodeFactory.getDefaultInstance();
    }

    /**
     * Parses source to intermediate AST and returns a closure for the translation to Truffle AST.
     */
    public static Supplier internalParseForTiming(JSContext context, Source source) {
        com.oracle.js.parser.ir.FunctionNode ast = GraalJSParserHelper.parseScript(context, source, context.getParserOptions());
        return () -> JavaScriptTranslator.translateFunction(NodeFactory.getInstance(context), context, null, source, 0, false, ast);
    }

    @TruffleBoundary
    @Override
    public JSModuleData parseModule(JSContext context, Source source) {
        try {
            return JavaScriptTranslator.translateModule(NodeFactory.getInstance(context), context, source);
        } catch (com.oracle.js.parser.ParserException e) {
            throw Errors.createSyntaxError(e, context);
        }

    }

    @TruffleBoundary
    @Override
    public JSModuleData envParseModule(JSRealm realm, Source source) {
        assert isModuleSource(source) : source;
        CallTarget parseResult = realm.getEnv().parsePublic(source);
        CallTarget moduleScriptCallTarget = JavaScriptLanguage.getParsedProgramCallTarget(((RootCallTarget) parseResult).getRootNode());
        ModuleScriptRoot moduleScriptRoot = (ModuleScriptRoot) ((RootCallTarget) moduleScriptCallTarget).getRootNode();
        return moduleScriptRoot.getModuleData();
    }

    @TruffleBoundary
    @Override
    public JSModuleRecord parseJSONModule(JSRealm realm, Source source) {
        assert isModuleSource(source) : source;
        Object json = JSFunction.call(JSArguments.createOneArg(Undefined.instance, realm.getJsonParseFunctionObject(), Strings.fromJavaString(source.getCharacters().toString())));
        return createSyntheticJSONModule(realm, source, json);
    }

    private static JSModuleRecord createSyntheticJSONModule(JSRealm realm, Source source, Object hostDefined) {
        final TruffleString exportName = Strings.DEFAULT;
        JSFrameDescriptor frameDescBuilder = new JSFrameDescriptor(Undefined.instance);
        JSFrameSlot slot = frameDescBuilder.addFrameSlot(exportName);
        FrameDescriptor frameDescriptor = frameDescBuilder.toFrameDescriptor();
        List localExportEntries = Collections.singletonList(ExportEntry.exportSpecifier(exportName));
        Module moduleNode = new Module(Collections.emptyList(), Collections.emptyList(), localExportEntries, Collections.emptyList(), Collections.emptyList(), null, null);
        JavaScriptRootNode rootNode = new JavaScriptRootNode(realm.getContext().getLanguage(), source.createUnavailableSection(), frameDescriptor) {
            private final int defaultSlot = slot.getIndex();

            @Override
            public Object execute(VirtualFrame frame) {
                JSModuleRecord module = (JSModuleRecord) JSArguments.getUserArgument(frame.getArguments(), 0);
                if (module.getEnvironment() == null) {
                    assert module.getStatus() == Status.Linking;
                    module.setEnvironment(frame.materialize());
                } else {
                    assert module.getStatus() == Status.Evaluating;
                    setSyntheticModuleExport(module);
                }
                return Undefined.instance;
            }

            private void setSyntheticModuleExport(JSModuleRecord module) {
                module.getEnvironment().setObject(defaultSlot, module.getHostDefined());
            }
        };
        CallTarget callTarget = rootNode.getCallTarget();
        JSFunctionData functionData = JSFunctionData.create(realm.getContext(), callTarget, callTarget, 0, Strings.EMPTY_STRING, false, false, true, true);
        final JSModuleData parseModule = new JSModuleData(moduleNode, source, functionData, frameDescriptor);
        return new JSModuleRecord(parseModule, realm.getModuleLoader(), hostDefined);
    }

    @TruffleBoundary
    @Override
    public JSModuleRecord hostResolveImportedModule(JSContext context, ScriptOrModule referrer, ModuleRequest moduleRequest) {
        filterSupportedImportAttributes(context, moduleRequest);
        JSModuleLoader moduleLoader = referrer instanceof JSModuleRecord ? ((JSModuleRecord) referrer).getModuleLoader() : JSRealm.get(null).getModuleLoader();
        return moduleLoader.resolveImportedModule(referrer, moduleRequest);
    }

    private static JSModuleRecord hostResolveImportedModule(JSModuleRecord referencingModule, ModuleRequest moduleRequest) {
        filterSupportedImportAttributes(referencingModule.getContext(), moduleRequest);
        return referencingModule.getModuleLoader().resolveImportedModule(referencingModule, moduleRequest);
    }

    private static void filterSupportedImportAttributes(final JSContext context, final ModuleRequest moduleRequest) {
        if (moduleRequest.getAttributes().isEmpty()) {
            return;
        }
        Map supportedAttributes = new HashMap<>();
        for (Map.Entry attributes : moduleRequest.getAttributes().entrySet()) {
            TruffleString key = attributes.getKey();
            TruffleString value = attributes.getValue();
            if (context.getSupportedImportAttributes().contains(key)) {
                supportedAttributes.put(key, value);
            }
        }
        moduleRequest.setAttributes(supportedAttributes);
    }

    Collection getExportedNames(JSModuleRecord moduleRecord) {
        return getExportedNames(moduleRecord, new HashSet<>());
    }

    private Collection getExportedNames(JSModuleRecord moduleRecord, Set exportStarSet) {
        if (exportStarSet.contains(moduleRecord)) {
            // Assert: We've reached the starting point of an import * circularity.
            return Collections.emptySortedSet();
        }
        exportStarSet.add(moduleRecord);
        Collection exportedNames = new HashSet<>();
        Module module = moduleRecord.getModule();
        for (ExportEntry exportEntry : module.getLocalExportEntries()) {
            // Assert: module provides the direct binding for this export.
            exportedNames.add(exportEntry.getExportName());
        }
        for (ExportEntry exportEntry : module.getIndirectExportEntries()) {
            // Assert: module imports a specific binding for this export.
            exportedNames.add(exportEntry.getExportName());
        }
        for (ExportEntry exportEntry : module.getStarExportEntries()) {
            JSModuleRecord requestedModule = hostResolveImportedModule(moduleRecord, exportEntry.getModuleRequest());
            Collection starNames = getExportedNames(requestedModule, exportStarSet);
            for (TruffleString starName : starNames) {
                if (!starName.equals(Module.DEFAULT_NAME)) {
                    if (!exportedNames.contains(starName)) {
                        exportedNames.add(starName);
                    }
                }
            }
        }
        return exportedNames;
    }

    @TruffleBoundary
    @Override
    public ExportResolution resolveExport(JSModuleRecord referencingModule, TruffleString exportName) {
        return resolveExport(referencingModule, exportName, new HashSet<>());
    }

    /**
     * ResolveExport attempts to resolve an imported binding to the actual defining module and local
     * binding name. The defining module may be the module represented by the Module Record this
     * method was invoked on or some other module that is imported by that module. The parameter
     * resolveSet is use to detect unresolved circular import/export paths. If a pair consisting of
     * specific Module Record and exportName is reached that is already in resolveSet, an import
     * circularity has been encountered. Before recursively calling ResolveExport, a pair consisting
     * of module and exportName is added to resolveSet.
     *
     * If a defining module is found a Record {[[module]], [[bindingName]]} is returned. This record
     * identifies the resolved binding of the originally requested export. If no definition was
     * found or the request is found to be circular, null is returned. If the request is found to be
     * ambiguous, the string "ambiguous" is returned.
     */
    private ExportResolution resolveExport(JSModuleRecord referencingModule, TruffleString exportName, Set> resolveSet) {
        Pair resolved = new Pair<>(referencingModule, exportName);
        if (resolveSet.contains(resolved)) {
            // Assert: this is a circular import request.
            return ExportResolution.notFound();
        }
        resolveSet.add(resolved);
        Module module = referencingModule.getModule();
        for (ExportEntry exportEntry : module.getLocalExportEntries()) {
            if (exportEntry.getExportName().equals(exportName)) {
                // Assert: module provides the direct binding for this export.
                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)) {
                    // Assert: module does not provide the direct binding for this export.
                    return ExportResolution.resolved(importedModule, Module.NAMESPACE_EXPORT_BINDING_NAME);
                } else {
                    // Assert: module imports a specific binding for this export.
                    return resolveExport(importedModule, exportEntry.getImportName(), resolveSet);
                }
            }
        }
        if (exportName.equals(Module.DEFAULT_NAME)) {
            // Assert: A default export was not explicitly defined by this module.
            return ExportResolution.notFound();
            // NOTE: A default export cannot be provided by an `export *` or `export * from "mod"`.
        }
        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 {
                    // Assert: there is more than one * import that includes the requested name.
                    if (!resolution.equals(starResolution)) {
                        return ExportResolution.ambiguous();
                    }
                }
            }
        }
        return starResolution;
    }

    @TruffleBoundary
    @Override
    public JSDynamicObject getModuleNamespace(JSModuleRecord moduleRecord) {
        if (moduleRecord.getNamespace() != null) {
            return moduleRecord.getNamespace();
        }

        Collection exportedNames = getExportedNames(moduleRecord);
        List> unambiguousNames = new ArrayList<>();
        for (TruffleString exportedName : exportedNames) {
            ExportResolution resolution = resolveExport(moduleRecord, exportedName);
            if (resolution.isNull()) {
                throw Errors.createSyntaxError("Could not resolve export");
            } else if (!resolution.isAmbiguous()) {
                unambiguousNames.add(Map.entry(exportedName, resolution));
            }
        }
        unambiguousNames.sort((a, b) -> a.getKey().compareCharsUTF16Uncached(b.getKey()));
        JSModuleNamespaceObject namespace = JSModuleNamespace.create(moduleRecord.getContext(), JSRealm.get(null), moduleRecord, unambiguousNames);
        moduleRecord.setNamespace(namespace);
        return namespace;
    }

    @TruffleBoundary
    @Override
    public void moduleLinking(JSRealm realm, JSModuleRecord moduleRecord) {
        assert moduleRecord.getStatus() != Status.Linking && moduleRecord.getStatus() != Status.Evaluating;
        Deque stack = new ArrayDeque<>(4);

        try {
            innerModuleLinking(realm, moduleRecord, stack, 0);
        } catch (AbstractTruffleException e) {
            handleModuleLinkingError(moduleRecord, stack);
            throw e;
        }

        assert moduleRecord.getStatus() == Status.Linked || moduleRecord.getStatus() == Status.EvaluatingAsync || moduleRecord.getStatus() == Status.Evaluated;
        assert stack.isEmpty();
    }

    private static void handleModuleLinkingError(JSModuleRecord moduleRecord, Deque stack) {
        for (JSModuleRecord m : stack) {
            assert m.getStatus() == Status.Linking;
            m.setUnlinked();
        }
        assert moduleRecord.getStatus() == Status.Unlinked;
    }

    private int innerModuleLinking(JSRealm realm, JSModuleRecord moduleRecord, Deque stack, int index0) {
        int index = index0;
        if (moduleRecord.getStatus() == Status.Linking || moduleRecord.getStatus() == Status.Linked || moduleRecord.getStatus() == Status.EvaluatingAsync ||
                        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 = moduleRecord.getModule();
        for (ModuleRequest requestedModule : module.getRequestedModules()) {
            JSModuleRecord requiredModule = hostResolveImportedModule(moduleRecord, requestedModule);
            index = innerModuleLinking(realm, requiredModule, stack, index);
            assert requiredModule.getStatus() == Status.Linking || requiredModule.getStatus() == Status.Linked ||
                            requiredModule.getStatus() == Status.EvaluatingAsync || 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 = 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");
            }
        }

        // Initialize the environment by executing the module function.
        // It will automatically yield when the module is linked.
        var moduleFunction = JSFunction.create(realm, moduleRecord.getFunctionData());
        Object[] arguments = JSArguments.create(Undefined.instance, moduleFunction, moduleRecord);
        // The [[Construct]] target of a module is used to initialize the environment.
        JSFunction.getConstructTarget(moduleFunction).call(arguments);
    }

    @TruffleBoundary
    @Override
    public Object moduleEvaluation(JSRealm realm, JSModuleRecord moduleRecord) {
        // Evaluate ( ) Concrete Method
        JSModuleRecord module = moduleRecord;
        Deque stack = new ArrayDeque<>(4);
        if (realm.getContext().isOptionTopLevelAwait()) {
            assert module.getStatus() == Status.Linked || module.getStatus() == Status.EvaluatingAsync || module.getStatus() == Status.Evaluated;
            if (module.getStatus() == Status.EvaluatingAsync || module.getStatus() == Status.Evaluated) {
                module = module.getCycleRoot();
            }
            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.EvaluatingAsync || module.getStatus() == Status.Evaluated;
                assert module.getEvaluationError() == null;
                if (!module.isAsyncEvaluation()) {
                    assert module.getStatus() == Status.Evaluated;
                    JSFunction.call(JSArguments.create(Undefined.instance, capability.getResolve(), Undefined.instance));
                }
                assert stack.isEmpty();
            } catch (AbstractTruffleException e) {
                handleModuleEvaluationError(module, stack, e);
                throw e;
            }
            return capability.getPromise();
        } else {
            try {
                innerModuleEvaluation(realm, module, stack, 0);
            } catch (AbstractTruffleException e) {
                handleModuleEvaluationError(module, stack, e);
                throw e;
            }
            assert module.getStatus() == Status.EvaluatingAsync || module.getStatus() == Status.Evaluated;
            assert module.getEvaluationError() == null;

            assert stack.isEmpty();
            Object result = module.getExecutionResult();
            return result == null ? Undefined.instance : result;
        }
    }

    private static void handleModuleEvaluationError(JSModuleRecord module, Deque stack, AbstractTruffleException 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;
    }

    @TruffleBoundary
    private int innerModuleEvaluation(JSRealm realm, JSModuleRecord moduleRecord, Deque stack, int index0) {
        // InnerModuleEvaluation( module, stack, index )
        int index = index0;
        if (moduleRecord.getStatus() == Status.EvaluatingAsync || 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 = moduleRecord.getModule();
        for (ModuleRequest requestedModule : module.getRequestedModules()) {
            JSModuleRecord requiredModule = hostResolveImportedModule(moduleRecord, requestedModule);
            // Note: Link must have completed successfully prior to invoking this method,
            // so every requested module is guaranteed to resolve successfully.
            index = innerModuleEvaluation(realm, requiredModule, stack, index);
            assert requiredModule.getStatus() == Status.Evaluating || requiredModule.getStatus() == Status.EvaluatingAsync ||
                            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 = requiredModule.getCycleRoot();
                assert requiredModule.getStatus() == Status.EvaluatingAsync || requiredModule.getStatus() == Status.Evaluated;
                if (requiredModule.getEvaluationError() != null) {
                    throw JSRuntime.rethrow(moduleRecord.getEvaluationError());
                }
            }
            if (requiredModule.isAsyncEvaluation()) {
                moduleRecord.incPendingAsyncDependencies();
                requiredModule.appendAsyncParentModules(moduleRecord);
            }
        }
        if (moduleRecord.getPendingAsyncDependencies() > 0 || moduleRecord.hasTLA()) {
            assert !moduleRecord.isAsyncEvaluation();
            moduleRecord.setAsyncEvaluatingOrder(realm.nextAsyncEvaluationOrder());
            if (moduleRecord.getPendingAsyncDependencies() == 0) {
                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();
                if (!requiredModule.isAsyncEvaluation()) {
                    requiredModule.setStatus(Status.Evaluated);
                } else {
                    requiredModule.setStatus(Status.EvaluatingAsync);
                }
                requiredModule.setCycleRoot(moduleRecord);
                if (requiredModule.equals(moduleRecord)) {
                    break;
                }
            }
        }
        return index;
    }

    @TruffleBoundary
    private static void moduleAsyncExecution(JSRealm realm, JSModuleRecord module) {
        // ExecuteAsyncModule ( module )
        assert module.getStatus() == Status.Evaluating || module.getStatus() == Status.EvaluatingAsync;
        assert module.hasTLA();
        PromiseCapabilityRecord capability = NewPromiseCapabilityNode.createDefault(realm);
        JSFunctionObject onFulfilled = createCallAsyncModuleFulfilled(realm, module);
        JSFunctionObject onRejected = createCallAsyncModuleRejected(realm, module);
        Object then = JSObject.get(capability.getPromise(), Strings.THEN);
        JSFunction.call(JSArguments.create(capability.getPromise(), then, onFulfilled, onRejected));
        moduleExecution(realm, module, capability);
    }

    @TruffleBoundary
    private static JSFunctionObject createCallAsyncModuleFulfilled(JSRealm realm, JSModuleRecord module) {
        // AsyncModuleExecutionFulfilled ( module )
        JSFunctionData functionData = realm.getContext().getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.AsyncModuleExecutionFulfilled, (c) -> createAsyncModuleExecutionFulfilledImpl(c));
        JSFunctionObject function = JSFunction.create(realm, functionData);
        JSObjectUtil.putHiddenProperty(function, STORE_MODULE_KEY, module);
        return function;
    }

    private static JSFunctionData createAsyncModuleExecutionFulfilledImpl(JSContext context) {
        // AsyncModuleExecutionFulfilled ( module )
        class AsyncModuleFulfilledRoot extends JavaScriptRootNode {
            @Child private PropertyGetNode getModule = PropertyGetNode.createGetHidden(STORE_MODULE_KEY, context);

            @Override
            public Object execute(VirtualFrame frame) {
                Object module = getModule.getValue(JSArguments.getFunctionObject(frame.getArguments()));
                return asyncModuleExecutionFulfilled(getRealm(), (JSModuleRecord) module);
            }
        }
        return JSFunctionData.createCallOnly(context, new AsyncModuleFulfilledRoot().getCallTarget(), 1, Strings.EMPTY_STRING);
    }

    @TruffleBoundary
    private static JSFunctionObject createCallAsyncModuleRejected(JSRealm realm, JSModuleRecord module) {
        // AsyncModuleExecutionRejected ( module )
        JSFunctionData functionData = realm.getContext().getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.AsyncModuleExecutionRejected, (c) -> createAsyncModuleExecutionRejectedImpl(c));
        JSFunctionObject function = JSFunction.create(realm, functionData);
        JSObjectUtil.putHiddenProperty(function, STORE_MODULE_KEY, module);
        return function;
    }

    private static JSFunctionData createAsyncModuleExecutionRejectedImpl(JSContext context) {
        // AsyncModuleExecutionRejected ( module, error )
        class AsyncModuleExecutionRejectedRoot extends JavaScriptRootNode {
            @Child private PropertyGetNode getModule = PropertyGetNode.createGetHidden(STORE_MODULE_KEY, context);
            @Child private JavaScriptNode errorArgument = AccessIndexedArgumentNode.create(0);

            @Override
            public Object execute(VirtualFrame frame) {
                JSModuleRecord module = (JSModuleRecord) getModule.getValue(JSArguments.getFunctionObject(frame.getArguments()));
                Object error = errorArgument.execute(frame);
                return asyncModuleExecutionRejected(getRealm(), module, error);
            }
        }
        return JSFunctionData.createCallOnly(context, new AsyncModuleExecutionRejectedRoot().getCallTarget(), 1, Strings.EMPTY_STRING);
    }

    private static void gatherAvailableAncestors(JSModuleRecord module, Set execList) {
        // GatherAvailableAncestors ( module, execList )
        for (JSModuleRecord m : module.getAsyncParentModules()) {
            if (!execList.contains(m) && m.getCycleRoot().getEvaluationError() == null) {
                assert m.getStatus() == Status.EvaluatingAsync;
                assert m.getEvaluationError() == null;
                assert m.isAsyncEvaluation();
                assert m.getPendingAsyncDependencies() > 0;
                m.decPendingAsyncDependencies();
                if (m.getPendingAsyncDependencies() == 0) {
                    execList.add(m);
                    if (!m.hasTLA()) {
                        gatherAvailableAncestors(m, execList);
                    }
                }
            }
        }
    }

    @TruffleBoundary
    private static Object asyncModuleExecutionFulfilled(JSRealm realm, JSModuleRecord module) {
        if (module.getStatus() == Status.Evaluated) {
            assert module.getEvaluationError() != null;
            return Undefined.instance;
        }
        assert module.getStatus() == Status.EvaluatingAsync;
        assert module.isAsyncEvaluation();
        assert module.getEvaluationError() == null;
        module.setStatus(Status.Evaluated);
        if (module.getTopLevelCapability() != null) {
            assert module.getCycleRoot() == module;
            JSFunction.call(JSArguments.create(Undefined.instance, module.getTopLevelCapability().getResolve(), Undefined.instance));
        }
        Set execList = new TreeSet<>(new Comparator() {
            @Override
            public int compare(JSModuleRecord o1, JSModuleRecord o2) {
                return Long.compare(o1.getAsyncEvaluatingOrder(), o2.getAsyncEvaluatingOrder());
            }
        });
        gatherAvailableAncestors(module, execList);
        for (JSModuleRecord m : execList) {
            if (m.getStatus() == Status.Evaluated) {
                assert m.getEvaluationError() != null;
            } else if (m.hasTLA()) {
                moduleAsyncExecution(realm, m);
            } else {
                try {
                    moduleExecution(realm, m, null);
                    m.setStatus(Status.Evaluated);
                    if (m.getTopLevelCapability() != null) {
                        assert m.getCycleRoot() == m;
                        JSFunction.call(JSArguments.create(Undefined.instance, m.getTopLevelCapability().getResolve(), Undefined.instance));
                    }
                } catch (AbstractTruffleException ex) {
                    Object error = ex instanceof GraalJSException ? ((GraalJSException) ex).getErrorObject() : ex;
                    asyncModuleExecutionRejected(realm, m, error);
                }
            }
        }
        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";
        if (module.getStatus() == Status.Evaluated) {
            assert module.getEvaluationError() != null;
            return Undefined.instance;
        }
        assert module.getStatus() == Status.EvaluatingAsync;
        assert module.isAsyncEvaluation();
        assert module.getEvaluationError() == null;
        module.setEvaluationError(JSRuntime.getException(error));
        module.setStatus(Status.Evaluated);
        for (JSModuleRecord m : module.getAsyncParentModules()) {
            asyncModuleExecutionRejected(realm, m, error);
        }
        if (module.getTopLevelCapability() != null) {
            assert module.getCycleRoot() == module;
            JSFunction.call((JSFunctionObject) module.getTopLevelCapability().getReject(), Undefined.instance, new Object[]{error});
        }
        return Undefined.instance;
    }

    private static Object moduleExecution(JSRealm realm, JSModuleRecord moduleRecord, PromiseCapabilityRecord capability) {
        JSFunctionObject moduleFunction = JSFunction.create(realm, moduleRecord.getFunctionData());
        if (!moduleRecord.hasTLA()) {
            assert capability == null;
            return JSFunction.call(JSArguments.create(Undefined.instance, moduleFunction, moduleRecord));
        } else {
            assert capability != null;
            return JSFunction.call(JSArguments.create(Undefined.instance, moduleFunction, moduleRecord, capability));
        }
    }

    private static boolean occursExactlyOnce(JSModuleRecord moduleRecord, Collection stack) {
        return stack.stream().filter(moduleRecord::equals).count() == 1;
    }

    @Override
    public ScriptNode parseScript(JSContext context, Source source, ByteBuffer binary) {
        return ScriptNode.fromFunctionRoot((FunctionRootNode) new BinarySnapshotProvider(binary).apply(NodeFactory.getInstance(context), context, source));
    }

    @Override
    public ScriptNode parseScript(JSContext context, Source source, SnapshotProvider snapshotProvider) {
        return ScriptNode.fromFunctionRoot((FunctionRootNode) snapshotProvider.apply(NodeFactory.getInstance(context), context, source));
    }

    @Override
    public JavaScriptNode parseInlineScript(JSContext context, Source source, MaterializedFrame lexicalContextFrame, boolean isStrict, Node locationNode) {
        Environment env;
        Object scope;
        try {
            scope = NodeLibrary.getUncached().getScope(locationNode, lexicalContextFrame, true);
            env = new DebugEnvironment(null, NodeFactory.getInstance(context), context, scope);
        } catch (UnsupportedMessageException e) {
            scope = null;
            env = null;
        }
        ScriptNode script = JavaScriptTranslator.translateInlineScript(NodeFactory.getInstance(context), context, env, source, isStrict,
                        EvalNode.findActiveScriptOrModule(locationNode));
        return createInlineScriptCallNode(context, script.getFunctionData(), script.getCallTarget(), locationNode);
    }

    private static JavaScriptNode createInlineScriptCallNode(JSContext context, JSFunctionData functionData, RootCallTarget callTarget, Node locationNode) {
        return new JavaScriptNode() {
            @Child private DirectCallNode callNode = DirectCallNode.create(callTarget);
            @Child private PropertySetNode setScopeNode = PropertySetNode.createSetHidden(JSFunction.DEBUG_SCOPE_ID, context);
            @Child private NodeLibrary nodeLibrary = NodeLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit);

            @Override
            public Object execute(VirtualFrame frame) {
                JSDynamicObject closure = JSFunction.create(getRealm(), functionData);
                try {
                    Object scope = nodeLibrary.getScope(locationNode, frame, true);
                    setScopeNode.setValue(closure, scope);
                } catch (UnsupportedMessageException e) {
                    // ignore
                }
                return callNode.call(JSArguments.createZeroArg(JSFrameUtil.getThisObj(frame), closure));
            }
        };
    }

    @Override
    public Expression parseExpression(JSContext context, String sourceString) {
        return GraalJSParserHelper.parseExpression(context, Source.newBuilder(JavaScriptLanguage.ID, sourceString, "").build(), context.getParserOptions());
    }

    @Override
    public void checkFunctionSyntax(JSContext context, JSParserOptions parserOptions, String parameterList, String body, boolean generator, boolean async, String sourceName) {
        try {
            GraalJSParserHelper.checkFunctionSyntax(context, parserOptions, parameterList, body, generator, async, sourceName);
        } catch (com.oracle.js.parser.ParserException ex) {
            // throw the correct JS error
            parseFunction(context, parameterList, body, false, false, sourceName, null);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy