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

com.oracle.js.parser.ir.Scope Maven / Gradle / Ivy

There is a newer version: 4.15.102
Show newest version
/*
 * Copyright (c) 2019, 2020, 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.js.parser.ir;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

import org.graalvm.collections.EconomicMap;

/**
 * Represents a binding scope (corresponds to LexicalEnvironment or VariableEnvironment).
 */
public final class Scope {
    private final Scope parent;
    private final int type;
    private final int flags;

    private static final int BLOCK_SCOPE = 1 << 0;
    private static final int FUNCTION_BODY_SCOPE = 1 << 1;
    private static final int FUNCTION_PARAMETER_SCOPE = 1 << 2;
    private static final int CATCH_PARAMETER_SCOPE = 1 << 3;
    private static final int GLOBAL_SCOPE = 1 << 4;
    private static final int MODULE_SCOPE = 1 << 5;
    private static final int FUNCTION_TOP_SCOPE = 1 << 6;
    private static final int SWITCH_BLOCK_SCOPE = 1 << 7;
    private static final int CLASS_SCOPE = 1 << 8;
    private static final int EVAL_SCOPE = 1 << 9;

    /** Scope is in a function context. {@code new.target} is available. */
    private static final int IN_FUNCTION = 1 << 16;
    /** Scope is in a method context. Super property accesses are allowed. */
    private static final int IN_METHOD = 1 << 17;
    /** Scope is in a derived class constructor. Super calls are allowed. */
    private static final int IN_DERIVED_CONSTRUCTOR = 1 << 18;
    /** Scope is in a class field initializer. 'arguments' is not allowed. */
    private static final int IS_CLASS_FIELD_INITIALIZER = 1 << 19;

    /** Symbol table - keys must be returned in the order they were put in. */
    protected final EconomicMap symbols;
    protected List> hoistedVarDeclarations;
    protected List> hoistableBlockFunctionDeclarations;

    private int blockScopedOrRedeclaredSymbols;
    private int declaredNames;
    private boolean closed;

    private Scope(Scope parent, int type, int flags) {
        this.parent = parent;
        this.type = type | (isFunctionTopScope(type, parent) ? FUNCTION_TOP_SCOPE : 0);
        this.symbols = EconomicMap.create();
        this.flags = flags;
    }

    private Scope(Scope parent, int type) {
        this(parent, type, parent == null ? 0 : parent.flags);
    }

    private static boolean isFunctionTopScope(int type, Scope parent) {
        return (type & FUNCTION_PARAMETER_SCOPE) != 0 || ((type & FUNCTION_BODY_SCOPE) != 0 && (parent == null || !parent.isFunctionParameterScope()));
    }

    private static int computeFlags(Scope parent, int functionFlags) {
        if ((functionFlags & FunctionNode.IS_ARROW) != 0) {
            // propagate flags from enclosing function scope.
            return parent.flags;
        } else {
            int flags = 0;
            flags |= IN_FUNCTION;
            flags |= ((functionFlags & FunctionNode.IS_METHOD) != 0) ? IN_METHOD : 0;
            flags |= ((functionFlags & FunctionNode.IS_DERIVED_CONSTRUCTOR) != 0) ? IN_DERIVED_CONSTRUCTOR : 0;
            flags |= ((functionFlags & FunctionNode.IS_CLASS_FIELD_INITIALIZER) != 0) ? IS_CLASS_FIELD_INITIALIZER : 0;
            return flags;
        }
    }

    public static Scope createGlobal() {
        return new Scope(null, FUNCTION_BODY_SCOPE | GLOBAL_SCOPE);
    }

    public static Scope createModule() {
        return new Scope(null, FUNCTION_BODY_SCOPE | MODULE_SCOPE);
    }

    public static Scope createFunctionBody(Scope parent, int functionFlags) {
        return new Scope(parent, FUNCTION_BODY_SCOPE, computeFlags(parent, functionFlags));
    }

    public static Scope createBlock(Scope parent) {
        return new Scope(parent, BLOCK_SCOPE);
    }

    public static Scope createCatch(Scope parent) {
        return new Scope(parent, CATCH_PARAMETER_SCOPE);
    }

    public static Scope createParameter(Scope parent, int functionFlags) {
        return new Scope(parent, FUNCTION_PARAMETER_SCOPE, computeFlags(parent, functionFlags));
    }

    public static Scope createSwitchBlock(Scope parent) {
        return new Scope(parent, BLOCK_SCOPE | SWITCH_BLOCK_SCOPE);
    }

    public static Scope createClass(Scope parent) {
        return new Scope(parent, BLOCK_SCOPE | CLASS_SCOPE);
    }

    public static Scope createEval(Scope parent, boolean strict) {
        return new Scope(parent, EVAL_SCOPE | (strict ? FUNCTION_BODY_SCOPE : 0));
    }

    public Scope getParent() {
        return parent;
    }

    /**
     * Get all the symbols defined in this block, in definition order.
     *
     * @return symbol iterator
     */
    public Iterable getSymbols() {
        return symbols.getValues();
    }

    /**
     * Retrieves an existing symbol defined in the current block.
     *
     * @param name the name of the symbol
     * @return an existing symbol with the specified name defined in the current block, or null if
     *         this block doesn't define a symbol with this name.
     */
    public Symbol getExistingSymbol(final String name) {
        return symbols.get(name);
    }

    /**
     * Test if a symbol with this name is defined in the current block.
     *
     * @param name the name of the symbol
     */
    public boolean hasSymbol(final String name) {
        return symbols.containsKey(name);
    }

    /**
     * Get the number of symbols defined in this block.
     */
    public int getSymbolCount() {
        return symbols.size();
    }

    /**
     * Add or overwrite an existing symbol in the block
     */
    public Symbol putSymbol(final Symbol symbol) {
        assert !closed : "scope is closed";
        Symbol existing = symbols.put(symbol.getName(), symbol);
        if (existing != null) {
            assert (existing.getFlags() & Symbol.KINDMASK) == (symbol.getFlags() & Symbol.KINDMASK) : symbol;
            return existing;
        }
        if (symbol.isBlockScoped() || symbol.isVarRedeclaredHere()) {
            blockScopedOrRedeclaredSymbols++;
        }
        if (symbol.isBlockScoped() || (symbol.isVar() && !symbol.isParam())) {
            declaredNames++;
        }
        return null;
    }

    public boolean hasBlockScopedOrRedeclaredSymbols() {
        return blockScopedOrRedeclaredSymbols != 0;
    }

    public boolean hasDeclarations() {
        return declaredNames != 0;
    }

    /**
     * Returns true if the name is lexically declared in this scope or any of its enclosing scopes
     * within this function.
     *
     * @param varName the declared name
     * @param annexB if true, ignore catch parameters
     * @param includeParameters include parameter scope?
     */
    public boolean isLexicallyDeclaredName(final String varName, final boolean annexB, final boolean includeParameters) {
        for (Scope current = this; current != null; current = current.getParent()) {
            Symbol existingSymbol = current.getExistingSymbol(varName);
            if (existingSymbol != null && existingSymbol.isBlockScoped()) {
                if (existingSymbol.isCatchParameter() && annexB) {
                    continue; // B.3.5 VariableStatements in Catch Blocks
                }
                return true;
            }
            if (includeParameters ? current.isFunctionTopScope() : current.isFunctionBodyScope()) {
                break;
            }
        }
        return false;
    }

    /**
     * Returns a block scoped symbol in this scope or any of its enclosing scopes within this
     * function.
     *
     * @param varName the symbol name
     */
    public Symbol findBlockScopedSymbolInFunction(String varName) {
        for (Scope current = this; current != null; current = current.getParent()) {
            Symbol existingSymbol = current.getExistingSymbol(varName);
            if (existingSymbol != null) {
                if (existingSymbol.isBlockScoped()) {
                    return existingSymbol;
                } else {
                    // early exit
                    break;
                }
            }
            if (current.isFunctionTopScope()) {
                break;
            }
        }
        return null;
    }

    public void recordHoistedVarDeclaration(final VarNode varDecl, final Scope scope) {
        assert !varDecl.isBlockScoped();
        if (hoistedVarDeclarations == null) {
            hoistedVarDeclarations = new ArrayList<>();
        }
        hoistedVarDeclarations.add(new AbstractMap.SimpleImmutableEntry<>(varDecl, scope));
    }

    public VarNode verifyHoistedVarDeclarations() {
        if (!hasHoistedVarDeclarations()) {
            // nothing to do
            return null;
        }
        for (Map.Entry entry : hoistedVarDeclarations) {
            VarNode varDecl = entry.getKey();
            Scope declScope = entry.getValue();
            String varName = varDecl.getName().getName();
            for (Scope current = declScope; current != this; current = current.getParent()) {
                Symbol existing = current.getExistingSymbol(varName);
                if (existing != null && existing.isBlockScoped()) {
                    if (existing.isCatchParameter()) {
                        continue; // B.3.5 VariableStatements in Catch Blocks
                    }
                    // let the caller throw the error
                    return varDecl;
                }
            }
        }
        return null;
    }

    public boolean hasHoistedVarDeclarations() {
        return hoistedVarDeclarations != null;
    }

    public void recordHoistableBlockFunctionDeclaration(final VarNode functionDeclaration, final Scope scope) {
        assert functionDeclaration.isFunctionDeclaration() && functionDeclaration.isBlockScoped();
        if (hoistableBlockFunctionDeclarations == null) {
            hoistableBlockFunctionDeclarations = new ArrayList<>();
        }
        hoistableBlockFunctionDeclarations.add(new AbstractMap.SimpleImmutableEntry<>(functionDeclaration, scope));
    }

    public void declareHoistedBlockFunctionDeclarations() {
        if (hoistableBlockFunctionDeclarations == null) {
            // nothing to do
            return;
        }
        next: for (Map.Entry entry : hoistableBlockFunctionDeclarations) {
            VarNode functionDecl = entry.getKey();
            Scope functionDeclScope = entry.getValue();
            String varName = functionDecl.getName().getName();
            for (Scope current = functionDeclScope.getParent(); current != null; current = current.getParent()) {
                Symbol existing = current.getExistingSymbol(varName);
                if (existing != null && (existing.isBlockScoped() && !existing.isCatchParameter())) {
                    // lexical declaration found, do not hoist
                    continue next;
                }
                if (current.isFunctionBodyScope()) {
                    break;
                }
            }
            // declare var (if not already declared) and hoist the function declaration
            if (getExistingSymbol(varName) == null) {
                putSymbol(new Symbol(varName, Symbol.IS_VAR | (isGlobalScope() ? Symbol.IS_GLOBAL : 0)));
            }
            functionDeclScope.getExistingSymbol(varName).setHoistedBlockFunctionDeclaration();
        }
    }

    /**
     * Add a private bound identifier.
     *
     * @return true if the private name was added, false if it was already declared (duplicate name)
     */
    public boolean addPrivateName(String name, int symbolFlags) {
        assert isClassScope();
        // Register a declared private name.
        if (hasSymbol(name)) {
            assert getExistingSymbol(name).isPrivateName();
            return false;
        } else {
            putSymbol(new Symbol(name, Symbol.IS_CONST | Symbol.IS_PRIVATE_NAME | Symbol.HAS_BEEN_DECLARED | symbolFlags));
            return true;
        }
    }

    public boolean findPrivateName(String name) {
        for (Scope current = this; current != null; current = current.parent) {
            if (current.hasSymbol(name)) {
                return true;
            }
        }
        return false;
    }

    public boolean isBlockScope() {
        return (type & BLOCK_SCOPE) != 0;
    }

    public boolean isFunctionBodyScope() {
        return (type & FUNCTION_BODY_SCOPE) != 0;
    }

    public boolean isFunctionParameterScope() {
        return (type & FUNCTION_PARAMETER_SCOPE) != 0;
    }

    public boolean isCatchParameterScope() {
        return (type & CATCH_PARAMETER_SCOPE) != 0;
    }

    public boolean isGlobalScope() {
        return (type & GLOBAL_SCOPE) != 0;
    }

    public boolean isModuleScope() {
        return (type & MODULE_SCOPE) != 0;
    }

    public boolean isFunctionTopScope() {
        return (type & FUNCTION_TOP_SCOPE) != 0;
    }

    public boolean isSwitchBlockScope() {
        return (type & SWITCH_BLOCK_SCOPE) != 0;
    }

    public boolean isClassScope() {
        return (type & CLASS_SCOPE) != 0;
    }

    public boolean isEvalScope() {
        return (type & EVAL_SCOPE) != 0;
    }

    public boolean inFunction() {
        return (flags & IN_FUNCTION) != 0;
    }

    public boolean inMethod() {
        return (flags & IN_METHOD) != 0;
    }

    public boolean inDerivedConstructor() {
        return (flags & IN_DERIVED_CONSTRUCTOR) != 0;
    }

    public boolean inClassFieldInitializer() {
        return (flags & IS_CLASS_FIELD_INITIALIZER) != 0;
    }

    /**
     * Closes the scope for symbol registration.
     */
    public void close() {
        if (closed) {
            return;
        }
        if (hoistableBlockFunctionDeclarations != null) {
            declareHoistedBlockFunctionDeclarations();
        }
        closed = true;
    }

    @Override
    public String toString() {
        StringJoiner names = new StringJoiner(",", "(", ")");
        for (String name : symbols.getKeys()) {
            names.add(name);
        }
        return "[" + getScopeKindName() + "Scope" + names + (parent == null ? "" : ", " + parent + "") + "]";
    }

    private String getScopeKindName() {
        if (isGlobalScope()) {
            return "Global";
        } else if (isModuleScope()) {
            return "Module";
        } else if (isFunctionBodyScope()) {
            return "Var";
        } else if (isFunctionParameterScope()) {
            return "Param";
        } else if (isCatchParameterScope()) {
            return "Catch";
        } else if (isSwitchBlockScope()) {
            return "Switch";
        } else if (isClassScope()) {
            return "Class";
        } else if (isEvalScope()) {
            return "Eval";
        }
        return "";
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy