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

com.oracle.truffle.js.builtins.GlobalBuiltins Maven / Gradle / Ivy

/*
 * Copyright (c) 2018, 2024, 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.builtins;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
import java.util.StringTokenizer;

import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.io.TruffleProcessBuilder;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObjectLibrary;
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleStringBuilder;
import com.oracle.truffle.api.strings.TruffleStringBuilderUTF16;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.GlobalNashornExtensionParseToJSONNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.GlobalScriptingEXECNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalDecodeURINodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalEncodeURINodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalExitNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalImportScriptEngineGlobalBindingsNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalIndirectEvalNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalIsFiniteNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalIsNaNNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalLoadNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalLoadWithNewGlobalNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalParseFloatNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalParseIntNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalPrintNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalReadBufferNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalReadFullyNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalReadLineNodeGen;
import com.oracle.truffle.js.builtins.GlobalBuiltinsFactory.JSGlobalUnEscapeNodeGen;
import com.oracle.truffle.js.builtins.helper.FloatParserNode;
import com.oracle.truffle.js.builtins.helper.StringEscape;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.nodes.JSGuards;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.ScriptNode;
import com.oracle.truffle.js.nodes.access.JSConstantNode;
import com.oracle.truffle.js.nodes.cast.JSToDoubleNode;
import com.oracle.truffle.js.nodes.cast.JSToInt32Node;
import com.oracle.truffle.js.nodes.cast.JSToNumberNode;
import com.oracle.truffle.js.nodes.cast.JSToStringNode;
import com.oracle.truffle.js.nodes.cast.JSTrimWhitespaceNode;
import com.oracle.truffle.js.nodes.function.EvalNode;
import com.oracle.truffle.js.nodes.function.JSBuiltin;
import com.oracle.truffle.js.nodes.function.JSBuiltinNode;
import com.oracle.truffle.js.nodes.function.JSLoadNode;
import com.oracle.truffle.js.nodes.interop.ImportValueNode;
import com.oracle.truffle.js.runtime.BigInt;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.Evaluator;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSConsoleUtil;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSException;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.SafeInteger;
import com.oracle.truffle.js.runtime.Strings;
import com.oracle.truffle.js.runtime.SuppressFBWarnings;
import com.oracle.truffle.js.runtime.Symbol;
import com.oracle.truffle.js.runtime.builtins.BuiltinEnum;
import com.oracle.truffle.js.runtime.builtins.JSArray;
import com.oracle.truffle.js.runtime.builtins.JSArrayBuffer;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSURLDecoder;
import com.oracle.truffle.js.runtime.builtins.JSURLEncoder;
import com.oracle.truffle.js.runtime.interop.JSInteropUtil;
import com.oracle.truffle.js.runtime.objects.JSAttributes;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.JSObjectUtil;
import com.oracle.truffle.js.runtime.objects.Null;
import com.oracle.truffle.js.runtime.objects.PropertyProxy;
import com.oracle.truffle.js.runtime.objects.ScriptOrModule;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.StringBuilderProfile;

/**
 * Contains builtins for the global object.
 */
public class GlobalBuiltins extends JSBuiltinsContainer.SwitchEnum {
    public static final JSBuiltinsContainer GLOBAL_FUNCTIONS = new GlobalBuiltins();
    public static final JSBuiltinsContainer GLOBAL_SHELL = new GlobalShellBuiltins();
    public static final JSBuiltinsContainer GLOBAL_NASHORN_EXTENSIONS = new GlobalNashornScriptingBuiltins();
    public static final JSBuiltinsContainer GLOBAL_PRINT = new GlobalPrintBuiltins();
    public static final JSBuiltinsContainer GLOBAL_LOAD = new GlobalLoadBuiltins();

    protected GlobalBuiltins() {
        super(Global.class);
    }

    public enum Global implements BuiltinEnum {
        isNaN(1),
        isFinite(1),
        parseFloat(1),
        parseInt(2),
        encodeURI(1),
        encodeURIComponent(1),
        decodeURI(1),
        decodeURIComponent(1),
        eval(1),

        // Annex B
        escape(1),
        unescape(1);

        private final int length;

        Global(int length) {
            this.length = length;
        }

        @Override
        public int getLength() {
            return length;
        }

        @Override
        public boolean isAnnexB() {
            return EnumSet.of(escape, unescape).contains(this);
        }
    }

    @Override
    protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, Global builtinEnum) {
        switch (builtinEnum) {
            case isNaN:
                return JSGlobalIsNaNNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
            case isFinite:
                return JSGlobalIsFiniteNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
            case parseFloat:
                return JSGlobalParseFloatNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
            case parseInt:
                return JSGlobalParseIntNodeGen.create(context, builtin, args().fixedArgs(2).createArgumentNodes(context));
            case encodeURI:
                return JSGlobalEncodeURINodeGen.create(context, builtin, true, args().fixedArgs(1).createArgumentNodes(context));
            case encodeURIComponent:
                return JSGlobalEncodeURINodeGen.create(context, builtin, false, args().fixedArgs(1).createArgumentNodes(context));
            case decodeURI:
                return JSGlobalDecodeURINodeGen.create(context, builtin, true, args().fixedArgs(1).createArgumentNodes(context));
            case decodeURIComponent:
                return JSGlobalDecodeURINodeGen.create(context, builtin, false, args().fixedArgs(1).createArgumentNodes(context));
            case eval:
                return JSGlobalIndirectEvalNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
            case escape:
                return JSGlobalUnEscapeNodeGen.create(context, builtin, false, args().fixedArgs(1).createArgumentNodes(context));
            case unescape:
                return JSGlobalUnEscapeNodeGen.create(context, builtin, true, args().fixedArgs(1).createArgumentNodes(context));
        }
        return null;
    }

    /**
     * Built-ins for js shell (for compatibility with e.g. d8).
     */
    public static final class GlobalShellBuiltins extends JSBuiltinsContainer.SwitchEnum {
        protected GlobalShellBuiltins() {
            super(GlobalShell.class);
        }

        public enum GlobalShell implements BuiltinEnum {
            quit(1),
            readline(1),
            read(1),
            readbuffer(1);

            private final int length;

            GlobalShell(int length) {
                this.length = length;
            }

            @Override
            public int getLength() {
                return length;
            }
        }

        @Override
        protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, GlobalShell builtinEnum) {
            switch (builtinEnum) {
                case quit:
                    return JSGlobalExitNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
                case readline:
                    return JSGlobalReadLineNodeGen.create(context, builtin, false, new JavaScriptNode[]{JSConstantNode.createUndefined()});
                case read:
                    return JSGlobalReadFullyNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
                case readbuffer:
                    return JSGlobalReadBufferNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
            }
            return null;
        }
    }

    /**
     * Built-ins for print.
     */
    public static final class GlobalPrintBuiltins extends JSBuiltinsContainer.SwitchEnum {
        protected GlobalPrintBuiltins() {
            super(GlobalPrint.class);
        }

        public enum GlobalPrint implements BuiltinEnum {
            print(1),
            printErr(1);

            private final int length;

            GlobalPrint(int length) {
                this.length = length;
            }

            @Override
            public int getLength() {
                return length;
            }
        }

        @Override
        protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, GlobalPrint builtinEnum) {
            boolean noNewline = context.getLanguageOptions().printNoNewline();
            switch (builtinEnum) {
                case print:
                    return JSGlobalPrintNodeGen.create(context, builtin, false, noNewline, args().varArgs().createArgumentNodes(context));
                case printErr:
                    return JSGlobalPrintNodeGen.create(context, builtin, true, noNewline, args().varArgs().createArgumentNodes(context));
            }
            return null;
        }
    }

    /**
     * Built-ins for load.
     */
    public static final class GlobalLoadBuiltins extends JSBuiltinsContainer.SwitchEnum {
        protected GlobalLoadBuiltins() {
            super(GlobalLoad.class);
        }

        public enum GlobalLoad implements BuiltinEnum {
            load(1),
            loadWithNewGlobal(1);

            private final int length;

            GlobalLoad(int length) {
                this.length = length;
            }

            @Override
            public int getLength() {
                return length;
            }
        }

        @Override
        protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, GlobalLoad builtinEnum) {
            switch (builtinEnum) {
                case load:
                    return JSGlobalLoadNodeGen.create(context, builtin, args().fixedArgs(1).varArgs().createArgumentNodes(context));
                case loadWithNewGlobal:
                    return JSGlobalLoadWithNewGlobalNodeGen.create(context, builtin, args().fixedArgs(1).varArgs().createArgumentNodes(context));
            }
            return null;
        }
    }

    public static final class GlobalNashornScriptingBuiltins extends JSBuiltinsContainer.SwitchEnum {
        protected GlobalNashornScriptingBuiltins() {
            super(GlobalNashornScripting.class);
        }

        /**
         * Manually added in initGlobalNashornExtensions or initGlobalScriptingExtensions.
         *
         * @see JSRealm
         */
        public enum GlobalNashornScripting implements BuiltinEnum {
            exit(1),
            quit(1),
            readLine(1),
            readFully(1),
            exec(1), // $EXEC
            parseToJSON(3),
            importScriptEngineGlobalBindings(1);

            private final int length;

            GlobalNashornScripting(int length) {
                this.length = length;
            }

            @Override
            public int getLength() {
                return length;
            }
        }

        @Override
        protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, GlobalNashornScripting builtinEnum) {
            switch (builtinEnum) {
                case exit:
                case quit:
                    return JSGlobalExitNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
                case readLine:
                    return JSGlobalReadLineNodeGen.create(context, builtin, true, args().fixedArgs(1).createArgumentNodes(context));
                case readFully:
                    return JSGlobalReadFullyNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
                case parseToJSON:
                    return GlobalNashornExtensionParseToJSONNodeGen.create(context, builtin, args().fixedArgs(3).createArgumentNodes(context));
                case exec:
                    return GlobalScriptingEXECNodeGen.create(context, builtin, args().fixedArgs(2).createArgumentNodes(context));
                case importScriptEngineGlobalBindings:
                    return JSGlobalImportScriptEngineGlobalBindingsNodeGen.create(context, builtin, args().fixedArgs(1).varArgs().createArgumentNodes(context));
            }
            return null;
        }
    }

    /**
     * For load("nashorn:parser.js") compatibility.
     */
    public abstract static class GlobalNashornExtensionParseToJSONNode extends JSBuiltinNode {
        public GlobalNashornExtensionParseToJSONNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @TruffleBoundary
        @Specialization
        protected TruffleString parseToJSON(Object code0, Object name0, Object location0) {
            String code = JSRuntime.toJavaString(code0);
            String name = name0 == Undefined.instance ? "" : JSRuntime.toJavaString(name0);
            boolean location = JSRuntime.toBoolean(location0);
            return Strings.fromJavaString(getContext().getEvaluator().parseToJSON(getContext(), code, name, location));
        }
    }

    /**
     * Implements $EXEC() in Nashorn scripting mode.
     */
    public abstract static class GlobalScriptingEXECNode extends JSBuiltinNode {
        public GlobalScriptingEXECNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization
        protected Object exec(Object cmd, Object input) {
            String cmdStr = JSRuntime.toJavaString(cmd);
            String inputStr = input != Undefined.instance ? JSRuntime.toJavaString(input) : null;
            return execIntl(cmdStr, inputStr);
        }

        @TruffleBoundary
        private Object execIntl(String cmd, String input) {
            JSRealm realm = getRealm();
            TruffleLanguage.Env env = realm.getEnv();
            JSDynamicObject globalObj = realm.getGlobalObject();
            StringTokenizer tok = new StringTokenizer(cmd);
            String[] cmds = new String[tok.countTokens()];
            for (int i = 0; tok.hasMoreTokens(); i++) {
                cmds[i] = tok.nextToken();
            }

            int exitCode = 0;
            String outStr = "";
            String errStr = "";
            Process process = null;
            try {
                TruffleProcessBuilder builder = env.newProcessBuilder(cmds);

                Object envObj = JSObject.get(globalObj, Strings.DOLLAR_ENV);
                if (JSGuards.isJSObject(envObj)) {
                    JSDynamicObject dynEnvObj = (JSDynamicObject) envObj;
                    Object pwd = JSObject.get(dynEnvObj, Strings.CAPS_PWD);
                    if (pwd != Undefined.instance) {
                        builder.directory(env.getPublicTruffleFile(JSRuntime.toJavaString(pwd)));
                    }

                    builder.clearEnvironment(true);
                    for (TruffleString key : JSObject.enumerableOwnNames(dynEnvObj)) {
                        builder.environment(Strings.toJavaString(key), JSRuntime.toJavaString(JSObject.get(dynEnvObj, key)));
                    }
                }

                ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
                ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();

                builder.redirectOutput(builder.createRedirectToStream(outBuffer));
                builder.redirectError(builder.createRedirectToStream(errBuffer));

                process = builder.start();

                try (OutputStreamWriter outputStream = new OutputStreamWriter(process.getOutputStream(), realm.getCharset())) {
                    if (input != null) {
                        outputStream.write(input, 0, input.length());
                    }
                } catch (IOException ex) {
                }

                exitCode = process.waitFor();

                outStr = outBuffer.toString();
                errStr = errBuffer.toString();
            } catch (InterruptedException e) {
                if (process.isAlive()) {
                    process.destroy();
                }
                if (exitCode == 0) {
                    exitCode = process.exitValue();
                }
            } catch (IOException | SecurityException | UnsupportedOperationException | IllegalArgumentException e) {
                throw Errors.createError(e.getMessage());
            }

            TruffleString outStrTS = Strings.fromJavaString(outStr);
            JSObject.set(globalObj, Strings.$_OUT, outStrTS);
            JSObject.set(globalObj, Strings.$_ERR, Strings.fromJavaString(errStr));
            JSObject.set(globalObj, Strings.$_EXIT, exitCode);

            return outStrTS;
        }
    }

    private abstract static class JSGlobalOperation extends JSBuiltinNode {

        JSGlobalOperation(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Child private JSToStringNode toString1Node;

        protected final TruffleString toString1(Object target) {
            if (toString1Node == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                toString1Node = insert(JSToStringNode.create());
            }
            return toString1Node.executeString(target);
        }
    }

    public abstract static class JSFileLoadingOperation extends JSGlobalOperation {

        protected JSFileLoadingOperation(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @TruffleBoundary(transferToInterpreterOnException = false)
        protected Source sourceFromPath(String path, JSRealm realm) {
            Source source = null;
            try {
                TruffleFile file = resolveRelativeFilePath(path, realm.getEnv());
                if (file.isRegularFile()) {
                    source = sourceFromTruffleFile(file);
                }
            } catch (SecurityException | UnsupportedOperationException | IllegalArgumentException e) {
                throw Errors.createErrorFromException(e);
            }
            if (source == null) {
                throw cannotLoadScript(path);
            }
            return source;
        }

        @TruffleBoundary(transferToInterpreterOnException = false)
        protected static JSException cannotLoadScript(Object script) {
            return Errors.createTypeError("Cannot load script: " + JSRuntime.safeToString(script));
        }

        @TruffleBoundary
        protected static Source sourceFromTruffleFile(TruffleFile file) {
            try {
                return Source.newBuilder(JavaScriptLanguage.ID, file).build();
            } catch (IOException | SecurityException e) {
                throw Errors.createErrorFromException(e);
            }
        }

    }

    /**
     * @throws SecurityException
     */
    public static TruffleFile resolveRelativeFilePath(String path, TruffleLanguage.Env env) {
        CompilerAsserts.neverPartOfCompilation();
        TruffleFile file = env.getPublicTruffleFile(path);
        if (!file.isAbsolute() && !file.exists()) {
            TruffleFile f = tryResolveCallerRelativeFilePath(path, env);
            if (f != null) {
                return f;
            }
        }
        return file;
    }

    private static TruffleFile tryResolveCallerRelativeFilePath(String path, TruffleLanguage.Env env) {
        CompilerAsserts.neverPartOfCompilation();
        Source callerSource = JSFunction.getCallerSource();
        if (callerSource != null) {
            String callerPath = callerSource.getPath();
            if (callerPath != null) {
                TruffleFile callerFile = env.getPublicTruffleFile(callerPath);
                if (callerFile.isAbsolute()) {
                    TruffleFile file = callerFile.resolveSibling(path).normalize();
                    if (file.isRegularFile()) {
                        return file;
                    }
                }
            }
        }
        return null;
    }

    public abstract static class JSLoadOperation extends JSFileLoadingOperation {
        public JSLoadOperation(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Child private JSLoadNode loadNode;

        protected final Object runImpl(JSRealm realm, Source source) {
            if (loadNode == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                loadNode = insert(JSLoadNode.create());
            }
            return loadNode.executeLoad(source, realm);
        }

        protected final ScriptNode loadStringImpl(TruffleString name, TruffleString script) {
            CompilerAsserts.neverPartOfCompilation();
            long startTime = getContext().getLanguageOptions().profileTime() ? System.nanoTime() : 0L;
            try {
                return getContext().getEvaluator().evalCompile(getContext(), Strings.toJavaString(script), Strings.toJavaString(name));
            } finally {
                if (getContext().getLanguageOptions().profileTime()) {
                    getContext().getTimeProfiler().printElapsed(startTime, "parsing " + name);
                }
            }
        }

        @TruffleBoundary
        protected final Source sourceFromURL(URL url, JSRealm realm) {
            assert getContext().isOptionNashornCompatibilityMode() || realm.getContextOptions().isLoadFromURL();
            try {
                return Source.newBuilder(JavaScriptLanguage.ID, url).name(url.getFile()).build();
            } catch (IOException | SecurityException e) {
                throw Errors.createErrorFromException(e);
            }
        }

        @TruffleBoundary
        protected static Source sourceFromFileName(String fileName, JSRealm realm) {
            try {
                return Source.newBuilder(JavaScriptLanguage.ID, realm.getEnv().getPublicTruffleFile(fileName)).name(fileName).build();
            } catch (IOException | SecurityException | UnsupportedOperationException | IllegalArgumentException e) {
                throw Errors.createErrorFromException(e);
            }
        }

        @Override
        @TruffleBoundary(transferToInterpreterOnException = false)
        protected Source sourceFromPath(String path, JSRealm realm) {
            Source source = null;
            JSContext ctx = getContext();
            if (path.indexOf(':') >= 2) {
                if (ctx.isOptionNashornCompatibilityMode() || realm.getContextOptions().isLoadFromURL() || realm.getContextOptions().isLoadFromClasspath()) {
                    source = sourceFromURI(path, realm);
                    if (source != null) {
                        return source;
                    }
                }
            } else {
                try {
                    TruffleFile file = resolveRelativeFilePath(path, realm.getEnv());
                    if (file.isRegularFile()) {
                        source = sourceFromTruffleFile(file);
                    }
                } catch (SecurityException | UnsupportedOperationException | IllegalArgumentException e) {
                    throw Errors.createErrorFromException(e);
                }
            }

            if (source == null) {
                throw cannotLoadScript(path);
            }
            return source;
        }

        public static final String LOAD_CLASSPATH = "classpath:";
        public static final String LOAD_FX = "fx:";
        public static final String LOAD_NASHORN = "nashorn:";

        public static final String RESOURCES_PATH = "resources/";
        public static final String FX_RESOURCES_PATH = "resources/fx/";
        public static final String NASHORN_BASE_PATH = "jdk/nashorn/internal/runtime/";
        public static final String NASHORN_PARSER_JS = "nashorn:parser.js";
        public static final String NASHORN_MOZILLA_COMPAT_JS = "nashorn:mozilla_compat.js";

        private Source sourceFromURI(String resource, JSRealm realm) {
            CompilerAsserts.neverPartOfCompilation();
            assert resource.indexOf(':') != -1;
            if (JSConfig.SubstrateVM) {
                return null;
            }
            if ((getContext().isOptionNashornCompatibilityMode() &&
                            (resource.startsWith(LOAD_NASHORN) || resource.startsWith(LOAD_CLASSPATH) || resource.startsWith(LOAD_FX))) ||
                            (realm.getContextOptions().isLoadFromClasspath() && resource.startsWith(LOAD_CLASSPATH))) {
                return sourceFromResourceURL(resource, realm);
            }
            if (getContext().isOptionNashornCompatibilityMode() || realm.getContextOptions().isLoadFromURL()) {
                if (resource.startsWith("file:")) {
                    try {
                        TruffleLanguage.Env env = realm.getEnv();
                        TruffleFile truffleFile;
                        try {
                            URI uri = new URI(resource);
                            assert "file".equals(uri.getScheme());
                            truffleFile = env.getPublicTruffleFile(uri);
                        } catch (URISyntaxException e) {
                            // Not a valid URI, try parsing it as a path.
                            boolean windowsPath = env.getFileNameSeparator().equals("\\");
                            String path = windowsPath ? resource.replace('\\', '/') : resource;
                            // Skip to start of path ("file:///path" --> "/path")
                            int start = "file:".length();
                            if (path.startsWith("///", start)) {
                                start += 2;
                            }
                            // "/c:/path" --> "c:/path"
                            if (windowsPath && path.length() > start + 2 && path.charAt(start) == '/' && path.charAt(start + 2) == ':') {
                                start += 1;
                            }
                            path = path.substring(start);
                            truffleFile = env.getPublicTruffleFile(path);
                        }
                        return sourceFromTruffleFile(truffleFile);
                    } catch (SecurityException | UnsupportedOperationException | IllegalArgumentException e) {
                        throw Errors.createErrorFromException(e);
                    }
                } else {
                    try {
                        URI uri = new URI(resource);
                        return sourceFromURL(uri.toURL(), realm);
                    } catch (MalformedURLException | URISyntaxException e) {
                        throw Errors.createErrorFromException(e);
                    }
                }
            }
            return null;
        }

        private Source sourceFromResourceURL(String resource, JSRealm realm) {
            CompilerAsserts.neverPartOfCompilation();
            assert getContext().isOptionNashornCompatibilityMode() || realm.getContextOptions().isLoadFromClasspath();
            InputStream stream = null;
            if (resource.startsWith(LOAD_NASHORN)) {
                if (NASHORN_PARSER_JS.equals(resource) || NASHORN_MOZILLA_COMPAT_JS.equals(resource)) {
                    stream = JSContext.class.getResourceAsStream(RESOURCES_PATH + resource.substring(LOAD_NASHORN.length()));
                }
            } else if (!JSConfig.SubstrateVM) {
                if (resource.startsWith(LOAD_CLASSPATH)) {
                    stream = ClassLoader.getSystemResourceAsStream(resource.substring(LOAD_CLASSPATH.length()));
                } else if (resource.startsWith(LOAD_FX)) {
                    stream = ClassLoader.getSystemResourceAsStream(NASHORN_BASE_PATH + FX_RESOURCES_PATH + resource.substring(LOAD_FX.length()));
                }
            }
            if (stream != null) {
                try (Reader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
                    return Source.newBuilder(JavaScriptLanguage.ID, reader, resource).build();
                } catch (IOException | SecurityException e) {
                }
            }
            return null;
        }
    }

    /**
     * Implementation of ECMAScript 5.1 15.1.2.4 isNaN() method.
     *
     */
    public abstract static class JSGlobalIsNaNNode extends JSBuiltinNode {

        public JSGlobalIsNaNNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization
        protected static boolean isNaNInt(@SuppressWarnings("unused") int value) {
            return false;
        }

        @Specialization
        protected static boolean isNaNDouble(double value) {
            return Double.isNaN(value);
        }

        @Specialization(guards = "!isUndefined(value)")
        protected static boolean isNaNGeneric(Object value,
                        @Cached JSToDoubleNode toDoubleNode) {
            return isNaNDouble(toDoubleNode.executeDouble(value));
        }

        @Specialization(guards = "isUndefined(value)")
        protected static boolean isNaNUndefined(@SuppressWarnings("unused") Object value) {
            return true;
        }
    }

    /**
     * Implementation of ECMAScript 5.1 15.1.2.5 isFinite() method.
     *
     */
    public abstract static class JSGlobalIsFiniteNode extends JSBuiltinNode {

        public JSGlobalIsFiniteNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization
        protected static boolean isFiniteInt(@SuppressWarnings("unused") int value) {
            return true;
        }

        @Specialization
        protected static boolean isFiniteDouble(double value) {
            return !Double.isInfinite(value) && !Double.isNaN(value);
        }

        @Specialization(guards = "!isUndefined(value)")
        protected static boolean isFiniteGeneric(Object value,
                        @Cached JSToDoubleNode toDoubleNode) {
            return isFiniteDouble(toDoubleNode.executeDouble(value));
        }

        @Specialization(guards = "isUndefined(value)")
        protected static boolean isFiniteUndefined(@SuppressWarnings("unused") Object value) {
            return false;
        }
    }

    /**
     * Implementation of ECMAScript 5.1 15.1.2.3 parseFloat() method.
     */
    public abstract static class JSGlobalParseFloatNode extends JSGlobalOperation {
        @Child protected JSTrimWhitespaceNode trimWhitespaceNode;
        @Child protected TruffleString.RegionEqualByteIndexNode regionEqualsNode;
        @Child protected FloatParserNode floatParserNode;

        private static final int INFINITY_LENGTH = "Infinity".length();

        public JSGlobalParseFloatNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization
        protected int parseFloatInt(int value) {
            return value;
        }

        @Specialization
        protected long parseFloatLong(long value) {
            return value;
        }

        @Specialization
        protected double parseFloatDouble(double value,
                        @Cached InlinedConditionProfile negativeZero) {
            if (negativeZero.profile(this, JSRuntime.isNegativeZero(value))) {
                return 0;
            }
            return value;
        }

        @Specialization
        protected double parseFloatBoolean(@SuppressWarnings("unused") boolean value) {
            return Double.NaN;
        }

        @Specialization(guards = "isUndefined(value)")
        protected double parseFloatUndefined(@SuppressWarnings("unused") Object value) {
            return Double.NaN;
        }

        @Specialization(guards = "isJSNull(value)")
        protected double parseFloatNull(@SuppressWarnings("unused") Object value) {
            return Double.NaN;
        }

        @Specialization
        protected double parseFloat(TruffleString value) {
            return parseFloatIntl(value);
        }

        @Specialization(guards = {"!isJSNull(value)", "!isUndefined(value)", "!isString(value)"})
        protected double parseFloat(TruffleObject value) {
            return parseFloatIntl(toString1(value));
        }

        private double parseFloatIntl(TruffleString inputString) {
            TruffleString trimmedString = trimWhitespace(inputString);
            return parseFloatIntl2(trimmedString);
        }

        private double parseFloatIntl2(TruffleString trimmedString) {
            int trimmedLength = Strings.length(trimmedString);
            if (trimmedLength >= INFINITY_LENGTH) {
                if (regionEqualsNode == null) {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    regionEqualsNode = insert(TruffleString.RegionEqualByteIndexNode.create());
                }
                if (Strings.startsWith(regionEqualsNode, trimmedString, Strings.INFINITY) || Strings.startsWith(regionEqualsNode, trimmedString, Strings.POSITIVE_INFINITY)) {
                    return Double.POSITIVE_INFINITY;
                } else if (Strings.startsWith(regionEqualsNode, trimmedString, Strings.NEGATIVE_INFINITY)) {
                    return Double.NEGATIVE_INFINITY;
                }
            }
            if (floatParserNode == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                floatParserNode = insert(FloatParserNode.create());
            }
            return floatParserNode.parse(trimmedString);
        }

        protected TruffleString trimWhitespace(TruffleString s) {
            if (trimWhitespaceNode == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                trimWhitespaceNode = insert(JSTrimWhitespaceNode.create());
            }
            return trimWhitespaceNode.executeString(s);
        }
    }

    /**
     * Implementation of ECMAScript 5.1 15.1.2.2 parseInt() method.
     *
     */
    public abstract static class JSGlobalParseIntNode extends JSBuiltinNode {

        public JSGlobalParseIntNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization(guards = "isUndefined(radix0)")
        protected int parseIntNoRadix(int value, @SuppressWarnings("unused") Object radix0) {
            return value;
        }

        @Specialization(guards = "!isUndefined(radix0)")
        protected Object parseIntInt(int value, Object radix0,
                        @Cached @Shared JSToInt32Node toInt32,
                        @Cached @Shared InlinedBranchProfile needsRadixConversion,
                        @Cached @Shared InlinedBranchProfile needsNaN) {
            int radix = toInt32.executeInt(radix0);
            if (radix == 10 || radix == 0) {
                return value;
            }
            if (radix < 2 || radix > 36) {
                needsNaN.enter(this);
                return Double.NaN;
            }
            needsRadixConversion.enter(this);
            return convertToRadix(value, radix);
        }

        @Specialization(guards = {"hasRegularToStringInInt32Range(value)", "isUndefined(radix0)"})
        protected int parseIntDoubleToInt(double value, @SuppressWarnings("unused") Object radix0) {
            return (int) value;
        }

        @Specialization(guards = {"hasRegularToString(value)", "isUndefined(radix0)"})
        protected double parseIntDoubleNoRadix(double value, @SuppressWarnings("unused") Object radix0) {
            return JSRuntime.truncateDouble(value);
        }

        // double specializations should not be used for numbers
        // that use a scientific notation when stringified
        // (parseInt(1e21) === parseInt('1e21') === 1)
        protected static boolean hasRegularToString(double value) {
            return (-1e21 < value && value <= -1e-6) || (1e-6 <= value && value < 1e21);
        }

        protected static boolean hasRegularToStringInInt32Range(double value) {
            return (Integer.MIN_VALUE - 1.0 < value && value <= -1) || (value == 0) || (1e-6 <= value && value < Integer.MAX_VALUE + 1.0);
        }

        @Specialization(guards = "hasRegularToString(value)")
        protected double parseIntDouble(double value, Object radix0,
                        @Cached @Shared JSToInt32Node toInt32,
                        @Cached @Shared InlinedBranchProfile needsRadixConversion,
                        @Cached @Shared InlinedBranchProfile needsNaN) {
            int radix = toInt32.executeInt(radix0);
            if (radix == 0) {
                radix = 10;
            } else if (radix < 2 || radix > 36) {
                needsNaN.enter(this);
                return Double.NaN;
            }
            double truncated = JSRuntime.truncateDouble(value);
            if (radix == 10) {
                return truncated;
            } else {
                needsRadixConversion.enter(this);
                return convertToRadix(truncated, radix);
            }
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"radix == 10", "stringLength(string) < 15"})
        protected Object parseIntStringInt10(TruffleString string, int radix,
                        @Cached @Shared TruffleString.ReadCharUTF16Node readRawNode,
                        @Cached @Shared InlinedBranchProfile needsRadix16,
                        @Cached @Shared InlinedBranchProfile needsDontFitLong) {
            assert isShortStringInt10(string, radix);

            int pos = 0;
            int lastIdx = Strings.length(string);
            boolean negate = false;

            if (lastIdx == 0) { // empty string
                return Double.NaN;
            }

            char firstChar = Strings.charAt(readRawNode, string, pos);
            if (!JSRuntime.isAsciiDigit(firstChar)) {
                if (JSRuntime.isWhiteSpaceOrLineTerminator(firstChar)) {
                    pos = JSRuntime.firstNonWhitespaceIndex(string, readRawNode);
                    if (Strings.length(string) <= pos) {
                        return Double.NaN;
                    }
                    firstChar = Strings.charAt(readRawNode, string, pos);
                }
                if (firstChar == '-') {
                    pos++;
                    negate = true;
                } else if (firstChar == '+') {
                    pos++;
                }
                if (pos >= lastIdx) {
                    return Double.NaN;
                }
            }

            int firstPos = pos;
            long value = 0;
            while (pos < lastIdx) {
                char c = Strings.charAt(readRawNode, string, pos);
                int cval = JSRuntime.valueInRadix10(c);
                if (cval < 0) {
                    if (pos != firstPos) {
                        break;
                    } else {
                        return Double.NaN;
                    }
                }
                value *= 10;
                value += cval;
                pos++;
            }

            if (value == 0 && negate) {
                return -0.0; // long case below cannot handle negative zero
            }

            assert value >= 0;
            long signedValue = negate ? -value : value;

            if (value <= Integer.MAX_VALUE) {
                return (int) signedValue;
            } else {
                return (double) signedValue;
            }
        }

        protected static boolean isShortStringInt10(Object input, Object radix) {
            return input instanceof TruffleString inputStr && Strings.length(inputStr) < 15 && radix instanceof Integer radixInt && radixInt == 10;
        }

        @Specialization(guards = "!isShortStringInt10(input, radix0)")
        protected static Object parseIntGeneric(Object input, Object radix0,
                        @Bind("this") Node node,
                        @Cached JSToStringNode toStringNode,
                        @Cached @Shared JSToInt32Node toInt32,
                        @Cached @Shared InlinedBranchProfile needsNaN,
                        @Cached @Shared InlinedBranchProfile needsRadix16,
                        @Cached @Shared InlinedBranchProfile needsDontFitLong,
                        @Cached @Shared TruffleString.ReadCharUTF16Node readRawNode,
                        @Cached TruffleString.SubstringByteIndexNode substringNode) {
            TruffleString inputStr = toStringNode.executeString(input);

            int firstIdx = JSRuntime.firstNonWhitespaceIndex(inputStr, readRawNode);
            int lastIdx = JSRuntime.lastNonWhitespaceIndex(inputStr, readRawNode) + 1;

            int radix = toInt32.executeInt(radix0);
            if (lastIdx <= firstIdx) {
                needsNaN.enter(node);
                return Double.NaN;
            }

            char firstChar = Strings.charAt(readRawNode, inputStr, firstIdx);
            boolean negate = false;
            if (firstChar == '-') {
                negate = true;
                firstIdx++;
            } else if (firstChar == '+') {
                firstIdx++;
            }

            if (radix == 16 || radix == 0) {
                needsRadix16.enter(node);
                if (hasHexStart(readRawNode, inputStr, firstIdx, lastIdx)) {
                    firstIdx += 2;
                    radix = 16; // could be 0
                } else if (radix == 0) {
                    radix = 10;
                }
            } else if (radix < 2 || radix > 36) {
                needsNaN.enter(node);
                return Double.NaN;
            }

            int lastValidIdx = validStringLastIdx(readRawNode, inputStr, radix, firstIdx, lastIdx);
            int len = lastValidIdx - firstIdx;
            if (len <= 0) {
                needsNaN.enter(node);
                return Double.NaN;
            }
            if ((radix <= 10 && len >= 18) || (10 < radix && radix <= 16 && len >= 15) || (radix > 16 && len >= 12)) {
                needsDontFitLong.enter(node);
                if (radix == 10) {
                    // parseRawDontFitLong() can produce an incorrect result
                    // due to subtle rounding errors (for radix 10) but the spec.
                    // requires exact processing for this radix
                    return parseDouble(Strings.lazySubstring(substringNode, inputStr, firstIdx, len), negate);
                } else {
                    return JSRuntime.parseRawDontFitLong(inputStr, radix, firstIdx, lastValidIdx, negate);
                }
            }
            return JSRuntime.parseRawFitsLong(inputStr, radix, firstIdx, lastValidIdx, negate);
        }

        @TruffleBoundary
        private static double parseDouble(TruffleString s, boolean negate) {
            double value = Double.parseDouble(Strings.toJavaString(s));
            return negate ? -value : value;
        }

        private static Object convertToRadix(int inputValue, int radix) {
            assert radix >= 2 && radix <= 36;
            boolean negative = inputValue < 0;
            long value = inputValue;
            if (negative) {
                value = -value;
            }
            long result = 0;
            long radixVal = 1;
            while (value != 0) {
                long digit = value % 10;
                value /= 10;
                if (digit >= radix) {
                    if (value == 0) { // first digit is invalid
                        return Double.NaN;
                    } else {
                        // ignore the digits seen so far and try again
                        result = 0;
                        radixVal = 1;
                        continue;
                    }
                }
                result += digit * radixVal;
                radixVal *= radix;
            }
            if (negative) {
                result = -result;
            }
            return JSRuntime.longToIntOrDouble(result);
        }

        @SuppressFBWarnings(value = "FL_FLOATS_AS_LOOP_COUNTERS", justification = "intentional use of floating-point variable as loop counter")
        private static double convertToRadix(double inputValue, int radix) {
            assert (radix >= 2 && radix <= 36);
            boolean negative = inputValue < 0;
            double value = negative ? -inputValue : inputValue;
            double result = 0;
            double radixVal = 1;
            while (value != 0) {
                double digit = (value % 10);
                value -= digit;
                value /= 10;
                if (digit >= radix) {
                    if (value == 0) { // first digit is invalid
                        return Double.NaN;
                    } else {
                        // ignore the digits seen so far and try again
                        result = 0;
                        radixVal = 1;
                        continue;
                    }
                }
                result += digit * radixVal;
                radixVal *= radix;
            }
            return negative ? -result : result;
        }

        // searches for '0x12345', assumes NO sign!
        private static boolean hasHexStart(TruffleString.ReadCharUTF16Node readRawNode, TruffleString inputString, int firstPos, int lastPos) {
            int length = lastPos - firstPos;
            if (length >= 2 && Strings.charAt(readRawNode, inputString, firstPos) == '0') {
                char c1 = Strings.charAt(readRawNode, inputString, firstPos + 1);
                return (c1 == 'x' || c1 == 'X');
            }
            return false;
        }

        private static int validStringLastIdx(TruffleString.ReadCharUTF16Node readRawNode, TruffleString input, int radix, int firstIdx, int lastIdx) {
            int pos = firstIdx;
            while (pos < lastIdx) {
                char c = Strings.charAt(readRawNode, input, pos);
                if (JSRuntime.valueInRadix(c, radix) == -1) {
                    break;
                }
                pos++;
            }
            return pos;
        }
    }

    /**
     * Implementation of ECMAScript 5.1 15.1.3.3 encodeURI() and of ECMAScript 5.1 15.1.3.4
     * encodeURIComponent().
     *
     */
    public abstract static class JSGlobalEncodeURINode extends JSGlobalOperation {

        private final JSURLEncoder encoder;

        public JSGlobalEncodeURINode(JSContext context, JSBuiltin builtin, boolean isSpecial) {
            super(context, builtin);
            this.encoder = new JSURLEncoder(isSpecial);
        }

        @Specialization
        protected TruffleString encodeURI(Object value) {
            return encoder.encode(toString1(value));
        }
    }

    /**
     * Implementation of ECMAScript 5.1 15.1.3.1 decodeURI() and of ECMAScript 5.1 15.1.3.2
     * decodeURIComponent().
     *
     */
    public abstract static class JSGlobalDecodeURINode extends JSGlobalOperation {

        private final JSURLDecoder decoder;

        public JSGlobalDecodeURINode(JSContext context, JSBuiltin builtin, boolean isSpecial) {
            super(context, builtin);
            this.decoder = new JSURLDecoder(isSpecial);
        }

        @Specialization
        protected Object decodeURI(Object value) {
            return decoder.decode(toString1(value));
        }
    }

    /**
     * This node is used only for indirect calls to eval. Direct calls are handled by
     * {@link EvalNode}.
     */
    public abstract static class JSGlobalIndirectEvalNode extends JSBuiltinNode {
        @Child private IndirectCallNode callNode = IndirectCallNode.create();

        public JSGlobalIndirectEvalNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization
        protected Object indirectEvalString(TruffleString source,
                        @Cached @Shared TruffleString.ToJavaStringNode toJavaString) {
            JSRealm realm = getRealm();
            return parseIndirectEval(realm, Strings.toJavaString(toJavaString, source)).runEval(callNode, realm);
        }

        @InliningCutoff
        @Specialization(guards = "isForeignObject(source)", limit = "3")
        protected Object indirectEvalForeignObject(Object source,
                        @CachedLibrary("source") InteropLibrary interop,
                        @Cached TruffleString.SwitchEncodingNode switchEncoding,
                        @Cached @Shared TruffleString.ToJavaStringNode toJavaString) {
            if (interop.isString(source)) {
                return indirectEvalString(Strings.interopAsTruffleString(source, interop, switchEncoding), toJavaString);
            } else {
                return source;
            }
        }

        @TruffleBoundary(transferToInterpreterOnException = false)
        private ScriptNode parseIndirectEval(JSRealm realm, String sourceCode) {
            assert isCallerSensitive();
            Node caller = EvalNode.findCallNode(realm);
            String sourceName = EvalNode.formatEvalOrigin(caller, getContext(), Evaluator.EVAL_SOURCE_NAME);
            ScriptOrModule activeScriptOrModule = EvalNode.findActiveScriptOrModule(caller);
            Source source = Source.newBuilder(JavaScriptLanguage.ID, sourceCode, sourceName).cached(false).build();
            return getContext().getEvaluator().parseEval(getContext(), this, source, activeScriptOrModule);
        }

        @Specialization
        protected int indirectEvalInt(int source) {
            return source;
        }

        @Specialization
        protected SafeInteger indirectEvalSafeInteger(SafeInteger source) {
            return source;
        }

        @Specialization
        protected long indirectEvalLong(long source) {
            return source;
        }

        @Specialization
        protected double indirectEvalDouble(double source) {
            return source;
        }

        @Specialization
        protected boolean indirectEvalBoolean(boolean source) {
            return source;
        }

        @Specialization
        protected Symbol indirectEvalSymbol(Symbol source) {
            return source;
        }

        @Specialization
        protected BigInt indirectEvalBigInt(BigInt source) {
            return source;
        }

        @Specialization
        public JSDynamicObject indirectEvalJSType(JSDynamicObject object) {
            return object;
        }

        @Override
        public boolean isCallerSensitive() {
            return true;
        }
    }

    /**
     * Implementation of ECMAScript 5.1 B.2.1 escape() method and of ECMAScript 5.1 B.2.2 unescape()
     * method.
     *
     */
    public abstract static class JSGlobalUnEscapeNode extends JSGlobalOperation {
        private final boolean unescape;

        public JSGlobalUnEscapeNode(JSContext context, JSBuiltin builtin, boolean unescape) {
            super(context, builtin);
            this.unescape = unescape;
        }

        @Specialization
        protected TruffleString escape(Object value) {
            TruffleString s = toString1(value);
            return unescape ? StringEscape.unescape(s) : StringEscape.escape(s);
        }
    }

    /**
     * Non-standard print()/printErr() method to write to the console.
     */
    public abstract static class JSGlobalPrintNode extends JSGlobalOperation {

        private final boolean useErr;
        private final boolean noNewLine;

        public JSGlobalPrintNode(JSContext context, JSBuiltin builtin, boolean useErr, boolean noNewline) {
            super(context, builtin);
            this.useErr = useErr;
            this.noNewLine = noNewline;
        }

        public abstract Object executeObjectArray(Object[] args);

        @Specialization
        protected Object print(Object[] arguments,
                        @Cached InlinedConditionProfile argumentsCount,
                        @Cached(parameters = "getContext().getStringLengthLimit()") StringBuilderProfile builderProfile,
                        @Cached TruffleStringBuilder.AppendCodePointNode appendCodePointNode,
                        @Cached TruffleStringBuilder.AppendStringNode appendStringNode,
                        @Cached TruffleStringBuilder.ToStringNode toStringNode) {
            // without a StringBuilder, synchronization fails testnashorn JDK-8041998.js
            JSRealm realm = getRealm();
            TruffleStringBuilderUTF16 sb = builderProfile.newStringBuilder();
            JSConsoleUtil consoleUtil = realm.getConsoleUtil();
            if (consoleUtil.getConsoleIndentation() > 0) {
                builderProfile.repeat(appendCodePointNode, sb, ' ', consoleUtil.getConsoleIndentation() * 2);
            }
            if (argumentsCount.profile(this, arguments.length == 1)) {
                builderProfile.append(appendStringNode, sb, toString1(arguments[0]));
            } else {
                for (int i = 0; i < arguments.length; i++) {
                    if (i != 0) {
                        builderProfile.append(appendCodePointNode, sb, ' ');
                    }
                    builderProfile.append(appendStringNode, sb, toString1(arguments[i]));
                }
            }
            if (!noNewLine) {
                builderProfile.append(appendStringNode, sb, Strings.LINE_SEPARATOR);
            }
            TruffleString string = StringBuilderProfile.toString(toStringNode, sb);
            return printString(string, realm);
        }

        @TruffleBoundary
        private Object printString(TruffleString string, JSRealm realm) {
            PrintWriter writer = useErr ? realm.getErrorWriter() : realm.getOutputWriter();
            writer.print(string);
            writer.flush();
            return Undefined.instance;
        }
    }

    @ImportStatic({JSInteropUtil.class, JSConfig.class})
    public abstract static class JSGlobalLoadNode extends JSLoadOperation {

        public JSGlobalLoadNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization
        protected Object loadString(TruffleString path, Object[] args) {
            JSRealm realm = getRealm();
            return loadFromPath(path, realm, args);
        }

        protected Object loadFromPath(TruffleString path, JSRealm realm, @SuppressWarnings("unused") Object[] args) {
            Source source = sourceFromPath(Strings.toJavaString(path), realm);
            return runImpl(realm, source);
        }

        @Specialization(guards = "isForeignObject(scriptObj)")
        protected Object loadTruffleObject(Object scriptObj, Object[] args,
                        @CachedLibrary(limit = "InteropLibraryLimit") InteropLibrary interop) {
            JSRealm realm = getRealm();
            TruffleLanguage.Env env = realm.getEnv();
            if (env.isHostObject(scriptObj)) {
                if (getContext().isOptionNashornCompatibilityMode() && env.asHostObject(scriptObj) instanceof URL) {
                    return loadURL(realm, (URL) env.asHostObject(scriptObj));
                } else if (interop.isMemberInvocable(scriptObj, "getPath")) {
                    // argument is most likely a java.io.File
                    return loadFile(realm, fileGetPath(scriptObj, interop));
                }
            }
            if (interop.isNull(scriptObj)) {
                throw cannotLoadScript(scriptObj);
            }
            TruffleString stringPath = toString1(scriptObj);
            return loadFromPath(stringPath, realm, args);
        }

        private String fileGetPath(Object scriptObj, InteropLibrary interop) {
            try {
                return interop.asString(interop.invokeMember(scriptObj, "getPath"));
            } catch (UnsupportedMessageException | ArityException | UnknownIdentifierException | UnsupportedTypeException e) {
                throw Errors.createTypeErrorInteropException(scriptObj, e, "getPath", this);
            }
        }

        @Specialization
        protected Object loadScriptObj(JSObject scriptObj, Object[] args) {
            if (JSObject.hasProperty(scriptObj, Strings.EVAL_OBJ_FILE_NAME) && JSObject.hasProperty(scriptObj, Strings.EVAL_OBJ_SOURCE)) {
                Object scriptNameObj = JSObject.get(scriptObj, Strings.EVAL_OBJ_FILE_NAME);
                Object sourceObj = JSObject.get(scriptObj, Strings.EVAL_OBJ_SOURCE);
                return evalObjectLiteral(scriptNameObj, sourceObj, args);
            } else {
                throw cannotLoadScript(scriptObj);
            }
        }

        private Object evalObjectLiteral(Object scriptName, Object scriptSource, Object[] args) {
            JSRealm realm = getRealm();
            return evalImpl(realm, toString1(scriptName), toString1(scriptSource), args);
        }

        @Specialization(guards = {"!isString(fileName)", "!isForeignObject(fileName)", "!isJSObject(fileName)"})
        protected Object loadConvertToString(Object fileName, Object[] args) {
            return loadString(toString1(fileName), args);
        }

        protected Object loadFile(JSRealm realm, String filePath) {
            return runImpl(realm, sourceFromFileName(filePath, realm));
        }

        protected Object loadURL(JSRealm realm, URL url) {
            assert getContext().isOptionNashornCompatibilityMode();
            return runImpl(realm, sourceFromURL(url, realm));
        }

        @TruffleBoundary(transferToInterpreterOnException = false)
        protected Object evalImpl(JSRealm realm, TruffleString fileName, TruffleString source, @SuppressWarnings("unused") Object[] args) {
            return loadStringImpl(fileName, source).run(realm);
        }
    }

    /**
     * Implementation of non-standard method loadWithNewGlobal() as defined by Nashorn.
     *
     */
    public abstract static class JSGlobalLoadWithNewGlobalNode extends JSGlobalLoadNode {

        public JSGlobalLoadWithNewGlobalNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Override
        @TruffleBoundary(transferToInterpreterOnException = false)
        protected Object evalImpl(JSRealm realm, TruffleString fileName, TruffleString source, Object[] args) {
            JSRealm childRealm = realm.createChildRealm();
            JSRealm mainRealm = JSRealm.getMain(this);
            JSRealm prevRealm = mainRealm.enterRealm(this, childRealm);
            try {
                JSDynamicObject argumentsArray = JSArray.createConstant(getContext(), childRealm, args);
                assert JSObject.getPrototype(argumentsArray) == childRealm.getArrayPrototype();
                JSRuntime.createDataProperty(childRealm.getGlobalObject(), JSFunction.ARGUMENTS, argumentsArray);
                return loadStringImpl(fileName, source).run(childRealm);
            } finally {
                mainRealm.leaveRealm(this, prevRealm);
            }
        }

        @Override
        @TruffleBoundary
        protected Object loadFromPath(TruffleString path, JSRealm realm, Object[] args) {
            JSRealm childRealm = realm.createChildRealm();
            JSRealm mainRealm = JSRealm.getMain(this);
            JSRealm prevRealm = mainRealm.enterRealm(this, childRealm);
            try {
                JSDynamicObject argumentsArray = JSArray.createConstant(getContext(), childRealm, args);
                assert JSObject.getPrototype(argumentsArray) == childRealm.getArrayPrototype();
                JSRuntime.createDataProperty(childRealm.getGlobalObject(), JSFunction.ARGUMENTS, argumentsArray);
                Source source = sourceFromPath(Strings.toJavaString(path), childRealm);
                return runImpl(childRealm, source);
            } finally {
                mainRealm.leaveRealm(this, prevRealm);
            }
        }
    }

    /**
     * Non-standard global exit function to provide compatibility with Nashorn (exit() and quit())
     * and V8 (only quit()) shells. Available as quit() if the {@code js.shell} option is enabled.
     */
    public abstract static class JSGlobalExitNode extends JSBuiltinNode {

        public JSGlobalExitNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization(guards = "isUndefined(arg)")
        protected Object exit(@SuppressWarnings("unused") Object arg) {
            return exit(0);
        }

        @Specialization
        protected Object exit(int exitCode) {
            getRealm().getEnv().getContext().closeExited(this, exitCode);
            return Undefined.instance;
        }

        @Specialization
        protected Object exit(Object arg,
                        @Cached JSToNumberNode toNumberNode) {
            int exitCode = (int) JSRuntime.toInteger(toNumberNode.executeNumber(arg));
            return exit(exitCode);
        }
    }

    /**
     * Non-standard readline() for V8 compatibility, and readLine(prompt) for Nashorn compatibility
     * (only available in nashorn-compat mode with scripting enabled).
     *
     * The prompt argument is only accepted and printed by Nashorn's variant.
     */
    public abstract static class JSGlobalReadLineNode extends JSGlobalOperation {

        // null for Nashorn, undefined for V8
        private final boolean returnNullWhenEmpty;

        public JSGlobalReadLineNode(JSContext context, JSBuiltin builtin, boolean returnNullWhenEmpty) {
            super(context, builtin);
            this.returnNullWhenEmpty = returnNullWhenEmpty;
        }

        @Specialization
        protected Object readLine(Object prompt) {
            TruffleString promptString = null;
            if (prompt != Undefined.instance) {
                promptString = toString1(prompt);
            }
            JSRealm realm = getRealm();
            return doReadLine(promptString, realm);
        }

        @TruffleBoundary
        private Object doReadLine(TruffleString promptString, JSRealm realm) {
            if (promptString != null) {
                realm.getOutputWriter().print(Strings.toJavaString(promptString));
            }
            try {
                final BufferedReader inReader = new BufferedReader(new InputStreamReader(realm.getEnv().in(), realm.getCharset()));
                String result = inReader.readLine();
                return result == null ? (returnNullWhenEmpty ? Null.instance : Undefined.instance) : Strings.fromJavaString(result);
            } catch (Exception ex) {
                throw Errors.createError(ex.getMessage());
            }
        }

    }

    static TruffleFile getFileFromArgument(Object arg, TruffleLanguage.Env env) {
        CompilerAsserts.neverPartOfCompilation();
        try {
            String path;
            if (arg instanceof TruffleString str) {
                path = Strings.toJavaString(str);
            } else {
                // if arg is a java.io.File, invokes toString() (equivalent to getPath()).
                path = JSRuntime.toJavaString(arg);
            }

            TruffleFile file = resolveRelativeFilePath(path, env);
            if (!file.isRegularFile()) {
                throw Errors.createNotAFileError(path);
            }
            return file;
        } catch (SecurityException | UnsupportedOperationException | IllegalArgumentException e) {
            throw Errors.createErrorFromException(e);
        }
    }

    /**
     * Non-standard read() and readFully() to provide compatibility with V8 and Nashorn,
     * respectively.
     */
    public abstract static class JSGlobalReadFullyNode extends JSBuiltinNode {
        private static final int BUFFER_SIZE = 2048;

        public JSGlobalReadFullyNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization
        @TruffleBoundary(transferToInterpreterOnException = false)
        protected TruffleString read(Object fileParam) {
            TruffleFile file = getFileFromArgument(fileParam, getRealm().getEnv());

            try {
                return readImpl(file.newBufferedReader());
            } catch (Exception ex) {
                throw Errors.createErrorFromException(ex);
            }
        }

        private static TruffleString readImpl(BufferedReader reader) throws IOException {
            var sb = Strings.builderCreate();
            final char[] arr = new char[BUFFER_SIZE];
            try {
                int numChars;
                while ((numChars = reader.read(arr, 0, arr.length)) > 0) {
                    Strings.builderAppend(sb, new String(arr, 0, numChars));
                }
            } finally {
                reader.close();
            }
            return Strings.builderToString(sb);
        }
    }

    /**
     * Non-standard readbuffer() to provide compatibility with V8.
     */
    public abstract static class JSGlobalReadBufferNode extends JSBuiltinNode {

        public JSGlobalReadBufferNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization
        @TruffleBoundary(transferToInterpreterOnException = false)
        protected final JSDynamicObject readbuffer(Object fileParam) {
            JSRealm realm = getRealm();
            TruffleFile file = getFileFromArgument(fileParam, realm.getEnv());

            try {
                final byte[] bytes = file.readAllBytes();

                final JSDynamicObject arrayBuffer;
                if (getContext().isOptionDirectByteBuffer()) {
                    ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
                    buffer.put(bytes);
                    buffer.rewind();
                    arrayBuffer = JSArrayBuffer.createDirectArrayBuffer(getContext(), realm, buffer);
                } else {
                    arrayBuffer = JSArrayBuffer.createArrayBuffer(getContext(), realm, bytes);
                }
                return arrayBuffer;
            } catch (Exception ex) {
                throw Errors.createErrorFromException(ex);
            }
        }
    }

    /**
     * Non-standard import helper function for support of global scope bindings in
     * GraalJSScriptEngine.
     */
    abstract static class JSGlobalImportScriptEngineGlobalBindingsNode extends JSBuiltinNode {

        JSGlobalImportScriptEngineGlobalBindingsNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization
        final Object importGlobalContext(Object globalContextBindings) {
            doImport(globalContextBindings);
            return Undefined.instance;
        }

        @TruffleBoundary
        private void doImport(Object globalContextBindings) {
            JSRealm realm = getRealm();
            JSDynamicObject globalObject = realm.getGlobalObject();

            InteropLibrary bindingsInterop = InteropLibrary.getUncached(globalContextBindings);
            try {
                Object members = bindingsInterop.getMembers(globalContextBindings);
                InteropLibrary membersInterop = InteropLibrary.getUncached(members);
                long size = membersInterop.getArraySize(members);
                for (long i = 0; i < size; i++) {
                    Object hashKey = membersInterop.readArrayElement(members, i);
                    InteropLibrary keyInterop = InteropLibrary.getUncached(hashKey);
                    if (keyInterop.isString(hashKey)) {
                        TruffleString stringKey = Strings.interopAsTruffleString(hashKey, keyInterop);
                        Object value = DynamicObjectLibrary.getUncached().getOrDefault(globalObject, stringKey, Undefined.instance);
                        if ((value == Undefined.instance || value instanceof ScriptEngineGlobalScopeBindingsPropertyProxy &&
                                        ((ScriptEngineGlobalScopeBindingsPropertyProxy) value).get(globalObject) == Undefined.instance) &&
                                        !JSObject.getPrototype(globalObject).getShape().hasProperty(stringKey)) {
                            JSObjectUtil.defineProxyProperty(globalObject, stringKey, new ScriptEngineGlobalScopeBindingsPropertyProxy(stringKey, globalContextBindings, bindingsInterop),
                                            JSAttributes.getDefault());
                        }
                    }
                }
            } catch (UnsupportedMessageException | InvalidArrayIndexException e) {
                throw Errors.createTypeErrorInteropException(globalContextBindings, e, "importScriptEngineGlobalBindings", this);
            }
        }

        private static final class ScriptEngineGlobalScopeBindingsPropertyProxy extends PropertyProxy {

            private final TruffleString key;
            private final Object globalContextBindings;
            private final InteropLibrary bindingsInterop;

            ScriptEngineGlobalScopeBindingsPropertyProxy(TruffleString key, Object globalContextBindings, InteropLibrary bindingsInterop) {
                this.key = key;
                this.globalContextBindings = globalContextBindings;
                this.bindingsInterop = bindingsInterop;
            }

            @Override
            @TruffleBoundary
            public Object get(JSDynamicObject store) {
                return JSInteropUtil.readMemberOrDefault(globalContextBindings, key, Undefined.instance, bindingsInterop, ImportValueNode.getUncached());
            }

            @TruffleBoundary
            @Override
            public boolean set(JSDynamicObject store, Object value) {
                JSObjectUtil.defineDataProperty(store, key, value, JSAttributes.getDefault());
                return true;
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy