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

com.oracle.truffle.js.parser.GraalJSTranslator 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.parser;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

import com.oracle.js.parser.Lexer;
import com.oracle.js.parser.TokenType;
import com.oracle.js.parser.ir.AccessNode;
import com.oracle.js.parser.ir.BaseNode;
import com.oracle.js.parser.ir.BinaryNode;
import com.oracle.js.parser.ir.Block;
import com.oracle.js.parser.ir.BlockExpression;
import com.oracle.js.parser.ir.BlockStatement;
import com.oracle.js.parser.ir.CallNode;
import com.oracle.js.parser.ir.CaseNode;
import com.oracle.js.parser.ir.CatchNode;
import com.oracle.js.parser.ir.ClassElement;
import com.oracle.js.parser.ir.ClassNode;
import com.oracle.js.parser.ir.DebuggerNode;
import com.oracle.js.parser.ir.Expression;
import com.oracle.js.parser.ir.ExpressionStatement;
import com.oracle.js.parser.ir.ForNode;
import com.oracle.js.parser.ir.FunctionNode;
import com.oracle.js.parser.ir.IdentNode;
import com.oracle.js.parser.ir.IfNode;
import com.oracle.js.parser.ir.IndexNode;
import com.oracle.js.parser.ir.JoinPredecessorExpression;
import com.oracle.js.parser.ir.LexicalContext;
import com.oracle.js.parser.ir.LexicalContextNode;
import com.oracle.js.parser.ir.LiteralNode;
import com.oracle.js.parser.ir.Module;
import com.oracle.js.parser.ir.Module.ImportEntry;
import com.oracle.js.parser.ir.Module.ModuleRequest;
import com.oracle.js.parser.ir.ObjectNode;
import com.oracle.js.parser.ir.ParameterNode;
import com.oracle.js.parser.ir.PropertyNode;
import com.oracle.js.parser.ir.Scope;
import com.oracle.js.parser.ir.Statement;
import com.oracle.js.parser.ir.Symbol;
import com.oracle.js.parser.ir.TemplateLiteralNode;
import com.oracle.js.parser.ir.TernaryNode;
import com.oracle.js.parser.ir.TryNode;
import com.oracle.js.parser.ir.UnaryNode;
import com.oracle.js.parser.ir.VarNode;
import com.oracle.js.parser.ir.WithNode;
import com.oracle.js.parser.ir.visitor.NodeVisitor;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.nodes.LoopNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeUtil;
import com.oracle.truffle.api.nodes.RepeatingNode;
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.decorators.DecoratorListEvaluationNode;
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.NodeFactory.BinaryOperation;
import com.oracle.truffle.js.nodes.NodeFactory.UnaryOperation;
import com.oracle.truffle.js.nodes.ReadNode;
import com.oracle.truffle.js.nodes.RepeatableNode;
import com.oracle.truffle.js.nodes.access.ArrayLiteralNode;
import com.oracle.truffle.js.nodes.access.CreateObjectNode;
import com.oracle.truffle.js.nodes.access.DeclareEvalVariableNode;
import com.oracle.truffle.js.nodes.access.DeclareGlobalNode;
import com.oracle.truffle.js.nodes.access.GetIteratorUnaryNode;
import com.oracle.truffle.js.nodes.access.GlobalPropertyNode;
import com.oracle.truffle.js.nodes.access.JSConstantNode;
import com.oracle.truffle.js.nodes.access.JSReadFrameSlotNode;
import com.oracle.truffle.js.nodes.access.JSWriteFrameSlotNode;
import com.oracle.truffle.js.nodes.access.ObjectLiteralNode;
import com.oracle.truffle.js.nodes.access.ObjectLiteralNode.ObjectLiteralMemberNode;
import com.oracle.truffle.js.nodes.access.OptionalChainNode;
import com.oracle.truffle.js.nodes.access.ReadElementNode;
import com.oracle.truffle.js.nodes.access.ScopeFrameNode;
import com.oracle.truffle.js.nodes.access.SuperPropertyReferenceNode;
import com.oracle.truffle.js.nodes.access.VarWrapperNode;
import com.oracle.truffle.js.nodes.access.WriteElementNode;
import com.oracle.truffle.js.nodes.access.WriteNode;
import com.oracle.truffle.js.nodes.access.WritePropertyNode;
import com.oracle.truffle.js.nodes.binary.DualNode;
import com.oracle.truffle.js.nodes.binary.JSBinaryNode;
import com.oracle.truffle.js.nodes.binary.JSTypeofIdenticalNode;
import com.oracle.truffle.js.nodes.control.AbstractBlockNode;
import com.oracle.truffle.js.nodes.control.BreakNode;
import com.oracle.truffle.js.nodes.control.BreakTarget;
import com.oracle.truffle.js.nodes.control.ContinueTarget;
import com.oracle.truffle.js.nodes.control.DiscardResultNode;
import com.oracle.truffle.js.nodes.control.EmptyNode;
import com.oracle.truffle.js.nodes.control.GeneratorNode;
import com.oracle.truffle.js.nodes.control.GeneratorWrapperNode;
import com.oracle.truffle.js.nodes.control.ModuleYieldNode;
import com.oracle.truffle.js.nodes.control.ResumableNode;
import com.oracle.truffle.js.nodes.control.ReturnNode;
import com.oracle.truffle.js.nodes.control.ReturnTargetNode;
import com.oracle.truffle.js.nodes.control.SequenceNode;
import com.oracle.truffle.js.nodes.control.StatementNode;
import com.oracle.truffle.js.nodes.control.SuspendNode;
import com.oracle.truffle.js.nodes.function.AbstractFunctionArgumentsNode;
import com.oracle.truffle.js.nodes.function.BlockScopeNode;
import com.oracle.truffle.js.nodes.function.EvalNode;
import com.oracle.truffle.js.nodes.function.FunctionBodyNode;
import com.oracle.truffle.js.nodes.function.FunctionRootNode;
import com.oracle.truffle.js.nodes.function.JSFunctionCallNode;
import com.oracle.truffle.js.nodes.function.JSFunctionExpressionNode;
import com.oracle.truffle.js.nodes.function.JSNewNode;
import com.oracle.truffle.js.nodes.function.SpreadArgumentNode;
import com.oracle.truffle.js.nodes.unary.JSUnaryNode;
import com.oracle.truffle.js.nodes.unary.TypeOfNode;
import com.oracle.truffle.js.nodes.unary.VoidNode;
import com.oracle.truffle.js.parser.env.BlockEnvironment;
import com.oracle.truffle.js.parser.env.DebugEnvironment;
import com.oracle.truffle.js.parser.env.Environment;
import com.oracle.truffle.js.parser.env.Environment.AbstractFrameVarRef;
import com.oracle.truffle.js.parser.env.Environment.FrameSlotVarRef;
import com.oracle.truffle.js.parser.env.Environment.VarRef;
import com.oracle.truffle.js.parser.env.EvalEnvironment;
import com.oracle.truffle.js.parser.env.FunctionEnvironment;
import com.oracle.truffle.js.parser.env.FunctionEnvironment.JumpTargetCloseable;
import com.oracle.truffle.js.parser.env.GlobalEnvironment;
import com.oracle.truffle.js.parser.env.PrivateEnvironment;
import com.oracle.truffle.js.parser.env.WithEnvironment;
import com.oracle.truffle.js.parser.internal.ir.debug.PrintVisitor;
import com.oracle.truffle.js.runtime.BigInt;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSErrorType;
import com.oracle.truffle.js.runtime.JSFrameUtil;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.Strings;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.objects.ScriptOrModule;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.InternalSlotId;
import com.oracle.truffle.js.runtime.util.Pair;

@SuppressWarnings("try")
abstract class GraalJSTranslator extends com.oracle.js.parser.ir.visitor.TranslatorNodeVisitor {

    public static final TruffleString SUPER_CALLED_TWICE = Strings.constant("super() called twice");
    public static final TruffleString UNSUPPORTED_REFERENCE_TO_SUPER = Strings.constant("Unsupported reference to 'super'");
    public static final String LINE__ = "__LINE__";
    public static final String FILE__ = "__FILE__";
    public static final String DIR__ = "__DIR__";
    public static final String IMPORT = "import";
    public static final String IMPORT_META = "import.meta";
    public static final String ARGUMENTS = "arguments";

    public static final JavaScriptNode[] EMPTY_NODE_ARRAY = new JavaScriptNode[0];
    private static final JavaScriptNode ANY_JAVA_SCRIPT_NODE = new JavaScriptNode() {
        @Override
        public Object execute(VirtualFrame frame) {
            CompilerDirectives.transferToInterpreter();
            throw new UnsupportedOperationException();
        }
    };

    private static final SourceSection unavailableInternalSection = Source.newBuilder(JavaScriptLanguage.ID, "", "").mimeType(
                    JavaScriptLanguage.APPLICATION_MIME_TYPE).internal(true).build().createUnavailableSection();

    private Environment environment;
    protected final JSContext context;
    protected final NodeFactory factory;
    protected final Source source;
    protected final List argumentNames;
    protected final int sourceLength;
    protected final int prologLength;
    protected final ScriptOrModule activeScriptOrModule;
    private final boolean isParentStrict;

    protected GraalJSTranslator(LexicalContext lc, NodeFactory factory, JSContext context, Source source, List argumentNames, int prologLength, Environment environment,
                    boolean isParentStrict, ScriptOrModule scriptOrModule) {
        super(lc);
        this.context = context;
        this.environment = environment;
        this.factory = factory;
        this.source = source;
        this.argumentNames = argumentNames;
        this.isParentStrict = isParentStrict;
        this.sourceLength = source.getCharacters().length();
        this.prologLength = prologLength;
        this.activeScriptOrModule = scriptOrModule;
    }

    protected final JavaScriptNode transform(com.oracle.js.parser.ir.Node node) {
        if (node != null) {
            return node.accept(this);
        }
        return null;
    }

    private JavaScriptNode tagStatement(JavaScriptNode resultNode, com.oracle.js.parser.ir.Node parseNode) {
        if (!resultNode.hasSourceSection()) {
            assignSourceSection(resultNode, parseNode);
        }
        assert resultNode.getSourceSection() != null;
        if (resultNode instanceof VarWrapperNode) {
            tagStatement(((VarWrapperNode) resultNode).getDelegateNode(), parseNode);
        } else {
            resultNode.addStatementTag();
        }
        return resultNode;
    }

    private JavaScriptNode tagExpression(JavaScriptNode resultNode, com.oracle.js.parser.ir.Node parseNode) {
        if (!resultNode.hasSourceSection()) {
            assignSourceSection(resultNode, parseNode);
        }
        assert resultNode.getSourceSection() != null;
        if (resultNode instanceof VarWrapperNode) {
            tagExpression(((VarWrapperNode) resultNode).getDelegateNode(), parseNode);
        } else {
            resultNode.addExpressionTag();
        }
        return resultNode;
    }

    private static JavaScriptNode tagCall(JavaScriptNode resultNode) {
        resultNode.addCallTag();
        return resultNode;
    }

    private JavaScriptNode tagBody(JavaScriptNode resultNode, com.oracle.js.parser.ir.Node parseNode) {
        if (!resultNode.hasSourceSection()) {
            assignSourceSection(resultNode, parseNode);
        }
        assert resultNode.getSourceSection() != null;
        if (resultNode instanceof VarWrapperNode) {
            tagBody(((VarWrapperNode) resultNode).getDelegateNode(), parseNode);
        } else {
            if (resultNode instanceof BlockScopeNode) {
                JavaScriptNode blockNode = ((BlockScopeNode) resultNode).getBlock();
                blockNode.addRootBodyTag();
                ensureHasSourceSection(blockNode, parseNode);
            } else {
                resultNode.addRootBodyTag();
            }
        }
        return resultNode;
    }

    private FunctionEnvironment currentFunction() {
        return environment.function();
    }

    private JavaScriptNode createBlock(JavaScriptNode... statements) {
        return createBlock(statements, false, false);
    }

    private JavaScriptNode createBlock(JavaScriptNode[] statements, boolean terminal, boolean expressionBlock) {
        if ((JSConfig.ReturnOptimizer && terminal) || expressionBlock || currentFunction().returnsLastStatementResult()) {
            return factory.createExprBlock(statements);
        } else {
            return factory.createVoidBlock(statements);
        }
    }

    protected final JavaScriptNode transformFunction(FunctionNode functionNode) {
        return transform(functionNode);
    }

    protected abstract GraalJSTranslator newTranslator(Environment env, LexicalContext savedLC);

    // ---

    @Override
    public JavaScriptNode enterFunctionNode(FunctionNode functionNode) {
        if (JSConfig.PrintParse) {
            printParse(functionNode);
        }

        boolean isStrict = functionNode.isStrict() || isParentStrict || (environment != null && environment.function() != null && environment.isStrictMode());
        boolean isArrowFunction = functionNode.isArrow();
        boolean isGeneratorFunction = functionNode.isGenerator();
        boolean isAsyncFunction = functionNode.isAsync();
        boolean isDerivedConstructor = functionNode.isDerivedConstructor();

        boolean isMethod = functionNode.isMethod();
        boolean needsNewTarget = functionNode.needsNewTarget();
        boolean isClassConstructor = functionNode.isClassConstructor();
        boolean isConstructor = !isArrowFunction && !isGeneratorFunction && !isAsyncFunction && ((!isMethod || context.getEcmaScriptVersion() == 5) || isClassConstructor);
        assert !isDerivedConstructor || isConstructor;
        boolean strictFunctionProperties = isStrict || isArrowFunction || isMethod || isGeneratorFunction;
        boolean isBuiltin = false;
        boolean hasSyntheticArguments = functionNode.isScript() && this.argumentNames != null;

        boolean isGlobal;
        boolean isEval = false;
        boolean isIndirectEval = false;
        boolean inDirectEval = false;
        if (environment instanceof EvalEnvironment) {
            isEval = true;
            boolean isDirectEval = ((EvalEnvironment) environment).isDirectEval();
            isIndirectEval = !isDirectEval;
            Environment evalParent = environment.getParent();
            isGlobal = evalParent == null || (isDirectEval && (!isStrict && evalParent.function().isGlobal()));
            inDirectEval = isDirectEval || (evalParent != null && evalParent.function().inDirectEval());
        } else if (environment instanceof DebugEnvironment) {
            isGlobal = environment.getParent() == null;
            isEval = true;
            inDirectEval = true;
        } else {
            isGlobal = environment == null && argumentNames == null;
            inDirectEval = environment != null && currentFunction().inDirectEval();
        }
        boolean functionMode = !isGlobal || (isStrict && isIndirectEval);

        boolean lazyTranslation = context.getLanguageOptions().lazyTranslation() && functionMode && !functionNode.isProgram() && !inDirectEval;

        TruffleString functionName = getFunctionName(functionNode);
        JSFunctionData functionData;
        FunctionRootNode functionRoot;
        JSFrameSlot blockScopeSlot;
        if (lazyTranslation) {
            assert functionMode && !functionNode.isProgram() && !functionNode.isModule();

            // function needs parent frame analysis has already been done
            boolean needsParentFrame = functionNode.usesAncestorScope();
            blockScopeSlot = needsParentFrame && environment != null ? environment.getCurrentBlockScopeSlot() : null;

            functionData = factory.createFunctionData(context, functionNode.getLength(), functionName, isConstructor, isDerivedConstructor, isStrict, isBuiltin,
                            needsParentFrame, isGeneratorFunction, isAsyncFunction, isClassConstructor, strictFunctionProperties, needsNewTarget);

            LexicalContext savedLC = lc.copy();
            Environment parentEnv = environment;
            functionData.setLazyInit(new JSFunctionData.Initializer() {
                @Override
                public void initializeRoot(JSFunctionData fd) {
                    synchronized (this) {
                        if (fd.getRootNode() == null) {
                            GraalJSTranslator translator = newTranslator(parentEnv, savedLC);
                            translator.translateFunctionOnDemand(functionNode, fd, isStrict, isGlobal, needsParentFrame, functionName, hasSyntheticArguments);
                            fd.releaseLazyInit();
                        }
                    }
                }
            });
            functionRoot = null;
        } else {
            Environment prevEnv = environment;
            try (EnvironmentCloseable functionEnv = enterFunctionEnvironment(functionNode, isStrict, isGlobal, hasSyntheticArguments)) {
                FunctionEnvironment currentFunction = currentFunction();
                currentFunction.setFunctionName(functionName);
                currentFunction.setInternalFunctionName(functionNode.getInternalNameTS());
                currentFunction.setNamedFunctionExpression(functionNode.isNamedFunctionExpression());

                declareParameters(functionNode);
                List declarations;
                if (functionMode) {
                    declarations = functionEnvInit(functionNode);
                } else if (functionNode.isModule()) {
                    assert currentFunction.isGlobal();
                    declarations = Collections.emptyList();
                } else {
                    assert currentFunction.isGlobal();
                    declarations = collectGlobalVars(functionNode, isEval);
                }

                if (functionNode.isProgram()) {
                    functionNeedsParentFramePass(functionNode, context);
                }

                boolean needsParentFrame = functionNode.usesAncestorScope();
                currentFunction.setNeedsParentFrame(needsParentFrame);

                JavaScriptNode body = translateFunctionBody(functionNode, declarations);

                needsParentFrame = currentFunction.needsParentFrame();
                blockScopeSlot = needsParentFrame && prevEnv != null ? prevEnv.getCurrentBlockScopeSlot() : null;

                functionData = factory.createFunctionData(context, functionNode.getLength(), functionName, isConstructor, isDerivedConstructor, isStrict, isBuiltin,
                                needsParentFrame, isGeneratorFunction, isAsyncFunction, isClassConstructor, strictFunctionProperties, needsNewTarget);

                if (functionNode.isModule()) {
                    functionRoot = createModuleRoot(functionNode, functionData, currentFunction, body);
                } else {
                    functionRoot = createFunctionRoot(functionNode, functionData, currentFunction, body);
                }

                // Freeze after root creation to allow registration of async/generator variables.
                currentFunction.freeze();

                if (isEval) {
                    // force eager call target init for Function() code to avoid deopt at call site
                    functionData.getCallTarget();
                }
            }
        }

        JavaScriptNode functionExpression;
        if (isArrowFunction && functionNode.needsThis() && !currentFunction().getNonArrowParentFunction().isDerivedConstructor()) {
            JavaScriptNode thisNode = createThisNode();
            functionExpression = factory.createFunctionExpressionLexicalThis(functionData, functionRoot, blockScopeSlot, thisNode);
        } else {
            functionExpression = factory.createFunctionExpression(functionData, functionRoot, blockScopeSlot);
        }

        if (functionNode.isDeclared()) {
            ensureHasSourceSection(functionExpression, functionNode);
        } else {
            functionExpression = tagExpression(functionExpression, functionNode);
        }
        return functionExpression;
    }

    JavaScriptNode translateFunctionBody(FunctionNode functionNode, List declarations) {
        JavaScriptNode body = transform(functionNode.getBody());

        if (functionNode.isAsync() && !functionNode.isGenerator()) {
            ensureHasSourceSection(body, functionNode);
            body = handleAsyncFunctionBody(body);
            ensureHasSourceSection(body, functionNode);
        }

        if (!declarations.isEmpty()) {
            body = prepareDeclarations(declarations, body);
        }

        JSFrameDescriptor fd = currentFunction().getFunctionFrameDescriptor();
        List slotsWithTDZ = new ArrayList<>(fd.getSize());
        for (JSFrameSlot slot : fd.getSlots()) {
            if (JSFrameUtil.hasTemporalDeadZone(slot)) {
                slotsWithTDZ.add(slot);
            }
        }
        if (!slotsWithTDZ.isEmpty()) {
            int[] slots = new int[slotsWithTDZ.size()];
            for (int i = 0; i < slotsWithTDZ.size(); i++) {
                slots[i] = slotsWithTDZ.get(i).getIndex();
            }
            body = factory.createExprBlock(factory.createClearFrameSlots(factory.createScopeFrame(0, 0, null), slots, 0, slots.length), body);
        }

        return body;
    }

    private FunctionRootNode translateFunctionOnDemand(FunctionNode functionNode, JSFunctionData functionData, boolean isStrict,
                    boolean isGlobal, boolean needsParentFrame, TruffleString functionName, boolean hasSyntheticArguments) {
        try (EnvironmentCloseable functionEnv = enterFunctionEnvironment(functionNode, isStrict, isGlobal, hasSyntheticArguments)) {
            FunctionEnvironment currentFunction = currentFunction();
            currentFunction.setFunctionName(functionName);
            currentFunction.setInternalFunctionName(functionNode.getInternalNameTS());
            currentFunction.setNamedFunctionExpression(functionNode.isNamedFunctionExpression());

            currentFunction.setNeedsParentFrame(needsParentFrame);

            declareParameters(functionNode);
            functionEnvInit(functionNode);

            JavaScriptNode body = translateFunctionBody(functionNode, Collections.emptyList());

            currentFunction.freeze();
            assert currentFunction.isDeepFrozen();

            return createFunctionRoot(functionNode, functionData, currentFunction, body);
        }
    }

    private FunctionRootNode createFunctionRoot(FunctionNode functionNode, JSFunctionData functionData, FunctionEnvironment currentFunction, JavaScriptNode body) {
        SourceSection functionSourceSection = createSourceSection(functionNode);
        FunctionBodyNode functionBody = factory.createFunctionBody(body);
        FunctionRootNode functionRoot = factory.createFunctionRootNode(functionBody, environment.getFunctionFrameDescriptor().toFrameDescriptor(), functionData, functionSourceSection,
                        activeScriptOrModule, currentFunction.getInternalFunctionName());

        if (JSConfig.PrintAst) {
            printAST(functionRoot);
        }
        return functionRoot;
    }

    /**
     * Creates one or two module root nodes out of a module body. Every module has exactly one yield
     * statement separating the linking and evaluation parts, so we try to split it up into two root
     * nodes, eliminating the yield and the need to suspend and resume (except for top-level await).
     *
     * @see #splitModuleBodyAtYield
     */
    private FunctionRootNode createModuleRoot(FunctionNode functionNode, JSFunctionData functionData, FunctionEnvironment currentFunction, JavaScriptNode body) {
        if (JSConfig.PrintAst) {
            printAST(body);
        }

        SourceSection moduleSourceSection = createSourceSection(functionNode);
        TruffleString internalFunctionName = currentFunction.getInternalFunctionName();
        JavaScriptNode[] statements = null;
        if (body instanceof SequenceNode) {
            statements = ((SequenceNode) body).getStatements();
        } else if (isModuleYieldStatement(body)) {
            statements = new JavaScriptNode[]{body};
        }
        if (JSConfig.SplitModuleRoot && statements != null) {
            for (int i = 0; i < statements.length; i++) {
                JavaScriptNode statement = statements[i];
                if (isModuleYieldStatement(statement)) {
                    // Split the module into two call targets:
                    // 1. InitializeEnvironment()
                    // 2. ExecuteModule() / ExecuteAsyncModule()
                    JavaScriptNode[] linkHalf = Arrays.copyOfRange(statements, 0, i);
                    JavaScriptNode[] evalHalf = Arrays.copyOfRange(statements, i + 1, statements.length);
                    JavaScriptNode linkBlock = tagBody(factory.createModuleInitializeEnvironment(factory.createVoidBlock(linkHalf)), functionNode);
                    JavaScriptNode evalBlock = handleModuleBody(factory.createExprBlock(evalHalf));
                    FunctionBodyNode linkBody = factory.createFunctionBody(linkBlock);
                    FunctionBodyNode evalBody = factory.createFunctionBody(evalBlock);
                    return factory.createModuleRootNode(linkBody, evalBody, environment.getFunctionFrameDescriptor().toFrameDescriptor(), functionData,
                                    moduleSourceSection, activeScriptOrModule, internalFunctionName);
                }
            }
        }

        // Fall back to single call target using generator yield logic.
        currentFunction.addYield(); // yield has not been added yet.
        FunctionBodyNode generatorBody = factory.createFunctionBody(handleModuleBody(body));
        return factory.createModuleRootNode(generatorBody, generatorBody, environment.getFunctionFrameDescriptor().toFrameDescriptor(), functionData,
                        moduleSourceSection, activeScriptOrModule, internalFunctionName);
    }

    private static void printAST(Node functionRoot) {
        NodeUtil.printCompactTree(System.out, functionRoot);
    }

    private static void printParse(FunctionNode functionNode) {
        System.out.printf(new PrintVisitor(functionNode).toString());
    }

    private JavaScriptNode finishDerivedConstructorBody(FunctionNode function, JavaScriptNode body) {
        JavaScriptNode getThisBinding = (function.hasDirectSuper() || function.hasEval() || function.hasArrowEval()) ? environment.findThisVar().createReadNode() : factory.createConstantUndefined();
        return factory.createDerivedConstructorResult(body, getThisBinding);
    }

    /**
     * Async function parse-time AST modifications.
     *
     * @return instrumented function body
     */
    private JavaScriptNode handleAsyncFunctionBody(JavaScriptNode body) {
        assert currentFunction().isAsyncFunction() && !currentFunction().isGeneratorFunction();
        VarRef asyncContextVar = environment.findAsyncContextVar();
        VarRef asyncResultVar = environment.findAsyncResultVar();
        JSWriteFrameSlotNode writeResultNode = (JSWriteFrameSlotNode) asyncResultVar.createWriteNode(null);
        JSWriteFrameSlotNode writeContextNode = (JSWriteFrameSlotNode) asyncContextVar.createWriteNode(null);
        JSReadFrameSlotNode readContextNode = (JSReadFrameSlotNode) asyncContextVar.createReadNode();
        JavaScriptNode instrumentedBody = instrumentSuspendNodes(body);
        return factory.createAsyncFunctionBody(context, instrumentedBody, writeContextNode, readContextNode, writeResultNode,
                        createSourceSection(lc.getCurrentFunction()), currentFunction().getExplicitOrInternalFunctionName(), activeScriptOrModule);
    }

    /**
     * Generator function parse-time AST modifications.
     *
     * @return instrumented function body
     */
    private JavaScriptNode finishGeneratorBody(JavaScriptNode body) {
        // Note: parameter initialization must precede (i.e. wrap) the (async) generator body
        assert lc.getCurrentBlock().isFunctionBody();
        assert !currentFunction().isModule();
        if (currentFunction().isAsyncGeneratorFunction()) {
            return handleAsyncGeneratorBody(body);
        } else {
            return handleGeneratorBody(body);
        }
    }

    private JavaScriptNode handleGeneratorBody(JavaScriptNode body) {
        assert currentFunction().isGeneratorFunction() && !currentFunction().isAsyncGeneratorFunction() && !currentFunction().isModule();
        JavaScriptNode instrumentedBody = instrumentSuspendNodes(body);
        VarRef yieldVar = environment.findYieldValueVar();
        JSWriteFrameSlotNode writeYieldValueNode = (JSWriteFrameSlotNode) yieldVar.createWriteNode(null);
        JSReadFrameSlotNode readYieldResultNode = JSConfig.YieldResultInFrame ? (JSReadFrameSlotNode) environment.findTempVar(currentFunction().getYieldResultSlot()).createReadNode() : null;
        return factory.createGeneratorBody(context, instrumentedBody, writeYieldValueNode, readYieldResultNode,
                        createSourceSection(lc.getCurrentFunction()), currentFunction().getExplicitOrInternalFunctionName(), activeScriptOrModule);
    }

    private JavaScriptNode handleAsyncGeneratorBody(JavaScriptNode body) {
        assert currentFunction().isAsyncGeneratorFunction() && !currentFunction().isModule();
        VarRef asyncContextVar = environment.findAsyncContextVar();
        JavaScriptNode instrumentedBody = instrumentSuspendNodes(body);
        VarRef yieldVar = environment.findAsyncResultVar();
        JSWriteFrameSlotNode writeAsyncContextNode = (JSWriteFrameSlotNode) asyncContextVar.createWriteNode(null);
        JSReadFrameSlotNode readAsyncContextNode = (JSReadFrameSlotNode) asyncContextVar.createReadNode();
        JSWriteFrameSlotNode writeYieldValueNode = (JSWriteFrameSlotNode) yieldVar.createWriteNode(null);
        JSReadFrameSlotNode readYieldResultNode = JSConfig.YieldResultInFrame ? (JSReadFrameSlotNode) environment.findTempVar(currentFunction().getYieldResultSlot()).createReadNode() : null;
        return factory.createAsyncGeneratorBody(context, instrumentedBody, writeYieldValueNode, readYieldResultNode, writeAsyncContextNode, readAsyncContextNode,
                        createSourceSection(lc.getCurrentFunction()), currentFunction().getExplicitOrInternalFunctionName(), activeScriptOrModule);
    }

    private JavaScriptNode handleModuleBody(JavaScriptNode body) {
        assert currentFunction().isModule();
        if (currentFunction().isAsyncGeneratorFunction()) {
            VarRef asyncContextVar = environment.findAsyncContextVar();
            JavaScriptNode instrumentedBody = instrumentSuspendNodes(body);
            VarRef yieldVar = environment.findAsyncResultVar();
            JSWriteFrameSlotNode writeAsyncContextNode = (JSWriteFrameSlotNode) asyncContextVar.createWriteNode(null);
            JSWriteFrameSlotNode writeYieldValueNode = (JSWriteFrameSlotNode) yieldVar.createWriteNode(null);
            return factory.createTopLevelAsyncModuleBody(context, instrumentedBody, writeYieldValueNode, writeAsyncContextNode,
                            createSourceSection(lc.getCurrentFunction()), activeScriptOrModule);
        } else {
            JavaScriptNode instrumentedBody = instrumentSuspendNodes(body);
            return factory.createModuleBody(instrumentedBody);
        }
    }

    /**
     * Instrument code paths leading to yield and await expressions.
     */
    private JavaScriptNode instrumentSuspendNodes(JavaScriptNode body) {
        if (!currentFunction().hasYield() && !currentFunction().hasAwait()) {
            return body;
        }
        JavaScriptNode newBody = (JavaScriptNode) instrumentSuspendHelper(body, null);
        if (newBody == null) {
            // No suspendable children found. They could have been eliminated
            // as a dead code during translation (for example, 'while (false) yield').
            return body;
        } else {
            return newBody;
        }
    }

    private Node instrumentSuspendHelper(Node parent, Node grandparent) {
        boolean hasSuspendChild = false;
        BitSet suspendableIndices = null;
        if (parent instanceof AbstractBlockNode) {
            AbstractBlockNode blockNode = ((AbstractBlockNode) parent);
            Node[] statements = blockNode.getStatements();
            for (int i = 0; i < statements.length; i++) {
                Node oldChild = statements[i];
                Node newChild = instrumentSuspendHelper(oldChild, parent);
                if (newChild != null) {
                    hasSuspendChild = true;
                    if (newChild != oldChild) {
                        factory.fixBlockNodeChild(blockNode, i, (JavaScriptNode) newChild);
                    }
                    if (suspendableIndices == null) {
                        suspendableIndices = new BitSet();
                    }
                    suspendableIndices.set(i);
                }
            }
        } else {
            for (Node child : getChildrenInExecutionOrder(parent)) {
                Node newChild = instrumentSuspendHelper(child, parent);
                if (newChild != null) {
                    hasSuspendChild = true;
                    if (child != newChild) {
                        factory.fixNodeChild(parent, child, newChild);
                    }
                    assert !(child instanceof ResumableNode) || newChild instanceof GeneratorNode || newChild instanceof SuspendNode : "resumable node not wrapped: " + child;
                }
            }
        }
        if (parent instanceof SuspendNode) {
            return parent;
        } else if (!hasSuspendChild) {
            return null;
        }

        if (parent instanceof AbstractBlockNode) {
            assert suspendableIndices != null && !suspendableIndices.isEmpty();
            return toGeneratorBlockNode((AbstractBlockNode) parent, suspendableIndices);
        } else if (parent instanceof ResumableNode) {
            return wrapResumableNode(parent);
        } else if (parent instanceof ReturnNode || parent instanceof ReturnTargetNode || isSideEffectFreeUnaryOpNode(parent)) {
            // these are side-effect-free, skip
            return parent;
        } else if (isSupportedDispersibleExpression(parent)) {
            // need to rescue side-effecting/non-repeatable expressions into temporaries
            // note that the expressions have to be extracted in evaluation order
            List extracted = new ArrayList<>();
            // we can only replace child fields assignable from JavaScriptNode
            if (grandparent == null || NodeUtil.isReplacementSafe(grandparent, parent, ANY_JAVA_SCRIPT_NODE)) {
                // extraction is a destructive step; only attempt it if replace can succeed
                extractChildrenTo(parent, extracted);
            } else {
                // not assignable to field type (e.g. JSTargetableNode), ignore for now
            }
            if (!extracted.isEmpty()) { // only if there's actually something to rescue
                extracted.add((JavaScriptNode) parent);
                // insert block node wrapper
                JavaScriptNode exprBlock = wrapResumableNode(factory.createExprBlock(extracted.toArray(EMPTY_NODE_ARRAY)));
                tagHiddenExpression(exprBlock);
                return exprBlock;
            } else {
                // nothing to do
                return parent;
            }
        } else {
            // if (parent instanceof JavaScriptNode):
            // unknown expression node type, either safe or unexpected (not handled)
            // else:
            // unsupported node type, skip over
            return parent;
        }
    }

    private JavaScriptNode wrapResumableNode(Node resumableNode) {
        if (resumableNode instanceof AbstractBlockNode) {
            BitSet all = new BitSet();
            all.set(0, ((AbstractBlockNode) resumableNode).getStatements().length);
            return toGeneratorBlockNode((AbstractBlockNode) resumableNode, all);
        }
        assert !(resumableNode instanceof SuspendNode) : resumableNode;
        JSFrameSlot stateSlot = addGeneratorStateSlot(environment.getFunctionFrameDescriptor(), ((ResumableNode) resumableNode).getStateSlotKind());
        return factory.createGeneratorWrapper((JavaScriptNode) resumableNode, stateSlot);
    }

    private JavaScriptNode toGeneratorBlockNode(AbstractBlockNode blockNode, BitSet suspendableIndices) {
        JSFrameDescriptor functionFrameDesc = environment.getFunctionFrameDescriptor();
        JavaScriptNode[] statements = blockNode.getStatements();
        boolean returnsResult = !blockNode.isResultAlwaysOfType(Undefined.class);
        JavaScriptNode genBlock;
        // we can resume at index 0 (start state) and every statement that contains a yield
        int resumePoints = suspendableIndices.cardinality() + (suspendableIndices.get(0) ? 0 : 1);
        if (resumePoints == statements.length) {
            // all statements are resume points
            JSFrameSlot stateSlot = addGeneratorStateSlot(functionFrameDesc, FrameSlotKind.Int);
            if (returnsResult) {
                genBlock = factory.createGeneratorExprBlock(statements, stateSlot);
            } else {
                genBlock = factory.createGeneratorVoidBlock(statements, stateSlot);
            }
        } else {
            // split block into resumable chunks of at least 1 statement.
            JavaScriptNode[] chunks = new JavaScriptNode[resumePoints];
            int fromIndex = 0;
            int toIndex;
            for (int chunkI = 0; chunkI < resumePoints; chunkI++) {
                toIndex = suspendableIndices.nextSetBit(fromIndex + 1);
                if (toIndex < 0) {
                    assert chunkI == resumePoints - 1;
                    toIndex = statements.length;
                }
                returnsResult = chunkI == resumePoints - 1 && !blockNode.isResultAlwaysOfType(Undefined.class);
                JavaScriptNode chunk;
                if (fromIndex + 1 == toIndex) {
                    chunk = statements[fromIndex];
                } else {
                    JavaScriptNode[] chunkStatements = Arrays.copyOfRange(statements, fromIndex, toIndex);
                    chunk = (returnsResult && chunkI == resumePoints - 1) ? factory.createExprBlock(chunkStatements) : factory.createVoidBlock(chunkStatements);
                }
                chunks[chunkI] = chunk;
                fromIndex = toIndex;
            }
            JSFrameSlot stateSlot = addGeneratorStateSlot(functionFrameDesc, FrameSlotKind.Int);
            if (returnsResult) {
                genBlock = factory.createGeneratorExprBlock(chunks, stateSlot);
            } else {
                genBlock = factory.createGeneratorVoidBlock(chunks, stateSlot);
            }
        }
        JavaScriptNode.transferSourceSectionAndTags(blockNode, genBlock);
        return genBlock;
    }

    private static boolean isSideEffectFreeUnaryOpNode(Node node) {
        // (conservative) non-exhaustive list
        return node instanceof DiscardResultNode || node instanceof VoidNode || node instanceof TypeOfNode || node instanceof JSTypeofIdenticalNode;
    }

    private static boolean isSupportedDispersibleExpression(Node node) {
        return node instanceof JSBinaryNode || node instanceof JSUnaryNode ||
                        node instanceof ArrayLiteralNode || node instanceof ObjectLiteralNode ||
                        node instanceof com.oracle.truffle.js.nodes.access.PropertyNode || node instanceof GlobalPropertyNode || node instanceof ReadElementNode ||
                        node instanceof WritePropertyNode || node instanceof WriteElementNode ||
                        node instanceof JSFunctionCallNode || node instanceof JSNewNode;
    }

    private static boolean isStatelessExpression(Node child) {
        return child instanceof JSConstantNode || child instanceof CreateObjectNode || (child instanceof RepeatableNode && !(child instanceof ReadNode));
    }

    private static boolean skipOverToChildren(Node node) {
        return node instanceof ObjectLiteralMemberNode || node instanceof AbstractFunctionArgumentsNode || node instanceof ArrayLiteralNode.SpreadArrayNode || node instanceof SpreadArgumentNode;
    }

    private void extractChildTo(Node child, Node parent, List extracted) {
        if (isStatelessExpression(child)) {
            return;
        }
        if (skipOverToChildren(child)) {
            extractChildrenTo(child, extracted);
        } else if (child instanceof JavaScriptNode) {
            JavaScriptNode jschild = (JavaScriptNode) child;
            if (NodeUtil.isReplacementSafe(parent, child, ANY_JAVA_SCRIPT_NODE)) {
                JSFrameDescriptor functionFrameDescriptor = environment.getFunctionFrameDescriptor();
                InternalSlotId identifier = factory.createInternalSlotId("generatorexpr", functionFrameDescriptor.getSize());
                JSFrameSlot frameSlot = functionFrameDescriptor.addFrameSlot(identifier);
                JavaScriptNode readState = factory.createReadCurrentFrameSlot(frameSlot);
                if (jschild.hasTag(StandardTags.ExpressionTag.class) ||
                                (jschild instanceof GeneratorWrapperNode && ((GeneratorWrapperNode) jschild).getResumableNode().hasTag(StandardTags.ExpressionTag.class))) {
                    tagHiddenExpression(readState);
                }
                JavaScriptNode writeState = factory.createWriteCurrentFrameSlot(frameSlot, jschild);
                extracted.add(writeState);
                // replace child with saved expression result
                factory.fixNodeChild(parent, child, readState);
            } else {
                // not assignable to field type (e.g. JSTargetableNode), cannot extract
                // but try to extract grandchildren instead, e.g.:
                // (yield)[yield](yield) => a = yield, b = yield, c = yield, a[b](c)
                extractChildrenTo(child, extracted);
            }
        }
    }

    private static Iterable getChildrenInExecutionOrder(Node parent) {
        // Note: Child and Children fields must be declared in execution order.
        return parent.getChildren();
    }

    private void extractChildrenTo(Node parent, List extracted) {
        for (Node child : getChildrenInExecutionOrder(parent)) {
            extractChildTo(child, parent, extracted);
        }
    }

    private JavaScriptNode handleFunctionReturn(FunctionNode functionNode, JavaScriptNode body) {
        FunctionEnvironment currentFunction = currentFunction();
        assert (currentFunction.isGlobal() || currentFunction.isEval() || currentFunction.hasSyntheticArguments()) == (functionNode.isScript() || functionNode.isModule());
        if (currentFunction.hasReturn()) {
            if (JSConfig.ReturnValueInFrame) {
                return factory.createFrameReturnTarget(body, factory.createReadCurrentFrameSlot(currentFunction.getReturnSlot()));
            } else {
                return factory.createReturnTarget(body);
            }
        } else if (currentFunction.returnsLastStatementResult()) {
            if (currentFunction.hasReturnSlot()) {
                return wrapGetCompletionValue(body);
            } else {
                return discardResult(body);
            }
        }
        return body;
    }

    private EnvironmentCloseable enterFunctionEnvironment(FunctionNode function, boolean isStrict, boolean isGlobal, boolean hasSyntheticArguments) {
        Scope scope = function.getBody().getScope();
        Environment functionEnv;
        if (environment instanceof EvalEnvironment) {
            assert !function.isArrow() && !function.isGenerator() && !function.isDerivedConstructor() && !function.isAsync();
            functionEnv = new FunctionEnvironment(environment.getParent(), factory, context, scope, isStrict,
                            true, ((EvalEnvironment) environment).isDirectEval(), false, false, false, false, isGlobal, hasSyntheticArguments);
        } else if (environment instanceof DebugEnvironment) {
            assert !function.isArrow() && !function.isGenerator() && !function.isDerivedConstructor() && !function.isAsync();
            functionEnv = new FunctionEnvironment(environment, factory, context, scope, isStrict, true, true, false, false, false, false, isGlobal, hasSyntheticArguments);
        } else {
            functionEnv = new FunctionEnvironment(environment, factory, context, scope, isStrict, false, false,
                            function.isArrow(), function.isGenerator(), function.isDerivedConstructor(), function.isAsync(), isGlobal, hasSyntheticArguments);
        }
        return new EnvironmentCloseable(functionEnv);
    }

    private void declareParameters(FunctionNode functionNode) {
        FunctionEnvironment currentFunction = currentFunction();
        currentFunction.setSimpleParameterList(functionNode.hasSimpleParameterList());
        List parameters = functionNode.getParameters();
        if (parameters.size() > 0 && parameters.get(parameters.size() - 1).isRestParameter()) {
            currentFunction.setRestParameter(true);
        }
        if (functionNode.getNumOfParams() > context.getFunctionArgumentsLimit()) {
            throw Errors.createSyntaxError("function has too many arguments");
        }
    }

    private JavaScriptNode prepareDeclarations(List declarations, JavaScriptNode body) {
        declarations.add(body);
        return factory.createExprBlock(declarations.toArray(EMPTY_NODE_ARRAY));
    }

    // footprint: avoid creating identical 0-sized arrays
    private static JavaScriptNode[] javaScriptNodeArray(int size) {
        return size == 0 ? EMPTY_NODE_ARRAY : new JavaScriptNode[size];
    }

    private TruffleString getFunctionName(FunctionNode functionNode) {
        if (context.getEcmaScriptVersion() < 6 && (functionNode.isGetter() || functionNode.isSetter())) {
            // strip getter/setter name prefix in ES5 mode
            assert !functionNode.isAnonymous();
            String name = functionNode.getName();
            if (functionNode.isGetter() && name.startsWith(Strings.GET_SPC.toJavaStringUncached()) || (functionNode.isSetter() && name.startsWith(Strings.SET_SPC.toJavaStringUncached()))) {
                return Strings.substring(context, functionNode.getNameTS(), 4);
            }
        }
        return functionNode.getNameTS();
    }

    private void prepareParameters(List init) {
        FunctionNode function = lc.getCurrentFunction();
        FunctionEnvironment currentFunction = currentFunction();

        if (needsThisSlot(function, currentFunction) && !function.isDerivedConstructor()) {
            // Derived constructor: `this` binding is initialized after super() call.
            assert environment.findThisVar() != null;
            init.add(prepareThis(function));
        }
        if (function.needsSuper()) {
            assert function.isMethod();
            init.add(prepareSuper());
        }
        if (function.needsNewTarget()) {
            init.add(prepareNewTarget());
        }

        if (function.needsArguments() && !currentFunction.isDirectArgumentsAccess() && !currentFunction.isDirectEval()) {
            assert !function.isArrow() && !function.isClassFieldInitializer();
            init.add(prepareArguments());
        }

        int parameterCount = function.getParameters().size();
        if (parameterCount == 0) {
            return;
        }
        int i = 0;
        boolean hasRestParameter = currentFunction.hasRestParameter();
        boolean hasMappedArguments = function.needsArguments() && !function.isStrict() && function.hasSimpleParameterList();
        for (int argIndex = currentFunction.getLeadingArgumentCount(); i < parameterCount; i++, argIndex++) {
            final JavaScriptNode valueNode;
            if (hasRestParameter && i == parameterCount - 1) {
                valueNode = tagHiddenExpression(factory.createAccessRestArgument(context, argIndex));
            } else {
                valueNode = tagHiddenExpression(factory.createAccessArgument(argIndex));
            }
            IdentNode param = function.getParameters().get(i);
            if (param.isIgnoredParameter()) {
                // Duplicate parameter names are allowed in non-strict mode but have no binding.
                assert !currentFunction.isStrictMode();
                continue;
            }
            TruffleString paramName = param.getNameTS();
            VarRef paramRef = environment.findLocalVar(paramName);
            init.add(tagHiddenExpression(paramRef.createWriteNode(valueNode)));
            if (hasMappedArguments) {
                currentFunction.addMappedParameter(paramRef.getFrameSlot(), i);
            }
        }
    }

    private static JavaScriptNode tagHiddenExpression(JavaScriptNode node) {
        node.setSourceSection(unavailableInternalSection);
        if (node instanceof VarWrapperNode) {
            tagHiddenExpression(((VarWrapperNode) node).getDelegateNode());
        } else {
            node.addExpressionTag();
        }
        return node;
    }

    private List functionEnvInit(FunctionNode functionNode) {
        FunctionEnvironment currentFunction = currentFunction();
        assert !currentFunction.isGlobal() || currentFunction.isIndirectEval();

        if (JSConfig.ReturnOptimizer) {
            markTerminalReturnNodes(functionNode.getBody());
        }

        if (JSConfig.OptimizeApplyArguments && functionNode.needsArguments() && functionNode.hasApplyArgumentsCall() &&
                        !functionNode.isArrow() && !functionNode.hasEval() && !functionNode.hasArrowEval() && !currentFunction.isDirectEval() &&
                        functionNode.getNumOfParams() == 0 &&
                        checkDirectArgumentsAccess(functionNode)) {
            currentFunction.setDirectArgumentsAccess(true);
        }

        if (functionNode.needsDynamicScope() && !currentFunction.isDirectEval()) {
            currentFunction.setIsDynamicallyScoped(true);
        }
        if (functionNode.needsNewTarget()) {
            currentFunction.setNeedsNewTarget(true);
        }

        return Collections.emptyList();
    }

    private static void functionNeedsParentFramePass(FunctionNode rootFunctionNode, JSContext context) {
        if (!context.getLanguageOptions().lazyTranslation()) {
            return; // nothing to do
        }

        com.oracle.js.parser.ir.visitor.NodeVisitor visitor = new com.oracle.js.parser.ir.visitor.NodeVisitor<>(new LexicalContext()) {
            @Override
            public boolean enterIdentNode(IdentNode identNode) {
                if (!identNode.isPropertyName()) {
                    String varName = identNode.getName();
                    findSymbol(varName);
                }
                return true;
            }

            @Override
            public boolean enterAccessNode(AccessNode accessNode) {
                if (accessNode.isPrivate()) {
                    findSymbol(accessNode.getPrivateName());
                }
                return true;
            }

            private void findSymbol(String varName) {
                boolean local = true;
                FunctionNode lastFunction = null;
                for (Iterator iterator = lc.getAllNodes(); iterator.hasNext();) {
                    LexicalContextNode node = iterator.next();
                    if (node instanceof Block) {
                        Symbol foundSymbol = ((Block) node).getScope().getExistingSymbol(varName);
                        if (foundSymbol != null && !foundSymbol.isGlobal()) {
                            if (!local) {
                                markUsesAncestorScopeUntil(lastFunction, true);
                            }
                            break;
                        }
                    } else if (node instanceof ClassNode) {
                        if (((ClassNode) node).getScope().hasSymbol(varName) || ((ClassNode) node).getClassHeadScope().hasSymbol(varName)) {
                            if (!local) {
                                markUsesAncestorScopeUntil(lastFunction, true);
                            }
                            break;
                        }
                    } else if (node instanceof FunctionNode) {
                        FunctionNode function = (FunctionNode) node;
                        if (function.isNamedFunctionExpression() && varName.equals(function.getIdent().getName())) {
                            if (!local) {
                                markUsesAncestorScopeUntil(lastFunction, true);
                            }
                            break;
                        } else if (!function.isProgram() && varName.equals(ARGUMENTS)) {
                            // arguments must be local if we are not in an arrow function
                            assert local || (lastFunction != null && lastFunction.isArrow());
                            // Arrow functions inherit 'arguments', but may also declare it.
                            // Therefore, continue until a non-arrow function or found symbol.
                            // Note that (direct) eval may also declare arguments dynamically.
                            if (!function.isArrow()) {
                                if (!local) {
                                    markUsesAncestorScopeUntil(lastFunction, true);
                                }
                                break;
                            }
                        } else if (function.isArrow() && isVarLexicallyScopedInArrowFunction(varName)) {
                            assert !varName.equals(ARGUMENTS);
                            FunctionNode nonArrowFunction = lc.getCurrentNonArrowFunction();
                            // `this` is read from the arrow function object,
                            // unless `this` is supplied by a subclass constructor
                            if (!varName.equals("this") || nonArrowFunction.isDerivedConstructor()) {
                                if (!nonArrowFunction.isProgram()) {
                                    markUsesAncestorScopeUntil(nonArrowFunction, false);
                                }
                            }
                            break;
                        }
                        if (function.hasEval() && !function.isProgram()) {
                            if (!local) {
                                markUsesAncestorScopeUntil(lastFunction, true);
                            }
                        } else if (function.isModule() && isImport(varName)) {
                            // needed for GetActiveScriptOrModule()
                            if (!local) {
                                markUsesAncestorScopeUntil(lastFunction, true);
                            }
                        }
                        lastFunction = function;
                        local = false;
                    } else if (node instanceof WithNode) {
                        if (!local) {
                            markUsesAncestorScopeUntil(lastFunction, true);
                        }
                    }
                }
            }

            private boolean isVarLexicallyScopedInArrowFunction(String varName) {
                return ARGUMENTS.equals(varName) || "new.target".equals(varName) || "super".equals(varName) || "this".equals(varName);
            }

            private boolean isImport(String varName) {
                return IMPORT.equals(varName) || IMPORT_META.equals(varName);
            }

            private void markUsesAncestorScopeUntil(FunctionNode untilFunction, boolean inclusive) {
                for (final Iterator functions = lc.getFunctions(); functions.hasNext();) {
                    FunctionNode function = functions.next();
                    if (!inclusive && function == untilFunction) {
                        break;
                    }
                    if (!function.isProgram()) {
                        function.setUsesAncestorScope(true);
                    }
                    if (inclusive && function == untilFunction) {
                        break;
                    }
                }
            }

            @Override
            public boolean enterFunctionNode(FunctionNode functionNode) {
                if (functionNode.hasEval()) {
                    markUsesAncestorScopeUntil(null, false);
                }
                // TODO if function does not have nested functions we can skip it
                return true;
            }
        };

        rootFunctionNode.accept(visitor);
    }

    private static boolean checkDirectArgumentsAccess(FunctionNode functionNode) {
        assert functionNode.needsArguments() && functionNode.hasApplyArgumentsCall() && !functionNode.isArrow() && !functionNode.hasEval() && !functionNode.hasArrowEval();
        assert functionNode.getNumOfParams() == 0 || functionNode.isStrict() || !functionNode.hasSimpleParameterList() : "must not have mapped parameters";
        class DirectArgumentsAccessVisitor extends com.oracle.js.parser.ir.visitor.NodeVisitor {
            boolean directArgumentsAccess = true;

            DirectArgumentsAccessVisitor(LexicalContext lc) {
                super(lc);
            }

            @Override
            public boolean enterIdentNode(IdentNode identNode) {
                if (!identNode.isPropertyName() && !identNode.isApplyArguments() && identNode.getName().equals(ARGUMENTS)) {
                    // `arguments` is used outside of `function.apply(_, arguments)`; bail out.
                    directArgumentsAccess = false;
                }
                return false;
            }

            @Override
            public boolean enterFunctionNode(FunctionNode nestedFunctionNode) {
                if (nestedFunctionNode == functionNode) {
                    return true;
                }
                if (nestedFunctionNode.isArrow()) {
                    // 1. arrow functions have lexical `arguments` binding;
                    // direct arguments access to outer frames currently not supported
                    directArgumentsAccess = false;
                }
                // 2. if not in strict mode, nested functions might access mapped parameters;
                // not a problem: we already ensured that this function does not have any.

                // We do not look inside nested functions.
                return false;
            }
        }

        DirectArgumentsAccessVisitor visitor = new DirectArgumentsAccessVisitor(new LexicalContext());
        functionNode.accept(visitor);
        return visitor.directArgumentsAccess;
    }

    private static void markTerminalReturnNodes(com.oracle.js.parser.ir.Node node) {
        if (node instanceof Block && ((Block) node).isTerminal()) {
            Statement lastStatement = ((Block) node).getLastStatement();
            if (lastStatement != null) {
                markTerminalReturnNodes(lastStatement);
            }
        } else if (node instanceof BlockStatement && ((BlockStatement) node).isTerminal()) {
            markTerminalReturnNodes(((BlockStatement) node).getBlock());
        } else if (node instanceof IfNode && ((IfNode) node).isTerminal()) {
            markTerminalReturnNodes(((IfNode) node).getPass());
            markTerminalReturnNodes(((IfNode) node).getFail());
        } else if (node instanceof com.oracle.js.parser.ir.ReturnNode) {
            ((com.oracle.js.parser.ir.ReturnNode) node).setInTerminalPosition(true);
        }
    }

    private List collectGlobalVars(FunctionNode functionNode, boolean configurable) {
        int symbolCount = functionNode.getBody().getSymbolCount();
        if (symbolCount == 0) {
            return Collections.emptyList();
        }
        final List declarations = new ArrayList<>(symbolCount);
        for (Symbol symbol : functionNode.getBody().getSymbols()) {
            if (symbol.isGlobal() && symbol.isVar()) {
                if (symbol.isHoistableDeclaration()) {
                    declarations.add(factory.createDeclareGlobalFunction(symbol.getNameTS(), configurable));
                } else {
                    declarations.add(factory.createDeclareGlobalVariable(symbol.getNameTS(), configurable));
                }
            } else if (!configurable) {
                assert symbol.isBlockScoped();
                declarations.add(factory.createDeclareGlobalLexicalVariable(symbol.getNameTS(), symbol.isConst()));
            }
        }
        final List nodes = new ArrayList<>(2);
        nodes.add(factory.createGlobalDeclarationInstantiation(context, declarations));
        return nodes;
    }

    private JavaScriptNode prepareArguments() {
        VarRef argumentsVar = environment.findLocalVar(Strings.ARGUMENTS);
        boolean unmappedArgumentsObject = currentFunction().isStrictMode() || !currentFunction().hasSimpleParameterList();
        JavaScriptNode argumentsObject = factory.createArgumentsObjectNode(context, unmappedArgumentsObject, currentFunction().getLeadingArgumentCount());
        if (!unmappedArgumentsObject) {
            argumentsObject = environment.findArgumentsVar().createWriteNode(argumentsObject);
        }
        return argumentsVar.createWriteNode(argumentsObject);
    }

    private JavaScriptNode prepareThis(FunctionNode functionNode) {
        // In a derived class constructor, we cannot get the this value from the arguments.
        assert !currentFunction().getNonArrowParentFunction().isDerivedConstructor();
        VarRef thisVar = environment.findThisVar();
        boolean isLexicalThis = functionNode.isArrow();
        JavaScriptNode getThisNode = isLexicalThis ? factory.createAccessLexicalThis() : factory.createAccessThis();
        if (!environment.isStrictMode() && !isLexicalThis) {
            getThisNode = factory.createPrepareThisBinding(context, getThisNode);
        }
        if (functionNode.isClassConstructor()) {
            getThisNode = initializeInstanceElements(getThisNode);
        }
        return thisVar.createWriteNode(getThisNode);
    }

    private JavaScriptNode prepareSuper() {
        JavaScriptNode getHomeObject = factory.createAccessHomeObject(context);
        return environment.findSuperVar().createWriteNode(getHomeObject);
    }

    private JavaScriptNode prepareNewTarget() {
        JavaScriptNode getNewTarget = factory.createAccessNewTarget();
        return environment.findNewTargetVar().createWriteNode(getNewTarget);
    }

    @Override
    public JavaScriptNode enterReturnNode(com.oracle.js.parser.ir.ReturnNode returnNode) {
        JavaScriptNode expression;
        if (returnNode.getExpression() != null) {
            expression = transform(returnNode.getExpression());
            if (currentFunction().isAsyncGeneratorFunction()) {
                expression = createAwaitNode(expression);
            }
        } else {
            expression = factory.createConstantUndefined();
        }

        ReturnNode returnStatement = returnNode.isInTerminalPosition() ? factory.createTerminalPositionReturn(expression) : createReturnNode(expression);
        return tagStatement(returnStatement, returnNode);
    }

    private ReturnNode createReturnNode(JavaScriptNode expression) {
        FunctionEnvironment currentFunction = currentFunction();
        currentFunction.addReturn();
        if (JSConfig.ReturnValueInFrame) {
            JavaScriptNode writeReturnSlotNode = environment.findTempVar(currentFunction.getReturnSlot()).createWriteNode(expression);
            return factory.createFrameReturn(writeReturnSlotNode);
        } else {
            return factory.createReturn(expression);
        }
    }

    @Override
    public JavaScriptNode enterBlock(Block block) {
        FunctionEnvironment currentFunction = currentFunction();
        JavaScriptNode result;
        try (EnvironmentCloseable blockEnv = enterBlockEnvironment(block)) {
            List blockStatements = block.getStatements();
            List scopeInit = new ArrayList<>(block.getSymbolCount());
            if (block.getScope().hasBlockScopedOrRedeclaredSymbols() && !(environment instanceof GlobalEnvironment)) {
                createTemporalDeadZoneInit(block.getScope(), scopeInit);
            }
            if (block.isModuleBody()) {
                createResolveImports(lc.getCurrentFunction(), scopeInit);
            }
            if (block.getScope().isFunctionTopScope() || block.getScope().isEvalScope()) {
                prepareParameters(scopeInit);
            }
            if (block.isParameterBlock() || block.isFunctionBody()) {
                // If there is a parameter scope, we declare a dynamic eval scope in both.
                // This is intentional: eval in parameter expressions share a common var scope that
                // is separate from the function body.
                // Tracking eval separately for parameter and body blocks would allow us to be more
                // precise w.r.t. dynamic eval scope bindings but probably not worth it since eval
                // in parameter expressions is rare and there's little cost in an unused frame slot.
                if (!currentFunction.isGlobal() && currentFunction.isDynamicallyScoped()) {
                    environment.reserveDynamicScopeSlot();
                }
                if (block.isFunctionBody()) {
                    if (currentFunction.isCallerContextEval()) {
                        prependDynamicScopeBindingInit(block, scopeInit);
                    }
                }
            }

            JavaScriptNode blockNode;
            if (block.isFunctionBody()) {
                // Note: Parameters should already be initialized when entering the function body.
                // Therefore, we need to create and tag the body block without the prolog.
                blockNode = transformStatements(blockStatements, block.isTerminal(), block.isExpressionBlock() || block.isParameterBlock());

                if (block.isModuleBody()) {
                    blockNode = splitModuleBodyAtYield(blockNode, scopeInit);
                }

                FunctionNode function = lc.getCurrentFunction();
                blockNode = handleFunctionReturn(function, blockNode);
                if (currentFunction.isDerivedConstructor()) {
                    blockNode = finishDerivedConstructorBody(function, blockNode);
                }
                tagBody(blockNode, block);

                if (!scopeInit.isEmpty()) {
                    scopeInit.add(blockNode);
                    blockNode = factory.createExprBlock(scopeInit.toArray(EMPTY_NODE_ARRAY));
                }
            } else {
                // Move hoistable declaration to the front of the block
                List newBlockStatements = null;
                for (Statement statement : blockStatements) {
                    if (statement instanceof VarNode) {
                        VarNode varNode = (VarNode) statement;
                        if (varNode.isHoistableDeclaration()) {
                            if (newBlockStatements == null) {
                                newBlockStatements = new ArrayList<>();
                            }
                            newBlockStatements.add(statement);
                        }
                    }
                }
                if (newBlockStatements == null) {
                    // no hoistable declarations
                    newBlockStatements = blockStatements;
                } else {
                    // append other statements
                    for (Statement statement : blockStatements) {
                        if (statement instanceof VarNode) {
                            VarNode varNode = (VarNode) statement;
                            if (varNode.isHoistableDeclaration()) {
                                if (annexBBlockToFunctionTransfer(varNode)) {
                                    newBlockStatements.add(varNode.setFlag(VarNode.IS_ANNEXB_BLOCK_TO_FUNCTION_TRANSFER));
                                } // else among declarations already
                                continue;
                            }
                        }
                        newBlockStatements.add(statement);
                    }
                }
                blockNode = transformStatements(newBlockStatements, block.isTerminal(), block.isExpressionBlock() || block.isParameterBlock(), scopeInit);
            }

            result = blockEnv.wrapBlockScope(blockNode);
        }
        // Parameter initialization must precede (i.e. wrap) the (async) generator function body
        if (block.isFunctionBody()) {
            if (currentFunction.isGeneratorFunction() && !currentFunction.isModule()) {
                result = finishGeneratorBody(result);
            }
        }
        ensureHasSourceSection(result, block);
        return result;
    }

    private boolean annexBBlockToFunctionTransfer(VarNode varNode) {
        return context.isOptionAnnexB() && !environment.isStrictMode() && varNode.isFunctionDeclaration();
    }

    private boolean allowScopeOptimization() {
        return context.getLanguageOptions().scopeOptimization();
    }

    @SuppressWarnings("static-method")
    private boolean allowTDZOptimization() {
        return false;
    }

    /**
     * Initialize block-scoped symbols with a dead marker value.
     */
    private void createTemporalDeadZoneInit(Scope blockScope, List blockWithInit) {
        assert blockScope.hasBlockScopedOrRedeclaredSymbols() && !(environment instanceof GlobalEnvironment);

        List slotsWithTDZ = new ArrayList<>();
        for (Symbol symbol : blockScope.getSymbols()) {
            if (symbol.isImportBinding()) {
                continue;
            }
            if (symbol.isBlockScoped() && !symbol.hasBeenDeclared()) {
                // Exported variables always need a temporal dead zone. We do not track which
                // bindings are exported, so we assume all bindings in the module scope may be.
                // Also, variables declared in a switch block always need a temporal dead zone since
                // generally, there is no dominance relationship between declaration and use.
                // In other cases, we can statically determine if a local use is in the TDZ,
                // so we can skip the initialization if there is no non-local use (or eval).
                // However, we currently need to initialize the variable for instrumentation.
                if (symbol.isClosedOver() || symbol.isDeclaredInSwitchBlock() || blockScope.isModuleScope() || blockScope.hasNestedEval() || !allowScopeOptimization() || !allowTDZOptimization()) {
                    FrameSlotVarRef slotRef = (FrameSlotVarRef) findScopeVar(symbol.getNameTS(), true);
                    assert JSFrameUtil.hasTemporalDeadZone(slotRef.getFrameSlot()) : slotRef.getFrameSlot();
                    slotsWithTDZ.add(slotRef);
                }
            }
            if (symbol.isVarRedeclaredHere()) {
                // redeclaration of parameter binding; initial value is copied from outer scope.
                assert blockScope.isFunctionBodyScope();
                VarRef outerVarRef = environment.findBlockScopedVar(symbol.getNameTS());
                VarRef innerVarRef = findScopeVar(symbol.getNameTS(), true);
                JavaScriptNode outerVar = outerVarRef.createReadNode();
                blockWithInit.add(innerVarRef.createWriteNode(outerVar));
            }
        }

        if (!slotsWithTDZ.isEmpty()) {
            slotsWithTDZ.sort(Comparator.comparingInt(FrameSlotVarRef::getScopeLevel));
            if (slotsWithTDZ.size() == 1 || slotsWithTDZ.get(0).getScopeLevel() == slotsWithTDZ.get(slotsWithTDZ.size() - 1).getScopeLevel()) {
                // all slots are in the same frame
                int[] slots = new int[slotsWithTDZ.size()];
                ScopeFrameNode scope = slotsWithTDZ.get(0).createScopeFrameNode();
                for (int i = 0; i < slots.length; i++) {
                    slots[i] = slotsWithTDZ.get(i).getFrameSlot().getIndex();
                }
                blockWithInit.add(factory.createClearFrameSlots(scope, slots, 0, slots.length));
            } else {
                // we have slots in separate frames
                int[] slots = new int[slotsWithTDZ.size()];
                for (int from = 0; from < slots.length;) {
                    FrameSlotVarRef first = slotsWithTDZ.get(from);
                    ScopeFrameNode scope = first.createScopeFrameNode();
                    slots[from] = slotsWithTDZ.get(from).getFrameSlot().getIndex();
                    int to = from + 1;
                    while (to < slots.length) {
                        FrameSlotVarRef next = slotsWithTDZ.get(to);
                        if (next.getScopeLevel() != first.getScopeLevel()) {
                            break;
                        }
                        slots[to++] = next.getFrameSlot().getIndex();
                    }
                    blockWithInit.add(factory.createClearFrameSlots(scope, slots, from, to));
                    from = to;
                }
            }
        }
    }

    private JavaScriptNode wrapTemporalDeadZoneInit(Scope scope, JavaScriptNode blockBody) {
        if (!scope.hasBlockScopedOrRedeclaredSymbols()) {
            return blockBody;
        }
        List init = new ArrayList<>(4);
        createTemporalDeadZoneInit(scope, init);
        if (init.isEmpty()) {
            return blockBody;
        } else {
            init.add(blockBody);
            return factory.createExprBlock(init.toArray(EMPTY_NODE_ARRAY));
        }
    }

    private void createResolveImports(FunctionNode functionNode, List declarations) {
        assert functionNode.isModule();

        // Assert: all named exports from module are resolvable.
        for (ImportEntry importEntry : functionNode.getModule().getImportEntries()) {
            ModuleRequest moduleRequest = importEntry.getModuleRequest();
            TruffleString localName = importEntry.getLocalName();
            String localNameJS = localName.toJavaStringUncached();
            JSWriteFrameSlotNode writeLocalNode = (JSWriteFrameSlotNode) environment.findLocalVar(localName).createWriteNode(null);
            JavaScriptNode thisModule = getActiveModule();
            if (importEntry.getImportName().equals(Module.STAR_NAME)) {
                assert functionNode.getBody().getScope().hasSymbol(localNameJS) && functionNode.getBody().getScope().getExistingSymbol(localNameJS).hasBeenDeclared();
                declarations.add(factory.createResolveStarImport(context, thisModule, moduleRequest, writeLocalNode));
            } else {
                assert functionNode.getBody().getScope().hasSymbol(localNameJS) && functionNode.getBody().getScope().getExistingSymbol(localNameJS).isImportBinding();
                declarations.add(factory.createResolveNamedImport(context, thisModule, moduleRequest, importEntry.getImportName(), writeLocalNode));
            }
        }
    }

    /**
     * Moves all statements up to, and including, module yield to scopeInit and returns a new block
     * containing the rest of the body. This ensures a flat block up to the yield, followed by a
     * RootBody-tagged (block) node. Otherwise we might end up with a nested block that contains the
     * yield, making it more involved to split up the module at the yield into link() and execute();
     * but even if we kept the yield, we'd still have two blocks to resume into instead of one.
     *
     * 
     * [ScopeInit] | [ModuleLink Yield ModuleExecute]:body =>
     * [ScopeInit ModuleLink Yield] | [ModuleExecute]:body
     * 
*/ private JavaScriptNode splitModuleBodyAtYield(JavaScriptNode blockNode, List scopeInit) { if (blockNode instanceof SequenceNode) { JavaScriptNode[] statements = ((SequenceNode) blockNode).getStatements(); for (int i = 0; i < statements.length; i++) { JavaScriptNode statement = statements[i]; if (isModuleYieldStatement(statement)) { scopeInit.addAll(Arrays.asList(statements).subList(0, i + 1)); return factory.createExprBlock(Arrays.copyOfRange(statements, i + 1, statements.length)); } } } else if (isModuleYieldStatement(blockNode)) { scopeInit.add(blockNode); return factory.createEmpty(); } // If yield has not been found (unexpected), keep everything as it is. return blockNode; } /** * Detects a module yield, optionally wrapped in a return value slot assignment. */ private static boolean isModuleYieldStatement(JavaScriptNode statement) { return statement instanceof ModuleYieldNode || (statement instanceof JSWriteFrameSlotNode && ((JSWriteFrameSlotNode) statement).getRhs() instanceof ModuleYieldNode); } /** * Create var-declared dynamic scope bindings in the variable environment of the caller. */ private void prependDynamicScopeBindingInit(Block block, List blockWithInit) { assert currentFunction().isCallerContextEval(); for (Symbol symbol : block.getSymbols()) { if (symbol.isVar() && !environment.getVariableEnvironment().hasLocalVar(symbol.getName())) { blockWithInit.add(createDynamicScopeBinding(symbol.getNameTS(), true)); } } } private JavaScriptNode createDynamicScopeBinding(TruffleString varName, boolean deleteable) { assert deleteable; VarRef dynamicScopeVar = environment.findDynamicScopeVar(); return new DeclareEvalVariableNode(context, varName, dynamicScopeVar.createReadNode(), (WriteNode) dynamicScopeVar.createWriteNode(null)); } private JavaScriptNode transformStatements(List blockStatements, boolean terminal, boolean expressionBlock) { return transformStatements(blockStatements, terminal, expressionBlock, javaScriptNodeArray(blockStatements.size()), 0); } private JavaScriptNode transformStatements(List blockStatements, boolean terminal, boolean expressionBlock, List prolog) { final int size = prolog.size() + blockStatements.size(); JavaScriptNode[] statements = javaScriptNodeArray(size); int pos = 0; if (!prolog.isEmpty()) { for (; pos < prolog.size(); pos++) { statements[pos] = prolog.get(pos); } } return transformStatements(blockStatements, terminal, expressionBlock, statements, pos); } private JavaScriptNode transformStatements(List blockStatements, boolean terminal, boolean expressionBlock, JavaScriptNode[] statements, int destPos) { int pos = destPos; int lastNonEmptyIndex = -1; boolean returnsLastStatementResult = currentFunction().returnsLastStatementResult(); for (int i = 0; i < blockStatements.size(); i++) { Statement statement = blockStatements.get(i); JavaScriptNode statementNode = transformStatementInBlock(statement); if (returnsLastStatementResult) { if (statement.isCompletionValueNeverEmpty()) { lastNonEmptyIndex = pos; } else { if (lastNonEmptyIndex >= 0) { statements[lastNonEmptyIndex] = wrapSetCompletionValue(statements[lastNonEmptyIndex]); lastNonEmptyIndex = -1; } } } statements[pos++] = statementNode; } if (returnsLastStatementResult && lastNonEmptyIndex >= 0) { statements[lastNonEmptyIndex] = wrapSetCompletionValue(statements[lastNonEmptyIndex]); } assert pos == statements.length; return createBlock(statements, terminal, expressionBlock); } private EnvironmentCloseable enterBlockEnvironment(Block block) { // Global lexical environment is shared by scripts (but not eval). // Note: indirect eval creates a new environment for lexically-scoped declarations. // Note 2: When argument names are present in Source, use standard (local) block environment // for function-like semantics, not the global scope. if (block.isFunctionBody() && lc.getCurrentFunction().isScript() && this.argumentNames == null) { FunctionEnvironment currentFunction = currentFunction(); if (!currentFunction.isEval()) { GlobalEnvironment globalEnv = new GlobalEnvironment(environment, factory, context); setupGlobalEnvironment(globalEnv, block); return new EnvironmentCloseable(globalEnv); } else if (currentFunction.isIndirectEval()) { GlobalEnvironment globalEnv = new GlobalEnvironment(environment, factory, context); BlockEnvironment blockEnv = new BlockEnvironment(globalEnv, factory, context, block.getScope()); blockEnv.addFrameSlotsFromSymbols(block.getScope().getSymbols()); return new EnvironmentCloseable(blockEnv); } else { assert currentFunction.isDirectEval(); // We already have a global environment. } } return enterBlockEnvironment(block.getScope()); } private EnvironmentCloseable enterBlockEnvironment(Scope scope) { if (scope != null) { /* * The function environment is filled with top-level vars from the function body, unless * the function has parameter expressions, then the function body gets a separate scope * and we populate the env with parameter vars (cf. FunctionDeclarationInstantiation). */ if (scope.isFunctionTopScope() || scope.isEvalScope()) { assert environment instanceof FunctionEnvironment; Environment functionEnv = environment; FunctionNode function = lc.getCurrentFunction(); assert function.hasClosures() || !hasClosures(function.getBody()) : function; if (!function.isModule() && (allowScopeOptimization() ? BlockEnvironment.isScopeCaptured(scope) : (function.hasClosures() || function.hasEval()))) { functionEnv = new BlockEnvironment(environment, factory, context, scope); } boolean onlyBlockScoped = currentFunction().isCallerContextEval(); addFunctionFrameSlots(functionEnv, function); functionEnv.addFrameSlotsFromSymbols(scope.getSymbols(), onlyBlockScoped, null); return new EnvironmentCloseable(functionEnv); } else if (scope.hasDeclarations() || JSConfig.ManyBlockScopes) { BlockEnvironment blockEnv = new BlockEnvironment(environment, factory, context, scope); blockEnv.addFrameSlotsFromSymbols(scope.getSymbols()); return new EnvironmentCloseable(blockEnv); } } return new EnvironmentCloseable(environment); } private Environment newPerIterationEnvironment(Scope scope) { BlockEnvironment blockEnv = new BlockEnvironment(environment, factory, context, scope); blockEnv.addFrameSlotsFromSymbols(scope.getSymbols(), true, (!scope.hasNestedEval() && allowScopeOptimization()) ? Symbol::isClosedOver : null); return blockEnv; } private void addFunctionFrameSlots(Environment env, FunctionNode function) { if (function.needsArguments()) { assert function.getBody().getScope().hasSymbol(ARGUMENTS) : function; env.reserveArgumentsSlot(); } FunctionEnvironment currentFunction = env.function(); if (needsThisSlot(function, currentFunction)) { env.reserveThisSlot(); } if (function.needsSuper()) { // arrow functions need to access [[HomeObject]] from outer non-arrow scope // note: an arrow function using also needs access assert function.isMethod(); env.reserveSuperSlot(); } if (function.needsNewTarget()) { env.reserveNewTargetSlot(); } } private boolean needsThisSlot(FunctionNode function, FunctionEnvironment currentFunction) { if (currentFunction.isGlobal()) { return false; } // Reserve this slot if function uses this or has super(). Arrow functions and direct eval // in a derived class constructor must use the constructor's this slot. Otherwise, // non-strict direct eval uses the caller's `this` but gets it via the this argument. if (function.needsThis() && !((function.isArrow() || currentFunction.isDirectEval()) && currentFunction.getNonArrowParentFunction().isDerivedConstructor())) { return true; } if (function.needsSuper()) { assert function.isMethod(); // A method using `super` also needs `this`. return true; } if (function.isClassConstructor() && (lc.getCurrentClass().needsInitializeInstanceElements())) { // Allocate the this slot to ensure InitializeInstanceElements is performed // regardless of whether the class constructor itself uses `this`. // Note: the this slot could be elided in this case. return true; } return false; } /** * Set up slots for lexical declarations in the global environment. * * @see #collectGlobalVars(FunctionNode, boolean) */ private static void setupGlobalEnvironment(GlobalEnvironment globalEnv, Block block) { for (com.oracle.js.parser.ir.Symbol symbol : block.getSymbols()) { if (symbol.isImportBinding()) { continue; // no frame slot required } if (symbol.isBlockScoped()) { globalEnv.addLexicalDeclaration(symbol.getNameTS(), symbol.isConst()); } else if (symbol.isGlobal() && symbol.isVar()) { globalEnv.addVarDeclaration(symbol.getNameTS()); } } } private JavaScriptNode transformStatementInBlock(Statement statement) { return transform(statement); } @Override public JavaScriptNode enterBlockStatement(BlockStatement blockStatement) { return transform(blockStatement.getBlock()); } @Override public JavaScriptNode enterLiteralNode(LiteralNode literalNode) { if (literalNode instanceof LiteralNode.ArrayLiteralNode) { return tagExpression(createArrayLiteral(((LiteralNode.ArrayLiteralNode) literalNode).getElementExpressions()), literalNode); } else { return tagExpression(enterLiteralDefaultNode(literalNode), literalNode); } } private JavaScriptNode enterLiteralDefaultNode(LiteralNode literalNode) { Object value = literalNode.getValue(); if (value == null) { return factory.createConstantNull(); } else if (value instanceof Long) { // we don't support long type long longValue = (long) value; if (JSRuntime.isSafeInteger(longValue)) { return factory.createConstantSafeInteger(longValue); } return factory.createConstantDouble(longValue); } else if (value instanceof Lexer.RegexToken) { return factory.createRegExpLiteral(context, ((Lexer.RegexToken) value).getExpressionTS(), ((Lexer.RegexToken) value).getOptionsTS()); } else if (value instanceof BigInteger) { value = BigInt.fromBigInteger((BigInteger) value); } return factory.createConstant(value); } private JavaScriptNode createArrayLiteral(List elementExpressions) { JavaScriptNode[] elements = javaScriptNodeArray(elementExpressions.size()); boolean hasSpread = false; for (int i = 0; i < elementExpressions.size(); i++) { Expression elementExpression = elementExpressions.get(i); hasSpread = hasSpread || elementExpression != null && elementExpression.isTokenType(TokenType.SPREAD_ARRAY); elements[i] = elementExpression != null ? transform(elementExpression) : factory.createEmpty(); } return hasSpread ? factory.createArrayLiteralWithSpread(context, elements) : factory.createArrayLiteral(context, elements); } @Override public JavaScriptNode enterIdentNode(IdentNode identNode) { assert !identNode.isPropertyName(); final JavaScriptNode result; if (identNode.isThis()) { result = createThisNode(); } else if (identNode.isSuper()) { result = enterIdentNodeSuper(identNode); } else if (identNode.isNewTarget()) { result = enterNewTarget(); } else if (identNode.isImportMeta()) { result = enterImportMeta(); } else if (identNode.isPrivateInCheck()) { TruffleString privateVarName = identNode.getNameTS(); VarRef privateVarRef = environment.findLocalVar(privateVarName); JavaScriptNode readNode = privateVarRef.createReadNode(); JSFrameSlot frameSlot = privateVarRef.getFrameSlot(); if (JSFrameUtil.needsPrivateBrandCheck(frameSlot)) { // Create a brand node so that a brand check can be performed in the InNode. result = getPrivateBrandNode(frameSlot, privateVarRef); } else { result = readNode; } } else { TruffleString varName = identNode.getNameTS(); VarRef varRef = findScopeVarCheckTDZ(varName, false); result = varRef.createReadNode(); } return tagExpression(result, identNode); } private JavaScriptNode enterNewTarget() { return environment.findNewTargetVar().createReadNode(); } private JavaScriptNode enterIdentNodeSuper(IdentNode identNode) { if (!identNode.isDirectSuper()) { // ES6 12.3.5.3 Runtime Semantics: MakeSuperPropertyReference(propertyKey, strict) // ES6 8.1.1.3.5 GetSuperBase() JavaScriptNode getSuperBase = factory.createGetPrototype(environment.findSuperVar().createReadNode()); JavaScriptNode receiver = checkThisBindingInitialized(environment.findThisVar().createReadNode()); return factory.createSuperPropertyReference(getSuperBase, receiver); } else { // ES6 12.3.5.2 Runtime Semantics: GetSuperConstructor() assert identNode.isDirectSuper(); // super accesses should not reach here JavaScriptNode activeFunction = factory.createAccessCallee(currentFunction().getThisFunctionLevel()); JavaScriptNode superConstructor = factory.createGetPrototype(activeFunction); JavaScriptNode receiver = environment.findThisVar().createReadNode(); return factory.createTargetableWrapper(superConstructor, receiver); } } private JavaScriptNode createThisNode() { if (currentFunction().isGlobal()) { return factory.createAccessThis(); } else { return checkThisBindingInitialized(environment.findThisVar().createReadNode()); } } private JavaScriptNode createThisNodeUnchecked() { if (currentFunction().isGlobal()) { return factory.createAccessThis(); } else { return environment.findThisVar().createReadNode(); } } private JavaScriptNode checkThisBindingInitialized(JavaScriptNode accessThisNode) { // TODO in most cases we should be able to prove that `this` is already initialized if (currentFunction().getNonArrowParentFunction().isDerivedConstructor()) { return factory.createDerivedConstructorThis(accessThisNode); } return accessThisNode; } private JavaScriptNode enterImportMeta() { return factory.createImportMeta(getActiveModule()); } private JavaScriptNode getActiveModule() { assert lc.inModule(); return environment.findActiveModule().createReadNode(); } private VarRef findScopeVar(TruffleString name, boolean skipWith) { return environment.findVar(name, skipWith); } private VarRef findScopeVarCheckTDZ(TruffleString name, boolean initializationAssignment) { VarRef varRef = findScopeVar(name, false); if (varRef.isFunctionLocal()) { if (varRef.hasBeenDeclared()) { return varRef; } Symbol symbol = lc.getCurrentScope().findBlockScopedSymbolInFunction(varRef.getName().toJavaStringUncached()); if (symbol == null) { // variable is not block-scoped return varRef; } else if (symbol.hasBeenDeclared()) { // variable has been unconditionally declared already return varRef; } else if (symbol.isDeclaredInSwitchBlock()) { // we cannot statically determine whether a block-scoped variable is in TDZ // in an unprotected switch case context, so we always need a dynamic check return varRef.withTDZCheck(); } else if (initializationAssignment) { varRef.setHasBeenDeclared(true); return varRef; } else { // variable reference is unconditionally in the temporal dead zone, i.e., // var ref is in declaring function and in scope but before the actual declaration // Note: may not throw a ReferenceError if shadowed by a with statement. return varRef.withTDZCheck(); } } return varRef.withTDZCheck(); } @Override public JavaScriptNode enterVarNode(VarNode varNode) { TruffleString varName = varNode.getName().getNameTS(); assert currentFunction().isGlobal() && (!varNode.isBlockScoped() || lc.getCurrentBlock().isFunctionBody()) || !findScopeVar(varName, true).isGlobal() || currentFunction().isCallerContextEval() : varNode; Symbol symbol = null; VarRef varRef = null; if (varNode.isBlockScoped()) { // below, `symbol!=null` implies `isBlockScoped` symbol = lc.getCurrentScope().getExistingSymbol(varNode.getName().getName()); varRef = findScopeVar(varName, true); assert symbol != null && varRef != null : varName; } JavaScriptNode assignment; if (varNode.isAssignment()) { assignment = createVarAssignNode(varNode, varName); } else if (symbol != null && (!varNode.isDestructuring() || symbol.isDeclaredInSwitchBlock()) && !symbol.hasBeenDeclared()) { assert varNode.isBlockScoped(); assignment = varRef.createWriteNode(factory.createConstantUndefined()); } else { assignment = factory.createEmpty(); } // mark block-scoped symbols as declared, except: // (a) symbols declared in a switch case always need the dynamic TDZ check // (b) destructuring: the symbol does not come alive until the destructuring assignment if (symbol != null && (!symbol.isDeclaredInSwitchBlock() && !varNode.isDestructuring())) { assert varNode.isBlockScoped(); varRef.setHasBeenDeclared(true); } return assignment; } private JavaScriptNode createVarAssignNode(VarNode varNode, TruffleString varName) { JavaScriptNode assignment = null; if (varNode.isBlockScoped() && varNode.isFunctionDeclaration() && context.isOptionAnnexB()) { // B.3.3 Block-Level Function Declarations Web Legacy Compatibility Semantics FunctionNode fn = lc.getCurrentFunction(); if (!fn.isStrict()) { Symbol symbol = lc.getCurrentScope().getExistingSymbol(varName.toJavaStringUncached()); if (symbol.isHoistedBlockFunctionDeclaration()) { if (varNode.getFlag(VarNode.IS_ANNEXB_BLOCK_TO_FUNCTION_TRANSFER)) { assert hasVarSymbol(fn.getVarDeclarationBlock().getScope(), varName) : varName; JavaScriptNode blockScopeValue = findScopeVar(varName, false).createReadNode(); assignment = environment.findVar(varName, true, false, true, false, false).withRequired(false).createWriteNode(blockScopeValue); tagExpression(assignment, varNode); } } } } if (assignment == null) { JavaScriptNode rhs = transform(varNode.getAssignmentSource()); assignment = findScopeVar(varName, false).createWriteNode(rhs); } // class declarations are not statements nor expressions if (varNode.isClassDeclaration()) { return discardResult(assignment); } // do not halt on function declarations if (!varNode.isHoistableDeclaration()) { tagStatement(assignment, varNode); } ensureHasSourceSection(assignment, varNode); return discardResult(assignment); } private static boolean hasVarSymbol(Scope scope, TruffleString varName) { Symbol varSymbol = scope.getExistingSymbol(varName.toJavaStringUncached()); return varSymbol != null && (varSymbol.isVar() && !varSymbol.isParam()); } @Override public JavaScriptNode enterWhileNode(com.oracle.js.parser.ir.WhileNode whileNode) { JavaScriptNode test = transform(whileNode.getTest()); tagStatement(test, whileNode.getTest()); try (JumpTargetCloseable target = currentFunction().pushContinueTarget(null)) { JavaScriptNode body = transform(whileNode.getBody()); JavaScriptNode wrappedBody = wrapClearCompletionValue(target.wrapContinueTargetNode(body)); JavaScriptNode result; if (whileNode.isDoWhile()) { result = createDoWhile(test, wrappedBody); } else { result = createWhileDo(test, wrappedBody); } return wrapClearAndGetCompletionValue(target.wrapBreakTargetNode(ensureHasSourceSection(result, whileNode))); } } private static boolean isConstantFalse(JavaScriptNode condition) { return condition instanceof JSConstantNode && !JSRuntime.toBoolean(((JSConstantNode) condition).getValue()); } private JavaScriptNode createDoWhile(JavaScriptNode condition, JavaScriptNode body) { if (isConstantFalse(condition)) { // do {} while (0); happens 336 times in Mandreel return body; } RepeatingNode repeatingNode = factory.createDoWhileRepeatingNode(condition, body); return factory.createDoWhile(factory.createLoopNode(repeatingNode)); } private JavaScriptNode createWhileDo(JavaScriptNode condition, JavaScriptNode body) { if (isConstantFalse(condition)) { return factory.createEmpty(); } RepeatingNode repeatingNode = factory.createWhileDoRepeatingNode(condition, body); return factory.createWhileDo(factory.createLoopNode(repeatingNode)); } private JavaScriptNode wrapGetCompletionValue(JavaScriptNode target) { if (currentFunction().returnsLastStatementResult()) { VarRef returnVar = environment.findTempVar(currentFunction().getReturnSlot()); return factory.createExprBlock(target, returnVar.createReadNode()); } return target; } /** * Sets the completion value to the return value of the statement, which must never be empty. */ private JavaScriptNode wrapSetCompletionValue(JavaScriptNode statement) { if (currentFunction().returnsLastStatementResult()) { VarRef returnVar = environment.findTempVar(currentFunction().getReturnSlot()); return returnVar.createWriteNode(statement); } return statement; } private JavaScriptNode wrapClearCompletionValue(JavaScriptNode statement) { if (currentFunction().returnsLastStatementResult()) { VarRef returnVar = environment.findTempVar(currentFunction().getReturnSlot()); return factory.createExprBlock(returnVar.createWriteNode(factory.createConstantUndefined()), statement); } return statement; } /** * Wraps a statement, completion value of which is never the value empty. Sets the completion * value to undefined, executes the statement, and reads and returns the completion value. */ private JavaScriptNode wrapClearAndGetCompletionValue(JavaScriptNode statement) { if (currentFunction().returnsLastStatementResult()) { VarRef returnVar = environment.findTempVar(currentFunction().getReturnSlot()); return factory.createExprBlock(returnVar.createWriteNode(factory.createConstantUndefined()), statement, returnVar.createReadNode()); } return statement; } private JavaScriptNode wrapSaveAndRestoreCompletionValue(JavaScriptNode statement) { if (currentFunction().returnsLastStatementResult()) { VarRef returnVar = environment.findTempVar(currentFunction().getReturnSlot()); VarRef tempVar = environment.createTempVar(); return factory.createExprBlock(tempVar.createWriteNode(returnVar.createReadNode()), statement, returnVar.createWriteNode(tempVar.createReadNode())); } return statement; } @Override public JavaScriptNode enterForNode(ForNode forNode) { // if init is destructuring, wait with transformation JavaScriptNode init = forNode.getInit() != null && !forNode.isForInOrOf() ? tagStatement(transform(forNode.getInit()), forNode.getInit()) : factory.createEmpty(); JavaScriptNode test = forNode.getTest() != null && forNode.getTest().getExpression() != null ? tagStatement(transform(forNode.getTest()), forNode.getTest()) : factory.createConstantBoolean(true); JavaScriptNode modify = forNode.getModify() != null ? tagStatement(transform(forNode.getModify()), forNode.getModify()) : factory.createEmpty(); try (JumpTargetCloseable target = currentFunction().pushContinueTarget(null)) { JavaScriptNode result; if (forNode.isForOf()) { result = desugarForOf(forNode, modify, target); } else if (forNode.isForIn()) { result = desugarForIn(forNode, modify, target); } else if (forNode.isForAwaitOf()) { result = desugarForAwaitOf(forNode, modify, target); } else { JavaScriptNode body = transform(forNode.getBody()); JavaScriptNode wrappedBody = wrapClearCompletionValue(target.wrapContinueTargetNode(body)); result = target.wrapBreakTargetNode(desugarFor(forNode, init, test, modify, wrappedBody)); } return wrapClearAndGetCompletionValue(result); } } private JavaScriptNode desugarFor(ForNode forNode, JavaScriptNode init, JavaScriptNode test, JavaScriptNode modify, JavaScriptNode wrappedBody) { if (needsPerIterationScope(forNode)) { VarRef firstTempVar = environment.createTempVar(); JSFrameDescriptor iterationBlockFrameDescriptor = environment.getBlockFrameDescriptor(); RepeatingNode repeatingNode = factory.createForRepeatingNode(test, wrappedBody, modify, iterationBlockFrameDescriptor.toFrameDescriptor(), firstTempVar.createReadNode(), firstTempVar.createWriteNode(factory.createConstantBoolean(false)), environment.getCurrentBlockScopeSlot()); StatementNode newFor = factory.createFor(factory.createLoopNode(repeatingNode)); ensureHasSourceSection(newFor, forNode); return createBlock(init, firstTempVar.createWriteNode(factory.createConstantBoolean(true)), newFor); } RepeatingNode repeatingNode = factory.createWhileDoRepeatingNode(test, createBlock(wrappedBody, modify)); JavaScriptNode whileDo = factory.createDesugaredFor(factory.createLoopNode(repeatingNode)); if (forNode.getTest() == null) { tagStatement(test, forNode); } else { ensureHasSourceSection(whileDo, forNode); } return createBlock(init, whileDo); } private JavaScriptNode desugarForIn(ForNode forNode, JavaScriptNode modify, JumpTargetCloseable jumpTarget) { JavaScriptNode createIteratorNode; if (forNode.isForEach()) { createIteratorNode = factory.createEnumerate(context, modify, true); } else { assert forNode.isForIn() && !forNode.isForEach() && !forNode.isForOf(); createIteratorNode = factory.createEnumerate(context, modify, false); } return desugarForInOrOfBody(forNode, factory.createGetIterator(createIteratorNode), jumpTarget); } private JavaScriptNode desugarForOf(ForNode forNode, JavaScriptNode modify, JumpTargetCloseable jumpTarget) { assert forNode.isForOf(); JavaScriptNode getIterator = factory.createGetIterator(modify); return desugarForInOrOfBody(forNode, getIterator, jumpTarget); } private JavaScriptNode desugarForInOrOfBody(ForNode forNode, JavaScriptNode iterator, JumpTargetCloseable jumpTarget) { assert forNode.isForInOrOf(); VarRef iteratorVar = environment.createTempVar(); JavaScriptNode iteratorInit = iteratorVar.createWriteNode(iterator); VarRef nextResultVar = environment.createTempVar(); JavaScriptNode iteratorNext = factory.createIteratorNext(iteratorVar.createReadNode()); // nextResult = IteratorNext(iterator) // while(!(done = IteratorComplete(nextResult))) JavaScriptNode condition = factory.createDual(context, factory.createIteratorSetDone(iteratorVar.createReadNode(), factory.createConstantBoolean(true)), factory.createUnary(UnaryOperation.NOT, factory.createIteratorComplete(context, nextResultVar.createWriteNode(iteratorNext)))); JavaScriptNode wrappedBody; try (EnvironmentCloseable blockEnv = new EnvironmentCloseable(needsPerIterationScope(forNode) ? newPerIterationEnvironment(lc.getCurrentBlock().getScope()) : environment)) { // var nextValue = IteratorValue(nextResult); VarRef nextResultVar2 = environment.findTempVar(nextResultVar.getFrameSlot()); VarRef nextValueVar = environment.createTempVar(); VarRef iteratorVar2 = environment.findTempVar(iteratorVar.getFrameSlot()); JavaScriptNode nextResult = nextResultVar2.createReadNode(); JavaScriptNode nextValue = factory.createIteratorValue(nextResult); JavaScriptNode writeNextValue = nextValueVar.createWriteNode(nextValue); JavaScriptNode writeNext = tagStatement(desugarForHeadAssignment(forNode, nextValueVar.createReadNode()), forNode); JavaScriptNode body = transform(forNode.getBody()); wrappedBody = blockEnv.wrapBlockScope(createBlock( writeNextValue, factory.createIteratorSetDone(iteratorVar2.createReadNode(), factory.createConstantBoolean(false)), writeNext, body)); } wrappedBody = jumpTarget.wrapContinueTargetNode(wrappedBody); RepeatingNode repeatingNode = factory.createWhileDoRepeatingNode(condition, wrappedBody); LoopNode loopNode = factory.createLoopNode(repeatingNode); JavaScriptNode whileNode = forNode.isForOf() ? factory.createDesugaredForOf(loopNode) : factory.createDesugaredForIn(loopNode); JavaScriptNode wrappedWhile = factory.createIteratorCloseIfNotDone(context, jumpTarget.wrapBreakTargetNode(whileNode), iteratorVar.createReadNode()); JavaScriptNode resetIterator = iteratorVar.createWriteNode(factory.createConstant(JSFrameUtil.DEFAULT_VALUE)); wrappedWhile = factory.createTryFinally(wrappedWhile, resetIterator); ensureHasSourceSection(whileNode, forNode); return createBlock(iteratorInit, wrappedWhile); } private JavaScriptNode desugarForHeadAssignment(ForNode forNode, JavaScriptNode next) { boolean lexicalBindingInit = forNode.hasPerIterationScope(); if (forNode.getInit() instanceof IdentNode && lexicalBindingInit) { return tagExpression(findScopeVarCheckTDZ(((IdentNode) forNode.getInit()).getNameTS(), lexicalBindingInit).createWriteNode(next), forNode); } else { // transform destructuring assignment return tagExpression(transformAssignment(forNode.getInit(), forNode.getInit(), next, lexicalBindingInit), forNode); } } private JavaScriptNode desugarForAwaitOf(ForNode forNode, JavaScriptNode modify, JumpTargetCloseable jumpTarget) { assert forNode.isForAwaitOf(); JavaScriptNode getIterator = factory.createGetAsyncIterator(modify); VarRef iteratorVar = environment.createTempVar(); JavaScriptNode iteratorInit = iteratorVar.createWriteNode(getIterator); VarRef nextResultVar = environment.createTempVar(); currentFunction().addAwait(); JSReadFrameSlotNode asyncResultNode = (JSReadFrameSlotNode) environment.findTempVar(currentFunction().getAsyncResultSlot()).createReadNode(); JSReadFrameSlotNode asyncContextNode = (JSReadFrameSlotNode) environment.findTempVar(currentFunction().getAsyncContextSlot()).createReadNode(); JSFrameDescriptor functionFrameDesc = environment.getFunctionFrameDescriptor(); JSFrameSlot stateSlot = addGeneratorStateSlot(functionFrameDesc, FrameSlotKind.Int); JavaScriptNode iteratorNext = factory.createAsyncIteratorNext(context, stateSlot, iteratorVar.createReadNode(), asyncContextNode, asyncResultNode); // nextResult = Await(IteratorNext(iterator)) // while(!(done = IteratorComplete(nextResult))) JavaScriptNode condition = factory.createDual(context, factory.createIteratorSetDone(iteratorVar.createReadNode(), factory.createConstantBoolean(true)), factory.createUnary(UnaryOperation.NOT, factory.createIteratorComplete(context, nextResultVar.createWriteNode(iteratorNext)))); JavaScriptNode wrappedBody; try (EnvironmentCloseable blockEnv = new EnvironmentCloseable(needsPerIterationScope(forNode) ? newPerIterationEnvironment(lc.getCurrentBlock().getScope()) : environment)) { // var nextValue = IteratorValue(nextResult); VarRef nextResultVar2 = environment.findTempVar(nextResultVar.getFrameSlot()); VarRef nextValueVar = environment.createTempVar(); VarRef iteratorVar2 = environment.findTempVar(iteratorVar.getFrameSlot()); JavaScriptNode nextResult = nextResultVar2.createReadNode(); JavaScriptNode nextValue = factory.createIteratorValue(nextResult); JavaScriptNode writeNextValue = nextValueVar.createWriteNode(nextValue); JavaScriptNode writeNext = tagStatement(desugarForHeadAssignment(forNode, nextValueVar.createReadNode()), forNode); JavaScriptNode body = transform(forNode.getBody()); wrappedBody = blockEnv.wrapBlockScope(createBlock( writeNextValue, factory.createIteratorSetDone(iteratorVar2.createReadNode(), factory.createConstantBoolean(false)), writeNext, body)); } wrappedBody = jumpTarget.wrapContinueTargetNode(wrappedBody); RepeatingNode repeatingNode = factory.createWhileDoRepeatingNode(condition, wrappedBody); LoopNode loopNode = factory.createLoopNode(repeatingNode); JavaScriptNode whileNode = factory.createDesugaredForAwaitOf(loopNode); currentFunction().addAwait(); stateSlot = addGeneratorStateSlot(functionFrameDesc, FrameSlotKind.Object); JavaScriptNode wrappedWhile = factory.createAsyncIteratorCloseWrapper(context, stateSlot, jumpTarget.wrapBreakTargetNode(whileNode), iteratorVar.createReadNode(), asyncContextNode, asyncResultNode); JavaScriptNode resetIterator = iteratorVar.createWriteNode(factory.createConstant(JSFrameUtil.DEFAULT_VALUE)); wrappedWhile = factory.createTryFinally(wrappedWhile, resetIterator); ensureHasSourceSection(whileNode, forNode); return createBlock(iteratorInit, wrappedWhile); } private boolean needsPerIterationScope(ForNode forNode) { // for loop init block may contain closures, too; that's why we check the surrounding block. if (forNode.hasPerIterationScope()) { if (allowScopeOptimization()) { Scope forScope = lc.getCurrentScope(); // assert forScope.hasDeclarations(); // implied by per-iteration scope flag. if (!forScope.hasDeclarations()) { return false; } if (forScope.hasClosures() || forScope.hasNestedEval()) { return true; } } else { FunctionNode function = lc.getCurrentFunction(); if (function.hasClosures() && hasClosures(lc.getCurrentBlock())) { return true; } else if (function.hasEval()) { return true; } } } return false; } private static boolean hasClosures(com.oracle.js.parser.ir.Node node) { class HasClosuresVisitor extends NodeVisitor { boolean hasClosures; HasClosuresVisitor(LexicalContext lc) { super(lc); } @Override public boolean enterFunctionNode(FunctionNode functionNode) { hasClosures = true; return false; // do not descend into functions } } HasClosuresVisitor visitor = new HasClosuresVisitor(new LexicalContext()); node.accept(visitor); return visitor.hasClosures; } @Override public JavaScriptNode enterLabelNode(com.oracle.js.parser.ir.LabelNode labelNode) { try (JumpTargetCloseable breakTarget = currentFunction().pushBreakTarget(labelNode.getLabelName())) { JavaScriptNode body = transform(labelNode.getBody()); return breakTarget.wrapLabelBreakTargetNode(body); } } @Override public JavaScriptNode enterBreakNode(com.oracle.js.parser.ir.BreakNode breakNode) { return tagStatement(factory.createBreak(currentFunction().findBreakTarget(breakNode.getLabelName())), breakNode); } @Override public JavaScriptNode enterContinueNode(com.oracle.js.parser.ir.ContinueNode continueNode) { return tagStatement(factory.createContinue(currentFunction().findContinueTarget(continueNode.getLabelName())), continueNode); } @Override public JavaScriptNode enterIfNode(com.oracle.js.parser.ir.IfNode ifNode) { JavaScriptNode test = transform(ifNode.getTest()); JavaScriptNode pass = transform(ifNode.getPass()); JavaScriptNode fail = transform(ifNode.getFail()); return tagStatement(factory.createIf(test, pass, fail), ifNode); } @Override public JavaScriptNode enterTernaryNode(TernaryNode ternaryNode) { JavaScriptNode test = transform(ternaryNode.getTest()); JavaScriptNode pass = transform(ternaryNode.getTrueExpression()); JavaScriptNode fail = transform(ternaryNode.getFalseExpression()); return tagExpression(factory.createIf(test, pass, fail), ternaryNode); } @Override public JavaScriptNode enterUnaryNode(UnaryNode unaryNode) { switch (unaryNode.tokenType()) { case ADD: case BIT_NOT: case NOT: case SUB: case VOID: return enterUnaryDefaultNode(unaryNode); case TYPEOF: return enterTypeofNode(unaryNode); case INCPREFIX: case INCPOSTFIX: case DECPREFIX: case DECPOSTFIX: return enterUnaryIncDecNode(unaryNode); case NEW: return enterNewNode(unaryNode); case DELETE: return enterDelete(unaryNode); case SPREAD_ARGUMENT: { JavaScriptNode argument = transform(unaryNode.getExpression()); GetIteratorUnaryNode getIterator = factory.createGetIterator(argument); return tagExpression(factory.createSpreadArgument(context, getIterator), unaryNode); } case SPREAD_ARRAY: { JavaScriptNode array = transform(unaryNode.getExpression()); GetIteratorUnaryNode getIterator = factory.createGetIterator(array); return tagExpression(factory.createSpreadArray(context, getIterator), unaryNode); } case YIELD: case YIELD_STAR: return tagExpression(createYieldNode(unaryNode), unaryNode); case AWAIT: return tagExpression(translateAwaitNode(unaryNode), unaryNode); case NAMEDEVALUATION: return enterNamedEvaluation(unaryNode); default: throw new UnsupportedOperationException(unaryNode.tokenType().toString()); } } public JSFrameSlot addGeneratorStateSlot(JSFrameDescriptor functionFrameDescriptor, FrameSlotKind slotKind) { InternalSlotId identifier = factory.createInternalSlotId("generatorstate", functionFrameDescriptor.getSize()); return functionFrameDescriptor.addFrameSlot(identifier, slotKind); } private JavaScriptNode translateAwaitNode(UnaryNode unaryNode) { JavaScriptNode expression = transform(unaryNode.getExpression()); return createAwaitNode(expression); } private JavaScriptNode createAwaitNode(JavaScriptNode expression) { FunctionEnvironment currentFunction = currentFunction(); currentFunction.addAwait(); JSReadFrameSlotNode asyncContextNode = (JSReadFrameSlotNode) environment.findTempVar(currentFunction.getAsyncContextSlot()).createReadNode(); JSReadFrameSlotNode asyncResultNode = (JSReadFrameSlotNode) environment.findTempVar(currentFunction.getAsyncResultSlot()).createReadNode(); JSFrameSlot stateSlot = addGeneratorStateSlot(currentFunction.getFunctionFrameDescriptor(), FrameSlotKind.Int); return factory.createAwait(context, stateSlot, expression, asyncContextNode, asyncResultNode); } private JavaScriptNode createYieldNode(UnaryNode unaryNode) { FunctionEnvironment currentFunction = currentFunction(); assert currentFunction.isGeneratorFunction(); JSFrameDescriptor functionFrameDesc = currentFunction.getFunctionFrameDescriptor(); if (lc.getCurrentFunction().isModule()) { return factory.createModuleYield(); } boolean asyncGeneratorYield = currentFunction.isAsyncFunction(); boolean yieldStar = unaryNode.tokenType() == TokenType.YIELD_STAR; JavaScriptNode expression = transform(unaryNode.getExpression()); ReturnNode returnNode = createReturnNode(null); if (asyncGeneratorYield) { currentFunction.addAwait(); JSReadFrameSlotNode asyncContextNode = (JSReadFrameSlotNode) environment.findTempVar(currentFunction.getAsyncContextSlot()).createReadNode(); JSReadFrameSlotNode asyncResultNode = (JSReadFrameSlotNode) environment.findTempVar(currentFunction.getAsyncResultSlot()).createReadNode(); JSFrameSlot stateSlot = addGeneratorStateSlot(functionFrameDesc, FrameSlotKind.Int); if (yieldStar) { JSFrameSlot iteratorTempSlot = addGeneratorStateSlot(functionFrameDesc, FrameSlotKind.Object); return factory.createAsyncGeneratorYieldStar(context, stateSlot, iteratorTempSlot, expression, asyncContextNode, asyncResultNode, returnNode); } else { return factory.createAsyncGeneratorYield(context, stateSlot, expression, asyncContextNode, asyncResultNode, returnNode); } } else { currentFunction.addYield(); JSWriteFrameSlotNode writeYieldResultNode = JSConfig.YieldResultInFrame ? (JSWriteFrameSlotNode) environment.findTempVar(currentFunction.getYieldResultSlot()).createWriteNode(null) : null; JSFrameSlot stateSlot = addGeneratorStateSlot(functionFrameDesc, yieldStar ? FrameSlotKind.Object : FrameSlotKind.Int); return factory.createYield(context, stateSlot, expression, environment.findYieldValueVar().createReadNode(), yieldStar, returnNode, writeYieldResultNode); } } private JavaScriptNode enterUnaryDefaultNode(UnaryNode unaryNode) { assert unaryNode.tokenType() != TokenType.TYPEOF; JavaScriptNode operand = transform(unaryNode.getExpression()); return tagExpression(factory.createUnary(tokenTypeToUnaryOperation(unaryNode.tokenType()), operand), unaryNode); } private JavaScriptNode enterTypeofNode(UnaryNode unaryNode) { assert unaryNode.tokenType() == TokenType.TYPEOF; JavaScriptNode operand = null; if (unaryNode.getExpression() instanceof IdentNode) { IdentNode identNode = (IdentNode) unaryNode.getExpression(); String identNodeName = identNode.getName(); TruffleString identNodeNameTS = identNode.getNameTS(); if (context.isOptionNashornCompatibilityMode() && (identNodeName.equals(LINE__) || identNodeName.equals(FILE__) || identNodeName.equals(DIR__))) { operand = GlobalPropertyNode.createPropertyNode(context, identNodeNameTS); } else if (!identNode.isThis() && !identNode.isMetaProperty()) { // typeof globalVar must not throw ReferenceError if globalVar does not exist operand = findScopeVarCheckTDZ(identNodeNameTS, false).withRequired(false).createReadNode(); } } if (operand == null) { operand = transform(unaryNode.getExpression()); } else { tagExpression(operand, unaryNode.getExpression()); } return tagExpression(factory.createUnary(tokenTypeToUnaryOperation(unaryNode.tokenType()), operand), unaryNode); } private JavaScriptNode enterUnaryIncDecNode(UnaryNode unaryNode) { if (JSConfig.LocalVarIncDecNode && unaryNode.getExpression() instanceof IdentNode) { IdentNode identNode = (IdentNode) unaryNode.getExpression(); assert !identNode.isPropertyName() && !identNode.isThis() && !identNode.isMetaProperty() && !identNode.isSuper(); VarRef varRef = findScopeVarCheckTDZ(identNode.getNameTS(), false); if (varRef instanceof FrameSlotVarRef) { FrameSlotVarRef frameVarRef = (FrameSlotVarRef) varRef; JSFrameSlot frameSlot = frameVarRef.getFrameSlot(); if (JSFrameUtil.isConst(frameSlot)) { // we know this is going to throw. do the read and throw TypeError. return tagExpression(checkMutableBinding(frameVarRef.createReadNode(), frameSlot.getIdentifier()), unaryNode); } return tagExpression(factory.createLocalVarInc(tokenTypeToUnaryOperation(unaryNode.tokenType()), frameSlot, frameVarRef.hasTDZCheck(), frameVarRef.createScopeFrameNode()), unaryNode); } } BinaryOperation operation = unaryNode.tokenType() == TokenType.INCPREFIX || unaryNode.tokenType() == TokenType.INCPOSTFIX ? BinaryOperation.ADD : BinaryOperation.SUBTRACT; boolean isPostfix = unaryNode.tokenType() == TokenType.INCPOSTFIX || unaryNode.tokenType() == TokenType.DECPOSTFIX; return tagExpression(transformCompoundAssignment(unaryNode, unaryNode.getExpression(), factory.createConstantNumericUnit(), operation, isPostfix, true), unaryNode); } private static UnaryOperation tokenTypeToUnaryOperation(TokenType tokenType) { switch (tokenType) { case ADD: return UnaryOperation.PLUS; case BIT_NOT: return UnaryOperation.BITWISE_COMPLEMENT; case NOT: return UnaryOperation.NOT; case SUB: return UnaryOperation.MINUS; case TYPEOF: return UnaryOperation.TYPE_OF; case VOID: return UnaryOperation.VOID; case DECPREFIX: return UnaryOperation.PREFIX_LOCAL_DECREMENT; case DECPOSTFIX: return UnaryOperation.POSTFIX_LOCAL_DECREMENT; case INCPREFIX: return UnaryOperation.PREFIX_LOCAL_INCREMENT; case INCPOSTFIX: return UnaryOperation.POSTFIX_LOCAL_INCREMENT; case NEW: case DELETE: default: throw new UnsupportedOperationException(tokenType.toString()); } } private JavaScriptNode enterNamedEvaluation(UnaryNode unaryNode) { return factory.createNamedEvaluation(transform(unaryNode.getExpression()), factory.createAccessArgument(1)); } private JavaScriptNode enterDelete(UnaryNode unaryNode) { Expression rhs = unaryNode.getExpression(); if (rhs instanceof AccessNode || rhs instanceof IndexNode) { return enterDeleteProperty(unaryNode); } else { return enterDeleteIdent(unaryNode); } } private JavaScriptNode enterDeleteIdent(UnaryNode unaryNode) { Expression rhs = unaryNode.getExpression(); JavaScriptNode result; if (rhs instanceof IdentNode) { // attempt to delete a binding TruffleString varName = ((IdentNode) rhs).getNameTS(); VarRef varRef = findScopeVar(varName, varName.equals(Strings.THIS)); result = varRef.createDeleteNode(); } else { // deleting a non-reference, always returns true if (rhs instanceof LiteralNode.PrimitiveLiteralNode) { result = factory.createConstantBoolean(true); } else { result = factory.createDual(context, transform(rhs), factory.createConstantBoolean(true)); } } return tagExpression(result, unaryNode); } private JavaScriptNode enterDeleteProperty(UnaryNode deleteNode) { BaseNode baseNode = (BaseNode) deleteNode.getExpression(); JavaScriptNode target = transform(baseNode.getBase()); JavaScriptNode key; if (baseNode instanceof AccessNode) { AccessNode accessNode = (AccessNode) baseNode; assert !accessNode.isPrivate(); key = factory.createConstantString(accessNode.getPropertyTS()); } else { assert baseNode instanceof IndexNode; IndexNode indexNode = (IndexNode) baseNode; key = transform(indexNode.getIndex()); } if (baseNode.isSuper()) { // delete UnaryExpression: // Let ref be ? Evaluation of UnaryExpression. // If IsSuperReference(ref) is true, throw a ReferenceError exception. // => ToPropertyKey(key) must be evaluated for 'delete super[key]' before we throw return tagExpression(factory.createDual(context, factory.createToPropertyKey(key), factory.createThrowError(JSErrorType.ReferenceError, UNSUPPORTED_REFERENCE_TO_SUPER)), deleteNode); } if (baseNode.isOptionalChain()) { target = filterOptionalChainTarget(target, baseNode.isOptional()); } JavaScriptNode delete = factory.createDeleteProperty(target, key, environment.isStrictMode()); tagExpression(delete, deleteNode); if (baseNode.isOptionalChain()) { delete = factory.createOptionalChain(delete); } return delete; } private JavaScriptNode filterOptionalChainTarget(JavaScriptNode target, boolean optional) { JavaScriptNode innerAccess; if (target instanceof OptionalChainNode) { innerAccess = ((OptionalChainNode) target).getAccessNode(); } else if (target instanceof OptionalChainNode.OptionalTargetableNode) { innerAccess = ((OptionalChainNode.OptionalTargetableNode) target).getDelegateNode(); } else { innerAccess = target; } if (optional) { innerAccess = factory.createOptionalChainShortCircuit(innerAccess); } return innerAccess; } private JavaScriptNode[] transformArgs(List argList) { int len = argList.size(); if (len > context.getFunctionArgumentsLimit()) { throw Errors.createSyntaxError("function has too many parameters"); } JavaScriptNode[] args = javaScriptNodeArray(len); for (int i = 0; i < len; i++) { args[i] = transform(argList.get(i)); } return args; } private JavaScriptNode enterNewNode(UnaryNode unaryNode) { CallNode callNode = (CallNode) unaryNode.getExpression(); JavaScriptNode function = transform(callNode.getFunction()); JavaScriptNode[] args = transformArgs(callNode.getArgs()); AbstractFunctionArgumentsNode arguments = factory.createFunctionArguments(context, args); JavaScriptNode call = factory.createNew(context, function, arguments); return tagExpression(tagCall(call), unaryNode); } @Override public JavaScriptNode enterCallNode(CallNode callNode) { JavaScriptNode function = transform(callNode.getFunction()); JavaScriptNode[] args = transformArgs(callNode.getArgs()); if (callNode.isOptionalChain()) { function = filterOptionalChainTarget(function, callNode.isOptional()); } JavaScriptNode call; if (callNode.isEval() && args.length >= 1) { call = createCallEvalNode(function, args); } else if (callNode.isApplyArguments() && currentFunction().isDirectArgumentsAccess()) { call = createCallApplyArgumentsNode(function, args); } else if (callNode.getFunction() instanceof IdentNode && ((IdentNode) callNode.getFunction()).isDirectSuper()) { call = createCallDirectSuper(function, args, callNode.isDefaultDerivedConstructorSuperCall()); } else if (callNode.isImport()) { call = createImportCallNode(args); } else { call = factory.createFunctionCall(context, function, args); } tagExpression(tagCall(call), callNode); if (callNode.isOptionalChain()) { call = factory.createOptionalChain(call); } return call; } private JavaScriptNode[] insertNewTargetArg(JavaScriptNode[] args) { JavaScriptNode[] result = new JavaScriptNode[args.length + 1]; result[0] = environment.findNewTargetVar().createReadNode(); System.arraycopy(args, 0, result, 1, args.length); return result; } /** * Initialize derived constructor this value. */ private JavaScriptNode initializeThis(JavaScriptNode thisValueNode) { VarRef thisVar = environment.findThisVar(); // (GR-2061) we don't have to do this check if super() can be called only once, provably // (incl. possible super calls in nested arrow functions) // => return factory.createWriteNode(thisVarNode, thisValueNode, context); VarRef tempVar = environment.createTempVar(); JavaScriptNode uninitialized = factory.createBinary(context, BinaryOperation.IDENTICAL, thisVar.createReadNode(), factory.createConstantUndefined()); return factory.createIf(factory.createDual(context, tempVar.createWriteNode(thisValueNode), uninitialized), initializeInstanceElements(thisVar.createWriteNode(tempVar.createReadNode())), factory.createThrowError(JSErrorType.ReferenceError, SUPER_CALLED_TWICE)); } private JavaScriptNode initializeInstanceElements(JavaScriptNode thisValueNode) { ClassNode classNode = lc.getCurrentClass(); if (!classNode.needsInitializeInstanceElements()) { return thisValueNode; } JavaScriptNode constructor = factory.createAccessCallee(currentFunction().getThisFunctionLevel()); return factory.createInitializeInstanceElements(context, thisValueNode, constructor); } private JavaScriptNode createCallEvalNode(JavaScriptNode function, JavaScriptNode[] args) { assert (currentFunction().isGlobal() || currentFunction().isStrictMode() || currentFunction().isDirectEval()) || currentFunction().isDynamicallyScoped(); currentFunction().prepareForDirectEval(); return EvalNode.create(context, function, args, createThisNodeUnchecked(), new DirectEvalContext(lc.getCurrentScope(), environment, lc.getCurrentClass(), activeScriptOrModule), environment.getCurrentBlockScopeSlot()); } private JavaScriptNode createCallApplyArgumentsNode(JavaScriptNode function, JavaScriptNode[] args) { return factory.createCallApplyArguments((JSFunctionCallNode) factory.createFunctionCall(context, function, args)); } private JavaScriptNode createCallDirectSuper(JavaScriptNode function, JavaScriptNode[] args, boolean inDefaultDerivedConstructor) { if (inDefaultDerivedConstructor) { VarRef thisVar = environment.findThisVar(); return initializeInstanceElements(thisVar.createWriteNode(factory.createDefaultDerivedConstructorSuperCall(function))); } else { return initializeThis(factory.createFunctionCallWithNewTarget(context, function, insertNewTargetArg(args))); } } private JavaScriptNode createImportCallNode(JavaScriptNode[] args) { assert args.length == 1 || (context.getLanguageOptions().importAttributes() && args.length == 2); if (context.getLanguageOptions().importAttributes() && args.length == 2) { return factory.createImportCall(context, args[0], activeScriptOrModule, args[1]); } return factory.createImportCall(context, args[0], activeScriptOrModule); } @Override public JavaScriptNode enterBinaryNode(BinaryNode binaryNode) { switch (binaryNode.tokenType()) { case ASSIGN: case ASSIGN_INIT: return enterBinaryAssignNode(binaryNode); case ASSIGN_ADD: case ASSIGN_BIT_AND: case ASSIGN_BIT_OR: case ASSIGN_BIT_XOR: case ASSIGN_DIV: case ASSIGN_MOD: case ASSIGN_MUL: case ASSIGN_EXP: case ASSIGN_SAR: case ASSIGN_SHL: case ASSIGN_SHR: case ASSIGN_SUB: case ASSIGN_AND: case ASSIGN_OR: case ASSIGN_NULLCOAL: return enterBinaryTransformNode(binaryNode); case ADD: case SUB: case MUL: case EXP: case DIV: case MOD: case EQ: case EQ_STRICT: case GE: case GT: case LE: case LT: case NE: case NE_STRICT: case BIT_AND: case BIT_OR: case BIT_XOR: case SAR: case SHL: case SHR: case AND: case OR: case NULLISHCOALESC: case INSTANCEOF: case IN: case COMMARIGHT: return enterBinaryExpressionNode(binaryNode); case COMMALEFT: case ARROW: default: throw new UnsupportedOperationException(binaryNode.tokenType().toString()); } } private JavaScriptNode enterBinaryExpressionNode(BinaryNode binaryNode) { Expression lhsExpr = binaryNode.getLhs(); JavaScriptNode lhs = transform(lhsExpr); JavaScriptNode rhs = transform(binaryNode.getRhs()); JavaScriptNode result; if (lhsExpr instanceof IdentNode && ((IdentNode) lhsExpr).isPrivateInCheck()) { result = factory.createPrivateFieldIn(lhs, rhs); } else { result = factory.createBinary(context, tokenTypeToBinaryOperation(binaryNode.tokenType()), lhs, rhs); } return tagExpression(result, binaryNode); } private JavaScriptNode enterBinaryTransformNode(BinaryNode binaryNode) { JavaScriptNode assignedValue = transform(binaryNode.getAssignmentSource()); return tagExpression(transformCompoundAssignment(binaryNode, binaryNode.getAssignmentDest(), assignedValue, tokenTypeToBinaryOperation(binaryNode.tokenType()), false, false), binaryNode); } private JavaScriptNode enterBinaryAssignNode(BinaryNode binaryNode) { Expression assignmentDest = binaryNode.getAssignmentDest(); JavaScriptNode assignedValue = transform(binaryNode.getAssignmentSource()); JavaScriptNode assignment = transformAssignment(binaryNode, assignmentDest, assignedValue, binaryNode.isTokenType(TokenType.ASSIGN_INIT)); assert assignedValue != null && (assignedValue.hasTag(StandardTags.ExpressionTag.class) || !assignedValue.isInstrumentable()) : "ExpressionTag expected but not found for: " + assignedValue; return tagExpression(assignment, binaryNode); } private static BinaryOperation tokenTypeToBinaryOperation(TokenType tokenType) { switch (tokenType) { case ASSIGN_ADD: case ADD: return BinaryOperation.ADD; case ASSIGN_SUB: case SUB: return BinaryOperation.SUBTRACT; case ASSIGN_MUL: case MUL: return BinaryOperation.MULTIPLY; case ASSIGN_EXP: case EXP: return BinaryOperation.EXPONENTIATE; case ASSIGN_DIV: case DIV: return BinaryOperation.DIVIDE; case ASSIGN_MOD: case MOD: return BinaryOperation.MODULO; case ASSIGN_BIT_AND: case BIT_AND: return BinaryOperation.BITWISE_AND; case ASSIGN_BIT_OR: case BIT_OR: return BinaryOperation.BITWISE_OR; case ASSIGN_BIT_XOR: case BIT_XOR: return BinaryOperation.BITWISE_XOR; case ASSIGN_SHL: case SHL: return BinaryOperation.BITWISE_LEFT_SHIFT; case ASSIGN_SAR: case SAR: return BinaryOperation.BITWISE_RIGHT_SHIFT; case ASSIGN_SHR: case SHR: return BinaryOperation.BITWISE_UNSIGNED_RIGHT_SHIFT; case EQ: return BinaryOperation.EQUAL; case EQ_STRICT: return BinaryOperation.IDENTICAL; case GE: return BinaryOperation.GREATER_OR_EQUAL; case GT: return BinaryOperation.GREATER; case LE: return BinaryOperation.LESS_OR_EQUAL; case LT: return BinaryOperation.LESS; case NE: return BinaryOperation.NOT_EQUAL; case NE_STRICT: return BinaryOperation.NOT_IDENTICAL; case ASSIGN_AND: case AND: return BinaryOperation.LOGICAL_AND; case ASSIGN_OR: case OR: return BinaryOperation.LOGICAL_OR; case ASSIGN_NULLCOAL: case NULLISHCOALESC: return BinaryOperation.NULLISH_COALESCING; case INSTANCEOF: return BinaryOperation.INSTANCEOF; case IN: return BinaryOperation.IN; case COMMARIGHT: return BinaryOperation.DUAL; default: throw new UnsupportedOperationException(tokenType.toString()); } } private JavaScriptNode transformAssignment(Expression assignmentExpression, Expression lhsExpression, JavaScriptNode assignedValue, boolean initializationAssignment) { return transformAssignmentImpl(assignmentExpression, lhsExpression, assignedValue, initializationAssignment, null, false, false); } private JavaScriptNode transformCompoundAssignment(Expression assignmentExpression, Expression lhsExpression, JavaScriptNode assignedValue, BinaryOperation binaryOp, boolean returnOldValue, boolean convertLHSToNumeric) { return transformAssignmentImpl(assignmentExpression, lhsExpression, assignedValue, false, binaryOp, returnOldValue, convertLHSToNumeric); } @SuppressWarnings("fallthrough") private JavaScriptNode transformAssignmentImpl(Expression assignmentExpression, Expression lhsExpression, JavaScriptNode assignedValue, boolean initializationAssignment, BinaryOperation binaryOp, boolean returnOldValue, boolean convertLHSToNumeric) { JavaScriptNode assignedNode; switch (lhsExpression.tokenType()) { // Checkstyle: stop default: // ident with other token type // Checkstyle: resume if (!(lhsExpression instanceof IdentNode)) { throw Errors.unsupported("unsupported assignment to token type: " + lhsExpression.tokenType().toString() + " " + lhsExpression.toString()); } // fall through case IDENT: assignedNode = transformAssignmentIdent((IdentNode) lhsExpression, assignedValue, binaryOp, returnOldValue, convertLHSToNumeric, initializationAssignment); break; case LBRACKET: // target[element] assignedNode = transformIndexAssignment((IndexNode) lhsExpression, assignedValue, binaryOp, returnOldValue, convertLHSToNumeric); break; case PERIOD: // target.property assignedNode = transformPropertyAssignment((AccessNode) lhsExpression, assignedValue, binaryOp, returnOldValue, convertLHSToNumeric); break; case ARRAY: assert binaryOp == null; assignedNode = transformDestructuringArrayAssignment(lhsExpression, assignedValue, initializationAssignment); break; case LBRACE: assert binaryOp == null; assignedNode = transformDestructuringObjectAssignment(lhsExpression, assignedValue, initializationAssignment); break; } if (returnOldValue && assignedNode instanceof DualNode) { ensureHasSourceSection(((DualNode) assignedNode).getLeft(), assignmentExpression); } return tagExpression(assignedNode, assignmentExpression); } private static boolean isLogicalOp(BinaryOperation op) { return op == BinaryOperation.LOGICAL_AND || op == BinaryOperation.LOGICAL_OR || op == BinaryOperation.NULLISH_COALESCING; } private JavaScriptNode transformAssignmentIdent(IdentNode identNode, JavaScriptNode assignedValue, BinaryOperation binaryOp, boolean returnOldValue, boolean convertLHSToNumeric, boolean initializationAssignment) { JavaScriptNode rhs = assignedValue; TruffleString ident = identNode.getNameTS(); VarRef scopeVar = findScopeVarCheckTDZ(ident, initializationAssignment); // if scopeVar is const, the assignment will never succeed and is only there to perform // the temporal dead zone check and throw a ReferenceError instead of a TypeError if (!initializationAssignment && scopeVar.isConst()) { rhs = checkMutableBinding(rhs, scopeVar.getName()); } if (binaryOp == null) { return scopeVar.createWriteNode(rhs); } else { if (isLogicalOp(binaryOp)) { assert !convertLHSToNumeric && !returnOldValue; JavaScriptNode readNode = tagExpression(scopeVar.createReadNode(), identNode); JavaScriptNode writeNode = scopeVar.createWriteNode(rhs); return factory.createBinary(context, binaryOp, readNode, writeNode); } else { // e.g.: lhs *= rhs => lhs = lhs * rhs // If lhs is a side-effecting getter that deletes lhs, we must not throw a // ReferenceError at the lhs assignment since the lhs reference is already resolved. // We also need to ensure that HasBinding is idempotent or evaluated at most once. Pair, UnaryOperator> pair = scopeVar.createCompoundAssignNode(); JavaScriptNode readNode = tagExpression(pair.getFirst().get(), identNode); if (convertLHSToNumeric) { readNode = factory.createToNumericOperand(readNode); } VarRef prevValueTemp = null; if (returnOldValue) { prevValueTemp = environment.createTempVar(); readNode = prevValueTemp.createWriteNode(readNode); } JavaScriptNode binOpNode = tagExpression(factory.createBinary(context, binaryOp, readNode, rhs), identNode); JavaScriptNode writeNode = pair.getSecond().apply(binOpNode); if (returnOldValue) { return factory.createDual(context, writeNode, prevValueTemp.createReadNode()); } else { return writeNode; } } } } /** * If this is an attempt to change the value of an immutable binding, throw a runtime TypeError. */ private JavaScriptNode checkMutableBinding(JavaScriptNode rhsNode, Object identifier) { // evaluate rhs and throw TypeError return factory.createWriteConstantVariable(rhsNode, true, identifier); } private JavaScriptNode transformPropertyAssignment(AccessNode accessNode, JavaScriptNode assignedValue, BinaryOperation binaryOp, boolean returnOldValue, boolean convertToNumeric) { JavaScriptNode assignedNode; JavaScriptNode target = transform(accessNode.getBase()); if (binaryOp == null) { assignedNode = createWriteProperty(accessNode, target, assignedValue); } else { JavaScriptNode target1; JavaScriptNode target2; if (target instanceof RepeatableNode) { target1 = target; target2 = factory.copy(target); } else { VarRef targetTemp = environment.createTempVar(); target1 = targetTemp.createWriteNode(target); target2 = targetTemp.createReadNode(); } if (isLogicalOp(binaryOp)) { assert !convertToNumeric && !returnOldValue; JavaScriptNode readNode = tagExpression(createReadProperty(accessNode, target1), accessNode); JavaScriptNode writeNode = createWriteProperty(accessNode, target2, assignedValue); assignedNode = factory.createBinary(context, binaryOp, readNode, writeNode); } else { VarRef prevValueTemp = null; JavaScriptNode readNode = tagExpression(createReadProperty(accessNode, target2), accessNode); if (convertToNumeric) { readNode = factory.createToNumericOperand(readNode); } if (returnOldValue) { prevValueTemp = environment.createTempVar(); readNode = prevValueTemp.createWriteNode(readNode); } JavaScriptNode binOpNode = tagExpression(factory.createBinary(context, binaryOp, readNode, assignedValue), accessNode); JavaScriptNode writeNode = createWriteProperty(accessNode, target1, binOpNode); if (returnOldValue) { assignedNode = factory.createDual(context, writeNode, prevValueTemp.createReadNode()); } else { assignedNode = writeNode; } } } return assignedNode; } private JavaScriptNode transformIndexAssignment(IndexNode indexNode, JavaScriptNode assignedValue, BinaryOperation binaryOp, boolean returnOldValue, boolean convertToNumeric) { JavaScriptNode assignedNode; JavaScriptNode target = transform(indexNode.getBase()); JavaScriptNode elem = transform(indexNode.getIndex()); if (binaryOp == null) { assignedNode = factory.createWriteElementNode(target, elem, assignedValue, context, environment.isStrictMode()); } else { // Evaluation order: // 1. target = GetValue(baseReference) // 2. key = GetValue(propertyNameReference) // 3. RequireObjectCoercible(target); safely repeatable // 4. key = ToPropertyKey(key); only once // 5. lhs = target[key]; // 6. result = lhs op rhs; // 7. target[key] = result // Index must be ToPropertyKey-converted only once, save it in temp var VarRef keyTemp = environment.createTempVar(); JavaScriptNode readIndex = keyTemp.createReadNode(); JSWriteFrameSlotNode writeIndex = (JSWriteFrameSlotNode) keyTemp.createWriteNode(null); JavaScriptNode target1; JavaScriptNode target2; if ((elem instanceof JSConstantNode && target instanceof RepeatableNode) || (target instanceof SuperPropertyReferenceNode)) { // Cannot be used for any indexNode and any target RepeatableNode // because there is an invocation of indexNode in between targets target1 = target; target2 = factory.copy(target); } else { VarRef targetTemp = environment.createTempVar(); target1 = targetTemp.createWriteNode(target); target2 = targetTemp.createReadNode(); } if (isLogicalOp(binaryOp)) { assert !convertToNumeric && !returnOldValue; JavaScriptNode readNode = tagExpression(factory.createReadElementNode(context, target1, keyTemp.createWriteNode(elem)), indexNode); JavaScriptNode writeNode = factory.createCompoundWriteElementNode(target2, readIndex, assignedValue, null, context, environment.isStrictMode()); assignedNode = factory.createBinary(context, binaryOp, readNode, writeNode); } else { JavaScriptNode readNode = tagExpression(factory.createReadElementNode(context, target2, readIndex), indexNode); if (convertToNumeric) { readNode = factory.createToNumericOperand(readNode); } VarRef prevValueTemp = null; if (returnOldValue) { prevValueTemp = environment.createTempVar(); readNode = prevValueTemp.createWriteNode(readNode); } JavaScriptNode binOpNode = tagExpression(factory.createBinary(context, binaryOp, readNode, assignedValue), indexNode); JavaScriptNode writeNode = factory.createCompoundWriteElementNode(target1, elem, binOpNode, writeIndex, context, environment.isStrictMode()); if (returnOldValue) { assignedNode = factory.createDual(context, writeNode, prevValueTemp.createReadNode()); } else { assignedNode = writeNode; } } } return assignedNode; } private JavaScriptNode transformDestructuringArrayAssignment(Expression lhsExpression, JavaScriptNode assignedValue, boolean initializationAssignment) { LiteralNode.ArrayLiteralNode arrayLiteralNode = (LiteralNode.ArrayLiteralNode) lhsExpression; List elementExpressions = arrayLiteralNode.getElementExpressions(); JavaScriptNode[] initElements = javaScriptNodeArray(elementExpressions.size()); VarRef iteratorTempVar = environment.createTempVar(); VarRef valueTempVar = environment.createTempVar(); JavaScriptNode initValue = valueTempVar.createWriteNode(assignedValue); // By default, we use the hint to track the type of iterator. JavaScriptNode getIterator = factory.createGetIterator(initValue); JavaScriptNode initIteratorTempVar = iteratorTempVar.createWriteNode(getIterator); for (int i = 0; i < elementExpressions.size(); i++) { Expression element = elementExpressions.get(i); Expression lhsExpr; Expression init = null; if (element instanceof IdentNode) { lhsExpr = element; } else if (element instanceof BinaryNode) { assert element.isTokenType(TokenType.ASSIGN) || element.isTokenType(TokenType.ASSIGN_INIT); lhsExpr = ((BinaryNode) element).getLhs(); init = ((BinaryNode) element).getRhs(); } else { lhsExpr = element; } JavaScriptNode iteratorGetNextValueNode = factory.createIteratorGetNextValue(context, iteratorTempVar.createReadNode(), factory.createConstantUndefined(), true, element != null); JavaScriptNode iteratorIsDoneNode = factory.createIteratorIsDone(iteratorTempVar.createReadNode()); JavaScriptNode rhsNode = factory.createIf(iteratorIsDoneNode, factory.createConstantUndefined(), iteratorGetNextValueNode); if (init != null) { rhsNode = factory.createNotUndefinedOr(rhsNode, transform(init)); } if (lhsExpr != null && lhsExpr.isTokenType(TokenType.SPREAD_ARRAY)) { rhsNode = factory.createIteratorToArray(context, iteratorTempVar.createReadNode()); lhsExpr = ((UnaryNode) lhsExpr).getExpression(); } if (lhsExpr != null) { initElements[i] = transformAssignment(lhsExpr, lhsExpr, rhsNode, initializationAssignment); } else { initElements[i] = rhsNode; } } JavaScriptNode closeIfNotDone = factory.createIteratorCloseIfNotDone(context, createBlock(initElements), iteratorTempVar.createReadNode()); return factory.createExprBlock(initIteratorTempVar, closeIfNotDone, valueTempVar.createReadNode()); } private JavaScriptNode transformDestructuringObjectAssignment(Expression lhsExpression, JavaScriptNode assignedValue, boolean initializationAssignment) { ObjectNode objectLiteralNode = (ObjectNode) lhsExpression; List propertyExpressions = objectLiteralNode.getElements(); if (propertyExpressions.isEmpty()) { return factory.createRequireObjectCoercible(assignedValue); } int numberOfProperties = propertyExpressions.size(); boolean hasRest = propertyExpressions.get(numberOfProperties - 1).isRest(); boolean requireObjectCoercible = hasRest && numberOfProperties == 1; JavaScriptNode[] initElements = javaScriptNodeArray(numberOfProperties); JavaScriptNode[] excludedKeys = hasRest ? javaScriptNodeArray(numberOfProperties - 1) : null; VarRef valueTempVar = environment.createTempVar(); JavaScriptNode initValueTempVar = valueTempVar.createWriteNode(requireObjectCoercible ? factory.createRequireObjectCoercible(assignedValue) : assignedValue); for (int i = 0; i < numberOfProperties; i++) { PropertyNode property = propertyExpressions.get(i); Expression lhsExpr; Expression init = null; if (property.getValue() instanceof BinaryNode) { assert property.getValue().isTokenType(TokenType.ASSIGN) || property.getValue().isTokenType(TokenType.ASSIGN_INIT); lhsExpr = ((BinaryNode) property.getValue()).getLhs(); init = ((BinaryNode) property.getValue()).getRhs(); } else if (property.isRest()) { assert hasRest; lhsExpr = ((UnaryNode) property.getKey()).getExpression(); } else { lhsExpr = property.getValue(); } JavaScriptNode rhsNode; JavaScriptNode toPropertyKey = null; if (property.isRest()) { JavaScriptNode excludedItemsArray = excludedKeys.length == 0 ? null : factory.createArrayLiteral(context, excludedKeys); rhsNode = factory.createRestObject(context, valueTempVar.createReadNode(), excludedItemsArray); } else if (property.getKey() instanceof IdentNode && !property.isComputed()) { TruffleString keyName = property.getKeyNameTS(); if (hasRest) { excludedKeys[i] = factory.createConstantString(keyName); } rhsNode = factory.createReadProperty(context, valueTempVar.createReadNode(), keyName); } else { JavaScriptNode key = transform(property.getKey()); VarRef keyTempVar = environment.createTempVar(); if (hasRest) { excludedKeys[i] = keyTempVar.createReadNode(); } toPropertyKey = keyTempVar.createWriteNode(factory.createToPropertyKey(key)); rhsNode = factory.createReadElementNode(context, valueTempVar.createReadNode(), keyTempVar.createReadNode()); } if (init != null) { rhsNode = factory.createNotUndefinedOr(rhsNode, transform(init)); } JavaScriptNode initElement = transformAssignment(lhsExpr, lhsExpr, rhsNode, initializationAssignment); initElements[i] = (toPropertyKey == null) ? initElement : factory.createDual(context, toPropertyKey, initElement); } return factory.createExprBlock(initValueTempVar, createBlock(initElements), valueTempVar.createReadNode()); } @Override public JavaScriptNode enterAccessNode(AccessNode accessNode) { JavaScriptNode base = transform(accessNode.getBase()); if (accessNode.isOptionalChain()) { return createOptionalAccessNode(accessNode, base); } JavaScriptNode read = createReadProperty(accessNode, base); tagExpression(read, accessNode); return read; } private JavaScriptNode createOptionalAccessNode(AccessNode accessNode, JavaScriptNode base) { JavaScriptNode innerAccess = filterOptionalChainTarget(base, accessNode.isOptional()); JavaScriptNode read = createReadProperty(accessNode, innerAccess); tagExpression(read, accessNode); return factory.createOptionalChain(read); } private JavaScriptNode createReadProperty(AccessNode accessNode, JavaScriptNode base) { if (accessNode.isPrivate()) { return createPrivateFieldGet(accessNode, base); } else { return factory.createReadProperty(context, base, accessNode.getPropertyTS(), accessNode.isFunction()); } } private JavaScriptNode createWriteProperty(AccessNode accessNode, JavaScriptNode base, JavaScriptNode rhs) { if (accessNode.isPrivate()) { return createPrivateFieldSet(accessNode, base, rhs); } else { return factory.createWriteProperty(base, accessNode.getPropertyTS(), rhs, context, environment.isStrictMode()); } } private JavaScriptNode createPrivateFieldGet(AccessNode accessNode, JavaScriptNode base) { VarRef privateNameVar = environment.findLocalVar(accessNode.getPrivateNameTS()); JavaScriptNode privateName = privateNameVar.createReadNode(); return factory.createPrivateFieldGet(context, insertPrivateBrandCheck(base, privateNameVar), privateName); } private JavaScriptNode createPrivateFieldSet(AccessNode accessNode, JavaScriptNode base, JavaScriptNode rhs) { VarRef privateNameVar = environment.findLocalVar(accessNode.getPrivateNameTS()); JavaScriptNode privateName = privateNameVar.createReadNode(); return factory.createPrivateFieldSet(context, insertPrivateBrandCheck(base, privateNameVar), privateName, rhs); } private JavaScriptNode insertPrivateBrandCheck(JavaScriptNode base, VarRef privateNameVar) { JSFrameSlot frameSlot = privateNameVar.getFrameSlot(); if (JSFrameUtil.needsPrivateBrandCheck(frameSlot)) { JavaScriptNode brand = getPrivateBrandNode(frameSlot, privateNameVar); return factory.createPrivateBrandCheck(base, brand); } else { return base; } } private JSFrameSlot getConstructorFrameSlotForVariable(VarRef privateNameVar) { int frameLevel = ((AbstractFrameVarRef) privateNameVar).getFrameLevel(); int scopeLevel = ((AbstractFrameVarRef) privateNameVar).getScopeLevel(); Environment memberEnv = environment.getParentAt(frameLevel, scopeLevel); return memberEnv.findBlockFrameSlot(ClassNode.PRIVATE_CONSTRUCTOR_BINDING_NAME); } private JavaScriptNode getPrivateBrandNode(JSFrameSlot frameSlot, VarRef privateNameVar) { int frameLevel = ((AbstractFrameVarRef) privateNameVar).getFrameLevel(); int scopeLevel = ((AbstractFrameVarRef) privateNameVar).getScopeLevel(); Environment memberEnv = environment.getParentAt(frameLevel, scopeLevel); JSFrameSlot constructorSlot = memberEnv.findBlockFrameSlot(ClassNode.PRIVATE_CONSTRUCTOR_BINDING_NAME); JavaScriptNode constructor = environment.createLocal(constructorSlot, frameLevel, scopeLevel); if (JSFrameUtil.isPrivateNameStatic(frameSlot)) { return constructor; } else { return factory.createGetPrivateBrand(context, constructor); } } @Override public JavaScriptNode enterIndexNode(IndexNode indexNode) { JavaScriptNode base = transform(indexNode.getBase()); JavaScriptNode index = transform(indexNode.getIndex()); if (indexNode.isOptionalChain()) { return createOptionalIndexNode(indexNode, base, index); } return tagExpression(factory.createReadElementNode(context, base, index), indexNode); } private JavaScriptNode createOptionalIndexNode(IndexNode indexNode, JavaScriptNode base, JavaScriptNode index) { JavaScriptNode read = factory.createReadElementNode(context, filterOptionalChainTarget(base, indexNode.isOptional()), index); tagExpression(read, indexNode); return factory.createOptionalChain(read); } @Override public JavaScriptNode enterObjectNode(ObjectNode objectNode) { ArrayList members = transformPropertyDefinitionList(objectNode.getElements(), false, null); return tagExpression(factory.createObjectLiteral(context, members), objectNode); } private ArrayList transformPropertyDefinitionList(List properties, boolean isClass, Symbol classNameSymbol) { ArrayList members = new ArrayList<>(properties.size()); for (int i = 0; i < properties.size(); i++) { PropertyNode property = properties.get(i); assert !property.isCoverInitializedName(); final ObjectLiteralMemberNode member; if (property.getValue() != null || (isClass && ((ClassElement) property).isClassFieldOrAutoAccessor())) { member = enterObjectPropertyNode(property, isClass, classNameSymbol); } else if (property.isRest()) { assert !isClass; JavaScriptNode from = transform(((UnaryNode) property.getKey()).getExpression()); member = factory.createSpreadObjectMember(property.isStatic(), from); } else { member = enterObjectAccessorNode(property, isClass); } members.add(member); } return members; } private DecoratorListEvaluationNode[] transformClassElementsDecorators(List elements) { DecoratorListEvaluationNode[] decoratedElements = new DecoratorListEvaluationNode[elements.size()]; int i = 0; for (ClassElement element : elements) { JavaScriptNode[] decorators = null; if (element.getDecorators() != null) { List d = element.getDecorators(); decorators = new JavaScriptNode[d.size()]; for (int j = 0; j < d.size(); j++) { decorators[j] = transform(d.get(j)); } } decoratedElements[i++] = decorators != null ? factory.createDecoratorListEvaluation(decorators) : null; } return decoratedElements; } private ObjectLiteralMemberNode enterObjectAccessorNode(PropertyNode property, boolean isClass) { assert property.getGetter() != null || property.getSetter() != null; JavaScriptNode getter = getAccessor(property.getGetter()); JavaScriptNode setter = getAccessor(property.getSetter()); boolean enumerable = !isClass; if (property.isComputed()) { JavaScriptNode key = transform(property.getKey()); JavaScriptNode keyWrapper = factory.createToPropertyKey(key); return factory.createComputedAccessorMember(keyWrapper, property.isStatic(), enumerable, getter, setter); } else if (property.isPrivate()) { VarRef privateVar = environment.findLocalVar(property.getPrivateNameTS()); JSWriteFrameSlotNode writePrivateNode = (JSWriteFrameSlotNode) privateVar.createWriteNode(null); JSFrameSlot constructorSlot = getConstructorFrameSlotForVariable(privateVar); return factory.createPrivateAccessorMember(property.isStatic(), getter, setter, writePrivateNode, constructorSlot.getIndex()); } else { return factory.createAccessorMember(property.getKeyNameTS(), property.isStatic(), enumerable, getter, setter); } } private JavaScriptNode getAccessor(FunctionNode accessorFunction) { if (accessorFunction == null) { return null; } JavaScriptNode function = transform(accessorFunction); if (accessorFunction.needsSuper()) { assert accessorFunction.isMethod(); function = factory.createMakeMethod(context, function); } return function; } private JavaScriptNode transformPropertyValue(Expression propertyValue, Symbol classNameSymbol) { if (propertyValue == null) { // class field without an initializer return factory.createConstantUndefined(); } // TDZ: class name symbol cannot be used as a key but may be used as a value. VarRef classNameVarRef = null; if (classNameSymbol != null) { classNameVarRef = findScopeVar(classNameSymbol.getNameTS(), true); assert classNameVarRef.isFrameVar() : classNameSymbol; assert !classNameVarRef.hasBeenDeclared() : classNameSymbol; classNameVarRef.setHasBeenDeclared(true); } JavaScriptNode value = transform(propertyValue); if (classNameSymbol != null) { classNameVarRef.setHasBeenDeclared(false); } if (propertyValue instanceof FunctionNode && ((FunctionNode) propertyValue).needsSuper()) { assert ((FunctionNode) propertyValue).isMethod(); value = factory.createMakeMethod(context, value); } return value; } private ObjectLiteralMemberNode enterObjectPropertyNode(PropertyNode property, boolean isClass, Symbol classNameSymbol) { JavaScriptNode value = transformPropertyValue(property.getValue(), classNameSymbol); boolean enumerable = !isClass || property.isClassField(); if (isClass && property.isPrivate()) { VarRef privateVar = environment.findLocalVar(property.getPrivateNameTS()); if (((ClassElement) property).isAutoAccessor()) { JSWriteFrameSlotNode writePrivateAccessor = (JSWriteFrameSlotNode) privateVar.createWriteNode(null); JavaScriptNode fieldStorageKey = factory.createNewPrivateName(property.getPrivateNameTS()); JSFrameSlot constructorSlot = getConstructorFrameSlotForVariable(privateVar); return factory.createPrivateAutoAccessorMember(property.isStatic(), value, writePrivateAccessor, fieldStorageKey, constructorSlot.getIndex()); } else if (property.isClassField()) { JSWriteFrameSlotNode writePrivateNode = (JSWriteFrameSlotNode) privateVar.createWriteNode(factory.createNewPrivateName(property.getPrivateNameTS())); return factory.createPrivateFieldMember(privateVar.createReadNode(), property.isStatic(), value, writePrivateNode); } else { JSWriteFrameSlotNode writePrivateNode = (JSWriteFrameSlotNode) privateVar.createWriteNode(null); JSFrameSlot constructorSlot = getConstructorFrameSlotForVariable(privateVar); return factory.createPrivateMethodMember(property.getPrivateNameTS(), property.isStatic(), value, writePrivateNode, constructorSlot.getIndex()); } } else if (isClass && ((ClassElement) property).isAutoAccessor()) { if (property.isComputed()) { JavaScriptNode computedKey = transform(property.getKey()); return factory.createComputedAutoAccessor(computedKey, property.isStatic(), enumerable, value); } else { return factory.createAutoAccessor(property.getKeyNameTS(), property.isStatic(), enumerable, value); } } else if (property.isComputed()) { JavaScriptNode computedKey = transform(property.getKey()); return factory.createComputedDataMember(computedKey, property.isStatic(), enumerable, value, property.isClassField(), property.isAnonymousFunctionDefinition()); } else if (!isClass && property.isProto()) { return factory.createProtoMember(property.getKeyNameTS(), property.isStatic(), value); } else if (isClass && property.isClassStaticBlock()) { return factory.createStaticBlockMember(value); } else { assert property.getKey() != null; return factory.createDataMember(property.getKeyNameTS(), property.isStatic(), enumerable, value, property.isClassField()); } } @Override public JavaScriptNode enterTryNode(TryNode tryNode) { JavaScriptNode tryBlock = transform(tryNode.getBody()); JavaScriptNode result = tryBlock; if (!tryNode.getCatchBlocks().isEmpty()) { for (Block catchParamBlock : tryNode.getCatchBlocks()) { CatchNode catchClause = (CatchNode) catchParamBlock.getLastStatement(); Expression catchParameter = catchClause.getException(); Block catchBody = catchClause.getBody(); Expression pattern = catchClause.getDestructuringPattern(); // manually enter the catch block; this hack is only necessary to be able to // evaluate the condition in the same block try (EnvironmentCloseable catchParamEnv = enterBlockEnvironment(catchParamBlock)) { lc.push(catchParamBlock); try { // mark variables as declared for (Statement statement : catchParamBlock.getStatements().subList(0, catchParamBlock.getStatementCount() - 1)) { assert statement instanceof VarNode; JavaScriptNode empty = transform(statement); assert empty instanceof EmptyNode; } JavaScriptNode writeErrorVar = null; JavaScriptNode destructuring = null; if (catchParameter != null) { TruffleString errorVarName = ((IdentNode) catchParameter).getNameTS(); VarRef errorVar = environment.findLocalVar(errorVarName); writeErrorVar = errorVar.createWriteNode(null); if (pattern != null) { // exception is being destructured destructuring = transformAssignment(pattern, pattern, errorVar.createReadNode(), true); destructuring = wrapTemporalDeadZoneInit(catchParamBlock.getScope(), destructuring); } } JavaScriptNode catchBlock = transform(catchBody); JavaScriptNode conditionExpression; if (catchClause.getExceptionCondition() != null) { conditionExpression = transform(catchClause.getExceptionCondition()); } else { conditionExpression = null; // equivalent to constant true } BlockScopeNode blockScope = (BlockScopeNode) catchParamEnv.wrapBlockScope(null); result = factory.createTryCatch(context, result, catchBlock, writeErrorVar, blockScope, destructuring, conditionExpression); ensureHasSourceSection(result, tryNode); } finally { lc.pop(catchParamBlock); } } } } if (tryNode.getFinallyBody() != null) { JavaScriptNode finallyBlock = transform(tryNode.getFinallyBody()); result = factory.createTryFinally(result, wrapSaveAndRestoreCompletionValue(wrapClearCompletionValue(finallyBlock))); } result = wrapClearAndGetCompletionValue(result); return result; } @Override public JavaScriptNode enterThrowNode(com.oracle.js.parser.ir.ThrowNode throwNode) { return tagStatement(factory.createThrow(context, transform(throwNode.getExpression())), throwNode); } @Override public JavaScriptNode enterSwitchNode(com.oracle.js.parser.ir.SwitchNode switchNode) { Block switchBlock = lc.getCurrentBlock(); assert switchBlock.isSwitchBlock(); InternalSlotId switchVarName = makeUniqueTempVarNameForStatement("switch", switchNode.getLineNumber()); environment.declareLocalVar(switchVarName); JavaScriptNode switchExpression = transform(switchNode.getExpression()); boolean isSwitchTypeofString = isSwitchTypeofStringConstant(switchNode, switchExpression); if (isSwitchTypeofString) { switchExpression = ((TypeOfNode) switchExpression).getOperand(); } VarRef switchVar = environment.findInternalSlot(switchVarName); JavaScriptNode writeSwitchNode = switchVar.createWriteNode(switchExpression); JavaScriptNode switchBody; try (JumpTargetCloseable target = currentFunction().pushBreakTarget(null)) { // when this switch does not use fall-through, rewrite it to an if-else-cascade if (JSConfig.OptimizeNoFallthroughSwitch && isNoFallthroughSwitch(switchNode)) { switchBody = ifElseFromSwitch(switchNode, switchVar, isSwitchTypeofString); } else { switchBody = defaultSwitchNode(switchNode, switchVar, isSwitchTypeofString); } tagStatement(switchBody, switchNode); switchBody = wrapClearAndGetCompletionValue(target.wrapBreakTargetNode(switchBody)); } return createBlock(writeSwitchNode, switchBody); } private JavaScriptNode defaultSwitchNode(com.oracle.js.parser.ir.SwitchNode switchNode, VarRef switchVar, boolean isSwitchTypeofString) { List cases = switchNode.getCases(); int size = cases.size() + (switchNode.hasDefaultCase() ? 0 : 1); int[] jumptable = new int[size]; int defaultpos = -1; List declarationList = new ArrayList<>(); List statementList = new ArrayList<>(); List caseExprList = new ArrayList<>(); int lastNonEmptyIndex = -1; for (CaseNode switchCase : cases) { if (switchCase.getTest() != null) { jumptable[caseExprList.size()] = statementList.size(); JavaScriptNode readSwitchVarNode = switchVar.createReadNode(); caseExprList.add(createSwitchCaseExpr(isSwitchTypeofString, switchCase, readSwitchVarNode)); } else { defaultpos = statementList.size(); } if (!switchCase.getStatements().isEmpty()) { List statements = switchCase.getStatements(); for (int i = 0; i < statements.size(); i++) { Statement statement = statements.get(i); if (statement instanceof VarNode) { VarNode varNode = (VarNode) statement; if (varNode.isHoistableDeclaration()) { declarationList.add(transform(statement)); if (annexBBlockToFunctionTransfer(varNode)) { statement = varNode.setFlag(VarNode.IS_ANNEXB_BLOCK_TO_FUNCTION_TRANSFER); } else { continue; } } } JavaScriptNode statementNode = transform(statement); if (currentFunction().returnsLastStatementResult()) { if (!statement.isCompletionValueNeverEmpty()) { if (lastNonEmptyIndex >= 0) { statementList.set(lastNonEmptyIndex, wrapSetCompletionValue(statementList.get(lastNonEmptyIndex))); lastNonEmptyIndex = -1; } } else { lastNonEmptyIndex = statementList.size(); } } statementList.add(statementNode); } } } if (currentFunction().returnsLastStatementResult() && lastNonEmptyIndex >= 0) { statementList.set(lastNonEmptyIndex, wrapSetCompletionValue(statementList.get(lastNonEmptyIndex))); } // set default case position to the end jumptable[jumptable.length - 1] = defaultpos != -1 ? defaultpos : statementList.size(); return factory.createSwitch(declarationList.toArray(EMPTY_NODE_ARRAY), caseExprList.toArray(EMPTY_NODE_ARRAY), jumptable, statementList.toArray(EMPTY_NODE_ARRAY)); } private JavaScriptNode createSwitchCaseExpr(boolean isSwitchTypeofString, CaseNode switchCase, JavaScriptNode readSwitchVarNode) { tagHiddenExpression(readSwitchVarNode); if (isSwitchTypeofString) { TruffleString typeString = (TruffleString) ((LiteralNode) switchCase.getTest()).getValue(); return tagExpression(factory.createTypeofIdentical(readSwitchVarNode, typeString), switchCase); } else { return tagExpression(factory.createBinary(context, BinaryOperation.IDENTICAL, readSwitchVarNode, transform(switchCase.getTest())), switchCase); } } /** * When a SwitchNode does not have any fall-through behavior, it can be transferred into an * if-else-cascade. */ private JavaScriptNode ifElseFromSwitch(com.oracle.js.parser.ir.SwitchNode switchNode, VarRef switchVar, boolean isSwitchTypeofString) { assert isNoFallthroughSwitch(switchNode); List cases = switchNode.getCases(); CaseNode defaultCase = switchNode.getDefaultCase(); JavaScriptNode curNode = null; if (defaultCase != null) { curNode = dropTerminalDirectBreakStatement(transformStatements(defaultCase.getStatements(), false, false)); ensureHasSourceSection(curNode, defaultCase); } boolean defaultCascade = false; boolean lastCase = true; for (int i = cases.size() - 1; i >= 0; i--) { CaseNode caseNode = cases.get(i); if (caseNode.getTest() == null) { // start of the cascade with the default case // (the default case is the last case in the cascade) defaultCascade = true; } else { JavaScriptNode readSwitchVarNode = switchVar.createReadNode(); JavaScriptNode test = createSwitchCaseExpr(isSwitchTypeofString, caseNode, readSwitchVarNode); if (caseNode.getStatements().isEmpty() && !lastCase) { // fall through to the previous case if (defaultCascade) { // fall through to default case, execute test only for potential side effect if (isPotentiallySideEffecting(test)) { test = factory.createIf(test, null, null); ensureHasSourceSection(test, caseNode); curNode = curNode == null ? discardResult(test) : createBlock(test, curNode); } } else { assert curNode instanceof com.oracle.truffle.js.nodes.control.IfNode; // if (condition) => if (test || condition) com.oracle.truffle.js.nodes.control.IfNode prevIfNode = (com.oracle.truffle.js.nodes.control.IfNode) curNode; curNode = factory.copyIfWithCondition(prevIfNode, factory.createLogicalOr(test, prevIfNode.getCondition())); } } else { // start of a cascade (without the default case) JavaScriptNode pass = dropTerminalDirectBreakStatement(transformStatements(caseNode.getStatements(), false, false)); ensureHasSourceSection(pass, caseNode); curNode = factory.createIf(test, pass, curNode); defaultCascade = false; } ensureHasSourceSection(curNode, caseNode.getTest()); } lastCase = false; } return curNode == null ? factory.createEmpty() : curNode; } static boolean isPotentiallySideEffecting(JavaScriptNode test) { if (test instanceof JSReadFrameSlotNode) { return ((JSReadFrameSlotNode) test).hasTemporalDeadZone(); } return !(test instanceof RepeatableNode); } private JavaScriptNode dropTerminalDirectBreakStatement(JavaScriptNode pass) { if (pass instanceof SequenceNode) { JavaScriptNode[] statements = ((SequenceNode) pass).getStatements(); if (statements.length > 0 && isDirectBreakStatement(statements[statements.length - 1])) { return createBlock(Arrays.copyOfRange(statements, 0, statements.length - 1)); } } return pass; } private static boolean isDirectBreakStatement(JavaScriptNode statement) { return statement instanceof BreakNode && ((BreakNode) statement).isDirectBreak(); } private static boolean isNoFallthroughSwitch(com.oracle.js.parser.ir.SwitchNode switchNode) { List cases = switchNode.getCases(); for (int i = 0; i < cases.size() - 1; i++) { // all but the last need to be checked CaseNode caseNode = cases.get(i); List statements = caseNode.getStatements(); if (statements.isEmpty()) { // fall-through supported if case body is empty if (caseNode.getTest() == null) { // default case fallthrough to other cases is not supported currently; // i.e., default case must either appear last or end in abrupt completion return false; } continue; } // Case clause must end in a break, continue, or terminal statement (return, throw). Statement lastStatement = statements.get(statements.size() - 1); if (!lastStatement.hasTerminalFlags()) { return false; } } return true; } /** * Identifies whether a SwitchNode matches the pattern where the expression is a typeof() and * the cases are all string constants. */ private static boolean isSwitchTypeofStringConstant(com.oracle.js.parser.ir.SwitchNode switchNode, JavaScriptNode switchExpression) { if (!(switchExpression instanceof TypeOfNode)) { return false; } for (CaseNode switchCase : switchNode.getCases()) { com.oracle.js.parser.ir.Node test = switchCase.getTest(); if (!(test == null || (test instanceof LiteralNode && Strings.isTString(((LiteralNode) test).getValue())))) { return false; } } return true; } private JavaScriptNode discardResult(JavaScriptNode test) { if (currentFunction().returnsLastStatementResult()) { return factory.createVoidBlock(test); } return test; } @Override public JavaScriptNode enterEmptyNode(com.oracle.js.parser.ir.EmptyNode emptyNode) { return factory.createEmpty(); } @Override public JavaScriptNode enterWithNode(com.oracle.js.parser.ir.WithNode withNode) { if (context.isOptionDisableWith()) { throw Errors.createSyntaxError("with statement is disabled."); } // Store with object in synthetic block environment that can be captured by closures/eval. FunctionNode function = lc.getCurrentFunction(); Environment withParentEnv = (function.hasClosures() || function.hasEval()) ? new BlockEnvironment(environment, factory, context) : environment; try (EnvironmentCloseable withParent = new EnvironmentCloseable(withParentEnv)) { JavaScriptNode withExpression = transform(withNode.getExpression()); JavaScriptNode toObject = factory.createToObjectForWithStatement(context, withExpression); InternalSlotId withVarName = makeUniqueTempVarNameForStatement("with", withNode.getLineNumber()); environment.declareInternalSlot(withVarName); JavaScriptNode writeWith = environment.findInternalSlot(withVarName).createWriteNode(toObject); JavaScriptNode withStatement; try (EnvironmentCloseable withEnv = enterWithEnvironment(withVarName)) { JavaScriptNode withBody = transform(withNode.getBody()); withStatement = tagStatement(factory.createWith(writeWith, wrapClearAndGetCompletionValue(withBody)), withNode); } return withParent.wrapBlockScope(withStatement); } } private EnvironmentCloseable enterWithEnvironment(Object withVarName) { return new EnvironmentCloseable(new WithEnvironment(environment, factory, context, withVarName)); } @Override public JavaScriptNode enterTemplateLiteralNode(TemplateLiteralNode templateLiteralNode) { JavaScriptNode result = null; if (templateLiteralNode instanceof TemplateLiteralNode.TaggedTemplateLiteralNode) { TemplateLiteralNode.TaggedTemplateLiteralNode tagged = (TemplateLiteralNode.TaggedTemplateLiteralNode) templateLiteralNode; result = factory.createTemplateObject(context, createArrayLiteral(tagged.getRawStrings()), createArrayLiteral(tagged.getCookedStrings())); } else { List expressions = ((TemplateLiteralNode.UntaggedTemplateLiteralNode) templateLiteralNode).getExpressions(); for (int i = 0; i < expressions.size(); i++) { JavaScriptNode expr = transform(expressions.get(i)); assert i % 2 != 0 || expr instanceof JSConstantNode : expr; if (i % 2 != 0) { expr = factory.createToString(expr); } result = result == null ? expr : factory.createBinary(context, BinaryOperation.ADD, result, expr); } } return tagExpression(result, templateLiteralNode); } @Override public JavaScriptNode enterDebuggerNode(DebuggerNode debuggerNode) { return tagStatement(factory.createDebugger(), debuggerNode); } @Override public JavaScriptNode enterExpressionStatement(ExpressionStatement expressionStatement) { JavaScriptNode expression = transform(expressionStatement.getExpression()); return tagStatement(expression, expressionStatement); } @Override public JavaScriptNode enterJoinPredecessorExpression(JoinPredecessorExpression expr) { return tagExpression(transform(expr.getExpression()), expr); } @Override public JavaScriptNode enterClassNode(ClassNode classNode) { Scope classHeadScope = classNode.getClassHeadScope(); Scope classBodyScope = classNode.getScope(); boolean needsScope = classHeadScope.hasDeclarations() || classBodyScope.hasDeclarations(); try (EnvironmentCloseable blockEnv = new EnvironmentCloseable(needsScope ? newClassEnvironment(classHeadScope) : environment)) { TruffleString className = null; Symbol classNameSymbol = null; IdentNode ident = classNode.getIdent(); if (ident != null) { className = ident.getNameTS(); classNameSymbol = classHeadScope.getExistingSymbol(ident.getName()); } JavaScriptNode classHeritage = transform(classNode.getClassHeritage()); JavaScriptNode classDefinition; try (EnvironmentCloseable privateEnv = new EnvironmentCloseable(newPrivateEnvironment(classBodyScope))) { JavaScriptNode classFunction = transform(classNode.getConstructor().getValue()); List members = transformPropertyDefinitionList(classNode.getClassElements(), true, classNameSymbol); DecoratorListEvaluationNode[] decoratedElementNodes = classNode.hasClassElementDecorators() ? transformClassElementsDecorators(classNode.getClassElements()) : null; JavaScriptNode[] classDecorators = transformClassDecorators(classNode.getDecorators()); JSWriteFrameSlotNode writeClassBinding = className == null ? null : (JSWriteFrameSlotNode) findScopeVar(className, true).createWriteNode(null); // internal constructor binding used for private brand checks. JSWriteFrameSlotNode writeInternalConstructorBrand = classNode.hasPrivateMethods() ? (JSWriteFrameSlotNode) environment.findLocalVar(ClassNode.PRIVATE_CONSTRUCTOR_BINDING_NAME).createWriteNode(null) : null; classDefinition = factory.createClassDefinition(context, (JSFunctionExpressionNode) classFunction, classHeritage, members.toArray(ObjectLiteralMemberNode.EMPTY), writeClassBinding, writeInternalConstructorBrand, classDecorators, decoratedElementNodes, className, classNode.getInstanceElementCount(), classNode.getStaticElementCount(), classNode.hasPrivateInstanceMethods(), classNode.hasInstanceFieldsOrAccessors(), environment.getCurrentBlockScopeSlot()); classDefinition = privateEnv.wrapBlockScope(classDefinition); } if (ident != null) { classDefinition = wrapTemporalDeadZoneInit(classHeadScope, classDefinition); } return tagExpression(blockEnv.wrapBlockScope(classDefinition), classNode); } } private JavaScriptNode[] transformClassDecorators(List decorators) { if (decorators == null) { return EMPTY_NODE_ARRAY; } JavaScriptNode[] transformedDecorators = new JavaScriptNode[decorators.size()]; int i = 0; for (Expression e : decorators) { transformedDecorators[i++] = transform(e); } return transformedDecorators; } private Environment newClassEnvironment(Scope scope) { assert scope.isClassHeadScope(); BlockEnvironment classEnv = new BlockEnvironment(environment, factory, context, scope); classEnv.addFrameSlotsFromSymbols(scope.getSymbols()); return classEnv; } private Environment newPrivateEnvironment(Scope scope) { assert scope.isClassBodyScope(); if (scope.hasPrivateNames()) { PrivateEnvironment privateEnv = new PrivateEnvironment(environment, factory, context, scope); privateEnv.addFrameSlotsFromSymbols(scope.getSymbols()); return privateEnv; } return environment; } @Override public JavaScriptNode enterBlockExpression(BlockExpression blockExpression) { return tagExpression(transform(blockExpression.getBlock()), blockExpression); } @Override public JavaScriptNode enterParameterNode(ParameterNode paramNode) { final FunctionEnvironment currentFunction = currentFunction(); final JavaScriptNode valueNode; if (paramNode.isRestParameter()) { valueNode = factory.createAccessRestArgument(context, currentFunction.getLeadingArgumentCount() + paramNode.getIndex()); } else { valueNode = factory.createAccessArgument(currentFunction.getLeadingArgumentCount() + paramNode.getIndex()); } return tagExpression(tagHiddenExpression(valueNode), paramNode); } // --- @Override protected JavaScriptNode enterDefault(com.oracle.js.parser.ir.Node node) { throw shouldNotReachHere(node); } private static AssertionError shouldNotReachHere(com.oracle.js.parser.ir.Node node) { throw new AssertionError(String.format("should not reach here. %s(%s)", node.getClass().getSimpleName(), node)); } // --- private SourceSection createSourceSection(FunctionNode functionNode) { int start = functionNode.getStartWithoutParens() - prologLength; int finish = functionNode.getFinishWithoutParens() - prologLength; int length = sourceLength; if (finish <= 0 || length <= start) { return source.createUnavailableSection(); } else { start = Math.max(0, start); finish = Math.min(length, finish); return source.createSection(start, finish - start); } } private JavaScriptNode ensureHasSourceSection(JavaScriptNode resultNode, com.oracle.js.parser.ir.Node parseNode) { if (!resultNode.hasSourceSection()) { assignSourceSection(resultNode, parseNode); if (resultNode instanceof VarWrapperNode) { ensureHasSourceSection(((VarWrapperNode) resultNode).getDelegateNode(), parseNode); } } return resultNode; } private void assignSourceSection(JavaScriptNode resultNode, com.oracle.js.parser.ir.Node parseNode) { int start = parseNode.getStart() - prologLength; int finish = parseNode.getFinish() - prologLength; int length = sourceLength; if (finish <= 0 || length <= start) { resultNode.setSourceSection(source.createUnavailableSection()); } else { start = Math.max(0, start); finish = Math.min(length, finish); resultNode.setSourceSection(source, start, finish - start); } } private InternalSlotId makeUniqueTempVarNameForStatement(String prefix, int lineNumber) { InternalSlotId name = factory.createInternalSlotId(prefix, lineNumber); assert !environment.hasLocalVar(name); return name; } private final class EnvironmentCloseable implements AutoCloseable { private final Environment prevEnv = environment; private final Environment newEnv; private int wrappedInBlockScopeNode; EnvironmentCloseable(Environment newEnv) { this.newEnv = newEnv; environment = newEnv; } public JavaScriptNode wrapBlockScope(JavaScriptNode block) { if (prevEnv != newEnv) { wrappedInBlockScopeNode++; if (newEnv instanceof BlockEnvironment) { BlockEnvironment blockEnv = (BlockEnvironment) newEnv; if (blockEnv.hasScopeFrame()) { return factory.createBlockScope(block, blockEnv.getCurrentBlockScopeSlot(), blockEnv.getBlockFrameDescriptor().toFrameDescriptor(), blockEnv.getParentSlot(), blockEnv.isFunctionBlock(), blockEnv.capturesFunctionFrame(), blockEnv.isGeneratorFunctionBlock(), blockEnv.getScopeLevel() > 1, blockEnv.getStart(), blockEnv.getEnd()); } else if (blockEnv.getStart() < blockEnv.getEnd() && !blockEnv.isFunctionBlock()) { return factory.createVirtualBlockScope(block, blockEnv.getStart(), blockEnv.getEnd()); } } } return block; } @Override public void close() { assert environment == newEnv; assert newEnv == prevEnv || !(newEnv instanceof BlockEnvironment) || wrappedInBlockScopeNode == 1; environment = prevEnv; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy