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

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

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;

import com.oracle.js.parser.ir.Scope;
import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.strings.TruffleString;
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.access.ScopeFrameNode;
import com.oracle.truffle.js.nodes.control.BreakTarget;
import com.oracle.truffle.js.nodes.control.ContinueTarget;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSFrameUtil;
import com.oracle.truffle.js.runtime.Strings;

public final class FunctionEnvironment extends Environment {
    private static final TruffleString RETURN_SLOT_IDENTIFIER = Strings.constant("");
    static final TruffleString ARGUMENTS_SLOT_IDENTIFIER = Strings.constant("");
    static final TruffleString THIS_SLOT_IDENTIFIER = JSFrameUtil.THIS_SLOT_ID;
    static final TruffleString SUPER_SLOT_IDENTIFIER = Strings.constant("");
    static final TruffleString NEW_TARGET_SLOT_IDENTIFIER = Strings.constant("");
    static final TruffleString YIELD_VALUE_SLOT_IDENTIFIER = Strings.constant("");
    static final TruffleString ASYNC_CONTEXT_SLOT_IDENTIFIER = Strings.constant("");
    static final TruffleString ASYNC_RESULT_SLOT_IDENTIFIER = Strings.constant("");
    private static final TruffleString YIELD_RESULT_SLOT_IDENTIFIER = Strings.constant("");
    public static final TruffleString DYNAMIC_SCOPE_IDENTIFIER = ScopeFrameNode.EVAL_SCOPE_IDENTIFIER;

    private final FunctionEnvironment parentFunction;
    private final JSFrameDescriptor frameDescriptor;
    private final boolean isStrictMode;
    private final Scope scope;

    private JSFrameSlot returnSlot;
    private JSFrameSlot blockScopeSlot;

    private TruffleString functionName = Strings.EMPTY_STRING;
    private TruffleString internalFunctionName = Strings.EMPTY_STRING;
    private boolean isNamedExpression;
    private boolean needsParentFrame;
    private boolean frozen;

    private int breakNodeCount;
    private int continueNodeCount;
    private boolean hasReturn;
    private boolean hasYield;
    private boolean hasAwait;

    private boolean hasMappedParameters;
    private List jumpTargetStack;
    private boolean directArgumentsAccess;

    private final boolean isGlobal;
    private final boolean isEval;
    private final boolean isDirectEval;
    private final boolean isArrowFunction;
    private final boolean isGeneratorFunction;
    private final boolean isDerivedConstructor;
    private final boolean isAsyncFunction;
    // Synthetic arguments are declared externally, e.g. when parsing with arguments.
    private final boolean hasSyntheticArguments;
    private boolean hasRestParameter;
    private boolean simpleParameterList = true;
    private boolean isDynamicallyScoped;
    private boolean needsNewTarget;
    private final boolean inDirectEval;

    public FunctionEnvironment(Environment parent, NodeFactory factory, JSContext context, Scope scope,
                    boolean isStrictMode, boolean isEval, boolean isDirectEval, boolean isArrowFunction, boolean isGeneratorFunction, boolean isDerivedConstructor, boolean isAsyncFunction,
                    boolean isGlobal, boolean hasSyntheticArguments) {
        super(parent, factory, context);
        this.isDirectEval = isDirectEval;
        this.isAsyncFunction = isAsyncFunction;
        this.isStrictMode = isStrictMode;
        this.isEval = isEval;
        this.isArrowFunction = isArrowFunction;
        this.isGeneratorFunction = isGeneratorFunction;
        this.isDerivedConstructor = isDerivedConstructor;
        this.isGlobal = isGlobal;
        this.hasSyntheticArguments = hasSyntheticArguments;
        this.parentFunction = parent == null ? null : parent.function();

        this.frameDescriptor = factory.createFunctionFrameDescriptor();
        this.scope = scope;
        this.inDirectEval = isDirectEval || (parent != null && parent.function() != null && parent.function().inDirectEval());
    }

    @Override
    public JSFrameSlot declareLocalVar(Object name) {
        assert !isFrozen() : name;
        return getFunctionFrameDescriptor().findOrAddFrameSlot(name, FrameSlotKind.Illegal);
    }

    @Override
    public JSFrameSlot declareInternalSlot(Object name) {
        assert JSFrameSlot.isAllowedIdentifierType(name) : name;
        return getFunctionFrameDescriptor().findOrAddFrameSlot(name);
    }

    public JSFrameSlot getReturnSlot() {
        if (returnSlot == null) {
            returnSlot = declareLocalVar(RETURN_SLOT_IDENTIFIER);
        }
        return returnSlot;
    }

    public boolean hasReturnSlot() {
        return returnSlot != null;
    }

    public JSFrameSlot getAsyncResultSlot() {
        return declareLocalVar(ASYNC_RESULT_SLOT_IDENTIFIER);
    }

    public JSFrameSlot getAsyncContextSlot() {
        return declareLocalVar(ASYNC_CONTEXT_SLOT_IDENTIFIER);
    }

    public JSFrameSlot getYieldResultSlot() {
        return declareLocalVar(YIELD_RESULT_SLOT_IDENTIFIER);
    }

    JSFrameSlot getOrCreateBlockScopeSlot() {
        if (blockScopeSlot == null) {
            blockScopeSlot = declareLocalVar(ScopeFrameNode.BLOCK_SCOPE_IDENTIFIER);
        }
        return blockScopeSlot;
    }

    /**
     * Returns the function's block scope frame slot, or null if it has not been created yet.
     *
     * Note: Always use {@link #getCurrentBlockScopeSlot()} to get the block scope slot at the
     * current location, which may be null also after the slot has been allocated.
     */
    JSFrameSlot getBlockScopeSlot() {
        return blockScopeSlot;
    }

    @Override
    public JSFrameSlot getCurrentBlockScopeSlot() {
        return null;
    }

    public boolean isEval() {
        return isEval;
    }

    public boolean isArrowFunction() {
        return isArrowFunction;
    }

    public boolean isGeneratorFunction() {
        return isGeneratorFunction;
    }

    @Override
    public JSFrameDescriptor getBlockFrameDescriptor() {
        return getFunctionFrameDescriptor();
    }

    @Override
    public JSFrameSlot findBlockFrameSlot(Object name) {
        return null;
    }

    @Override
    public JSFrameSlot findFunctionFrameSlot(Object name) {
        return getFunctionFrameDescriptor().findFrameSlot(name);
    }

    private  T pushJumpTarget(T target) {
        if (jumpTargetStack == null) {
            jumpTargetStack = new ArrayList<>(4);
        }
        jumpTargetStack.add(target);
        return target;
    }

    private void popJumpTarget(BreakTarget target) {
        assert jumpTargetStack != null && jumpTargetStack.get(jumpTargetStack.size() - 1) == target;
        jumpTargetStack.remove(jumpTargetStack.size() - 1);
    }

    public JumpTargetCloseable pushContinueTarget(String label) {
        ContinueTarget target = ContinueTarget.forLoop(label, -1);
        pushJumpTarget(target);
        return new JumpTargetCloseable<>(target);
    }

    public JumpTargetCloseable pushBreakTarget(String label) {
        BreakTarget target = label == null ? BreakTarget.forSwitch() : BreakTarget.forLabel(label, -1);
        pushJumpTarget(target);
        return new JumpTargetCloseable<>(target);
    }

    public BreakTarget findBreakTarget(Object label) {
        breakNodeCount++;
        return findJumpTarget(label, BreakTarget.class, true);
    }

    public ContinueTarget findContinueTarget(Object label) {
        continueNodeCount++;
        return findJumpTarget(label, ContinueTarget.class, false);
    }

    private  T findJumpTarget(Object label, Class targetClass, boolean direct) {
        T applicableTarget = null;
        for (ListIterator iterator = jumpTargetStack.listIterator(jumpTargetStack.size()); iterator.hasPrevious();) {
            BreakTarget target = iterator.previous();
            if (direct && label == null) {
                // break without a label is consumed by a switch or an iteration statement,
                // not by other labelled statements
                if ((BreakTarget.forSwitch() == target) || (target instanceof ContinueTarget)) {
                    return targetClass.cast(target);
                }
            } else if (direct || label == null) {
                // ignore label or label directly on target
                if (label == null || label.equals(target.getLabel())) {
                    if (targetClass.isInstance(target)) {
                        return targetClass.cast(target);
                    }
                }
            } else {
                assert !direct;
                // label is indirectly associated with last applicable target
                if (targetClass.isInstance(target)) {
                    applicableTarget = targetClass.cast(target);
                }
                if (label.equals(target.getLabel())) {
                    assert applicableTarget != null : "Illegal or duplicate label"; // SyntaxError
                    return applicableTarget;
                }
            }
        }
        throw new NoSuchElementException("jump target not found");
    }

    public boolean hasReturn() {
        return hasReturn;
    }

    public void addReturn() {
        hasReturn = true;
    }

    public boolean hasAwait() {
        return hasAwait;
    }

    public void addAwait() {
        hasAwait = true;
    }

    public boolean hasYield() {
        return hasYield;
    }

    public void addYield() {
        hasYield = true;
    }

    public void setDirectArgumentsAccess(boolean directArgumentsAccess) {
        this.directArgumentsAccess = directArgumentsAccess;
    }

    public boolean isDirectArgumentsAccess() {
        return directArgumentsAccess;
    }

    public void addMappedParameter(JSFrameSlot slot, int index) {
        assert slot != null && JSFrameUtil.isParam(slot) : slot;
        assert slot.getMappedParameterIndex() == -1;
        this.hasMappedParameters = true;
        slot.setMappedParameterIndex(index);
    }

    public TruffleString getFunctionName() {
        return functionName;
    }

    public void setFunctionName(TruffleString functionName) {
        this.functionName = functionName;
    }

    public TruffleString getInternalFunctionName() {
        return internalFunctionName;
    }

    public void setInternalFunctionName(TruffleString internalFunctionName) {
        this.internalFunctionName = internalFunctionName;
    }

    public TruffleString getExplicitOrInternalFunctionName() {
        if (functionName.isEmpty() && internalFunctionName != null) {
            return internalFunctionName;
        }
        return functionName;
    }

    public void setNamedFunctionExpression(boolean isNamedExpression) {
        this.isNamedExpression = isNamedExpression;
    }

    protected boolean isNamedFunctionExpression() {
        return isNamedExpression;
    }

    public boolean needsParentFrame() {
        return needsParentFrame;
    }

    public void setNeedsParentFrame(boolean needsParentFrame) {
        if (frozen && needsParentFrame != this.needsParentFrame) {
            throw errorFrozenEnv();
        }
        this.needsParentFrame = needsParentFrame;
    }

    private static RuntimeException errorFrozenEnv() {
        return new IllegalStateException("frozen function environment cannot be mutated");
    }

    public void freeze() {
        if (this.frozen) {
            return;
        }
        this.frozen = true;
    }

    public boolean isFrozen() {
        return frozen;
    }

    public boolean isDeepFrozen() {
        return isFrozen() && (getParentFunction() == null || getParentFunction().isDeepFrozen());
    }

    public boolean hasMappedParameters() {
        return hasMappedParameters;
    }

    @Override
    public JSFrameDescriptor getFunctionFrameDescriptor() {
        return frameDescriptor;
    }

    @Override
    public boolean isStrictMode() {
        return this.isStrictMode;
    }

    public FunctionEnvironment getParentFunction() {
        return parentFunction;
    }

    public FunctionEnvironment getNonArrowParentFunction() {
        if (isArrowFunction() || isDirectEval()) {
            return getParentFunction().getNonArrowParentFunction();
        }
        return this;
    }

    @Override
    public int getScopeLevel() {
        return 0;
    }

    public boolean isGlobal() {
        return isGlobal;
    }

    public boolean hasSyntheticArguments() {
        return this.hasSyntheticArguments;
    }

    public boolean returnsLastStatementResult() {
        return isGlobal() || isDirectEval() || hasSyntheticArguments();
    }

    public void setIsDynamicallyScoped(boolean isDynamicallyScoped) {
        this.isDynamicallyScoped = isDynamicallyScoped;
    }

    /**
     * Function is dynamically scope because it's non-strict and has a direct eval call.
     */
    @Override
    public boolean isDynamicallyScoped() {
        return isDynamicallyScoped;
    }

    @Override
    public boolean isDynamicScopeContext() {
        return isDynamicallyScoped() || isCallerContextEval() || super.isDynamicScopeContext();
    }

    @Override
    public Environment getVariableEnvironment() {
        if (isCallerContextEval()) {
            return getParentFunction().getVariableEnvironment();
        }
        return this;
    }

    public class JumpTargetCloseable implements AutoCloseable {

        private final T target;
        private final int prevBreakCount = breakNodeCount;
        private final int prevContinueCount = continueNodeCount;

        protected JumpTargetCloseable(T target) {
            this.target = target;
        }

        public T getTarget() {
            return target;
        }

        @Override
        public void close() {
            popJumpTarget(target);
        }

        private boolean hasBreak() {
            return breakNodeCount != prevBreakCount;
        }

        private boolean hasContinue() {
            return continueNodeCount != prevContinueCount;
        }

        public JavaScriptNode wrapContinueTargetNode(JavaScriptNode child) {
            boolean hasContinue = hasContinue();
            return hasContinue ? factory.createContinueTarget(child, (ContinueTarget) target) : child;
        }

        public JavaScriptNode wrapBreakTargetNode(JavaScriptNode child) {
            assert target.getLabel() == null;
            boolean hasBreak = hasBreak();
            return hasBreak ? factory.createDirectBreakTarget(child) : child;
        }

        public JavaScriptNode wrapLabelBreakTargetNode(JavaScriptNode child) {
            assert target.getLabel() != null;
            boolean hasBreak = hasBreak();
            return hasBreak ? factory.createLabel(child, target) : child;
        }
    }

    public boolean isDirectEval() {
        return isDirectEval;
    }

    public boolean isIndirectEval() {
        return isEval() && !isDirectEval();
    }

    public boolean isCallerContextEval() {
        return isDirectEval() && !isStrictMode() && !isGlobal();
    }

    public boolean inDirectEval() {
        return inDirectEval;
    }

    public void setNeedsNewTarget(boolean needsNewTarget) {
        this.needsNewTarget = needsNewTarget;
    }

    public void setRestParameter(boolean restParameter) {
        this.hasRestParameter = restParameter;
    }

    public boolean hasRestParameter() {
        return hasRestParameter;
    }

    public void setSimpleParameterList(boolean simpleParameterList) {
        this.simpleParameterList = simpleParameterList;
    }

    public boolean hasSimpleParameterList() {
        return simpleParameterList;
    }

    public int getLeadingArgumentCount() {
        return needsNewTarget ? 1 : 0;
    }

    public boolean isDerivedConstructor() {
        return isDerivedConstructor;
    }

    /**
     * Returns the number of function levels to skip to reach the function with the [[ThisValue]].
     * Loosely resembles GetThisEnvironment(), but we need to consider eval functions, too.
     */
    public int getThisFunctionLevel() {
        int level = 0;
        for (FunctionEnvironment currentFunction = this; currentFunction.isArrowFunction() || currentFunction.isDirectEval(); currentFunction = currentFunction.getParentFunction(), level++) {
            currentFunction.setNeedsParentFrame(true);
        }
        return level;
    }

    public boolean isAsyncFunction() {
        return isAsyncFunction;
    }

    public boolean isAsyncGeneratorFunction() {
        return isAsyncFunction && isGeneratorFunction;
    }

    @Override
    public Scope getScope() {
        return scope;
    }

    public boolean isModule() {
        return getScope().isModuleScope();
    }

    public boolean isScriptOrModule() {
        return getParentFunction() == null;
    }

    public void prepareForDirectEval() {
        FunctionEnvironment func = this;
        for (; func.getParentFunction() != null; func = func.getParentFunction()) {
            func.setNeedsParentFrame(true);
        }
    }

    @Override
    protected String toStringImpl(Map state) {
        int currentFrameLevel = state.getOrDefault("frameLevel", 0);
        state.put("frameLevel", currentFrameLevel + 1);
        state.put("scopeLevel", 0);
        return "Function(" + currentFrameLevel + ")" +
                        " size=" + getFunctionFrameDescriptor().getSize() + " " + joinElements(getFunctionFrameDescriptor().getIdentifiers());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy