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

com.oracle.truffle.js.runtime.interop.ScopeVariables Maven / Gradle / Ivy

/*
 * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.oracle.truffle.js.runtime.interop;

import java.util.OptionalInt;

import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.object.DynamicObjectLibrary;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.nodes.FrameDescriptorProvider;
import com.oracle.truffle.js.nodes.JSFrameSlot;
import com.oracle.truffle.js.nodes.JavaScriptNode;
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.ScopeFrameNode;
import com.oracle.truffle.js.nodes.access.WriteNode;
import com.oracle.truffle.js.nodes.function.BlockScopeNode;
import com.oracle.truffle.js.nodes.module.ReadImportBindingNode;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSFrameUtil;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.Properties;
import com.oracle.truffle.js.runtime.Strings;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.Null;
import com.oracle.truffle.js.runtime.objects.Undefined;

@ExportLibrary(InteropLibrary.class)
public final class ScopeVariables implements TruffleObject {

    public static final TruffleString RECEIVER_MEMBER = Strings.THIS;
    static final int LIMIT = 4;

    final Frame frame;
    final boolean nodeEnter;
    /** BlockScopeNode or RootNode. */
    final Node blockOrRoot;
    final Frame functionFrame;
    private ScopeMembers members;

    /**
     * @param frame Block scope or function frame
     * @param nodeEnter True if we are entering the node
     * @param blockOrRoot BlockScopeNode or FunctionRootNode
     * @param functionFrame Optional function frame not accessible via parent chain
     */
    private ScopeVariables(Frame frame, boolean nodeEnter, Node blockOrRoot, Frame functionFrame) {
        assert isBlockScopeOrRootNode(blockOrRoot);
        this.frame = frame;
        this.nodeEnter = nodeEnter;
        this.blockOrRoot = blockOrRoot;
        this.functionFrame = functionFrame;
    }

    static boolean isBlockScopeOrRootNode(Node blockOrRoot) {
        return blockOrRoot instanceof BlockScopeNode || blockOrRoot instanceof RootNode;
    }

    public static ScopeVariables create(Frame frame, boolean nodeEnter, Node blockOrRoot, Frame functionFrame) {
        return new ScopeVariables(frame, nodeEnter, blockOrRoot, functionFrame);
    }

    /**
     * Assure that we fix the current node and enter flag.
     */
    @ExportMessage
    boolean accepts(@Cached(value = "this.blockOrRoot", adopt = false) Node cachedNode,
                    @Cached(value = "this.nodeEnter", neverDefault = false) boolean cachedNodeEnter) {
        return this.blockOrRoot == cachedNode && this.nodeEnter == cachedNodeEnter;
    }

    @ExportMessage
    @SuppressWarnings("static-method")
    boolean isScope() {
        return true;
    }

    @ExportMessage
    @SuppressWarnings("static-method")
    boolean hasLanguage() {
        return true;
    }

    @ExportMessage
    @SuppressWarnings("static-method")
    Class> getLanguage() {
        return JavaScriptLanguage.class;
    }

    @ExportMessage
    @TruffleBoundary
    boolean hasScopeParent() {
        if (blockOrRoot instanceof BlockScopeNode) {
            BlockScopeNode blockScopeNode = (BlockScopeNode) blockOrRoot;
            Node parentBlock;
            while ((parentBlock = JavaScriptNode.findBlockScopeNode(blockScopeNode.getParent())) != null) {
                if (frame == null) {
                    return true;
                }
                if (blockScopeNode instanceof BlockScopeNode.FrameBlockScopeNode) {
                    if (blockScopeNode.isFunctionBlock()) {
                        if (parentBlock instanceof BlockScopeNode) {
                            blockScopeNode = (BlockScopeNode) parentBlock;
                            continue;
                        } else {
                            break;
                        }
                    }
                }
                if (parentBlock instanceof BlockScopeNode) {
                    return true;
                } else if (parentBlock instanceof RootNode && functionFrame != null) {
                    return true;
                }
                break;
            }
        } else {
            assert blockOrRoot instanceof RootNode;
            if (frame != null) {
                // For closures, we don't have any outer block nodes, only the RootNode.
                if (ScopeFrameNode.isBlockScopeFrame(frame)) {
                    if (getParentFrame() != null) {
                        return true;
                    }
                }
            }
        }
        if (frame != null) {
            Frame parentFrame = JSFrameUtil.getParentFrame(frame);
            return parentFrame != null && parentFrame != JSFrameUtil.NULL_MATERIALIZED_FRAME;
        }
        return false;
    }

    @ExportMessage
    @TruffleBoundary
    Object getScopeParent() throws UnsupportedMessageException {
        if (blockOrRoot instanceof BlockScopeNode) {
            BlockScopeNode blockScopeNode = (BlockScopeNode) blockOrRoot;
            Frame enclosingFrame = frame;
            Node parentBlock;
            while ((parentBlock = JavaScriptNode.findBlockScopeNode(blockScopeNode.getParent())) != null) {
                if (frame == null) {
                    return new ScopeVariables(null, true, parentBlock, null);
                }

                if (blockScopeNode instanceof BlockScopeNode.FrameBlockScopeNode) {
                    enclosingFrame = getParentFrame();
                    if (blockScopeNode.isFunctionBlock()) {
                        if (parentBlock instanceof BlockScopeNode) {
                            blockScopeNode = (BlockScopeNode) parentBlock;
                            assert enclosingFrame != null;
                            continue;
                        } else {
                            break;
                        }
                    }
                }
                if (parentBlock instanceof BlockScopeNode) {
                    return new ScopeVariables(enclosingFrame, true, parentBlock, functionFrame);
                } else if (parentBlock instanceof RootNode && functionFrame != null) {
                    return new ScopeVariables(functionFrame, true, parentBlock, functionFrame);
                }
                break;
            }
        } else {
            assert blockOrRoot instanceof RootNode;
            if (frame != null) {
                // For closures, we don't have any outer block nodes, only the RootNode.
                if (ScopeFrameNode.isBlockScopeFrame(frame)) {
                    Frame parentBlockScope = getParentFrame();
                    if (parentBlockScope != null) {
                        return new ScopeVariables(parentBlockScope, true, blockOrRoot, null);
                    }
                }
            }
        }
        if (frame != null) {
            Frame parentFrame = JSFrameUtil.getParentFrame(frame);
            if (parentFrame != null && parentFrame != JSFrameUtil.NULL_MATERIALIZED_FRAME) {
                RootNode rootNode = ((RootCallTarget) JSFunction.getCallTarget(JSFrameUtil.getFunctionObject(parentFrame))).getRootNode();
                return new ScopeVariables(parentFrame, true, rootNode, null);
            }
        }
        throw UnsupportedMessageException.create();
    }

    @TruffleBoundary
    private Frame getParentFrame() {
        OptionalInt parentSlot = JSFrameUtil.findOptionalFrameSlotIndex(frame.getFrameDescriptor(), ScopeFrameNode.PARENT_SCOPE_IDENTIFIER);
        if (parentSlot.isPresent()) {
            Object parent = frame.getObject(parentSlot.getAsInt());
            if (parent instanceof Frame) {
                return (Frame) parent;
            }
        }
        return null;
    }

    @ExportMessage
    @SuppressWarnings("static-method")
    boolean hasMembers() {
        return true;
    }

    @ExportMessage
    @TruffleBoundary
    Object getMembers(@SuppressWarnings("unused") boolean includeInternal) {
        ScopeMembers m = this.members;
        if (m == null) {
            m = new ScopeMembers(frame, blockOrRoot, functionFrame);
            this.members = m;
        }
        return m;
    }

    @ExportMessage
    static final class IsMemberReadable {

        @Specialization(guards = {"cachedMember.equals(member)"}, limit = "LIMIT")
        static boolean doCached(ScopeVariables receiver, String member,
                        @Cached("member") @SuppressWarnings("unused") String cachedMember,
                        // We cache the member existence for fast-path access
                        @Cached("doGeneric(receiver, member)") boolean cachedResult) {
            assert cachedResult == doGeneric(receiver, member);
            return cachedResult;
        }

        @Specialization(replaces = "doCached")
        @TruffleBoundary
        static boolean doGeneric(ScopeVariables receiver, String member) {
            return hasSlot(member, receiver);
        }
    }

    @ExportMessage
    static final class IsMemberModifiable {

        @Specialization(guards = {"cachedMember.equals(member)"}, limit = "LIMIT")
        static boolean doCached(ScopeVariables receiver, String member,
                        @Cached("member") @SuppressWarnings("unused") String cachedMember,
                        // We cache the member existence for fast-path access
                        @Cached("doGeneric(receiver, member)") boolean cachedResult) {
            assert cachedResult == doGeneric(receiver, member);
            return cachedResult;
        }

        @Specialization(replaces = "doCached")
        @TruffleBoundary
        static boolean doGeneric(ScopeVariables receiver, String member) {
            ResolvedSlot slot = findSlot(member, receiver);
            return slot != null && slot.isModifiable();
        }
    }

    @ExportMessage
    static final class ReadMember {

        @Specialization(guards = {"cachedMember.equals(member)"}, limit = "LIMIT")
        static Object doCached(ScopeVariables receiver, @SuppressWarnings("unused") String member,
                        @Cached("member") String cachedMember,
                        // We cache the member's read node for fast-path access
                        @Cached(value = "findSlot(member, receiver)") ResolvedSlot resolvedSlot,
                        @Cached(value = "findReadNode(resolvedSlot)") JavaScriptNode readNode) throws UnknownIdentifierException {
            return doRead(receiver, cachedMember, readNode, resolvedSlot);
        }

        @Specialization(replaces = "doCached")
        @TruffleBoundary
        static Object doGeneric(ScopeVariables receiver, String member) throws UnknownIdentifierException {
            ResolvedSlot resolvedSlot = findSlot(member, receiver);
            JavaScriptNode readNode = findReadNode(resolvedSlot);
            return doRead(receiver, member, readNode, resolvedSlot);
        }

        private static Object doRead(ScopeVariables receiver, String member, JavaScriptNode readNode, ResolvedSlot resolvedSlot) throws UnknownIdentifierException {
            if (readNode == null) {
                throw UnknownIdentifierException.create(member);
            }
            Frame frame = resolvedSlot.isFunctionFrame() ? receiver.functionFrame : receiver.frame;
            if (frame == null) {
                return Undefined.instance;
            } else {
                return readNode.execute((VirtualFrame) frame);
            }
        }
    }

    @ExportMessage
    static final class WriteMember {

        @Specialization(guards = {"cachedMember.equals(member)"}, limit = "LIMIT")
        static void doCached(ScopeVariables receiver, @SuppressWarnings("unused") String member, Object value,
                        @Cached("member") String cachedMember,
                        // We cache the member's write node for fast-path access
                        @Cached(value = "findSlot(member, receiver)") ResolvedSlot resolvedSlot,
                        @Cached(value = "findWriteNode(resolvedSlot)") WriteNode writeNode) throws UnknownIdentifierException {
            doWrite(receiver, cachedMember, value, writeNode, resolvedSlot);
        }

        @Specialization(replaces = "doCached")
        @TruffleBoundary
        static void doGeneric(ScopeVariables receiver, String member, Object value) throws UnknownIdentifierException {
            ResolvedSlot resolvedSlot = findSlot(member, receiver);
            WriteNode writeNode = findWriteNode(resolvedSlot);
            doWrite(receiver, member, value, writeNode, resolvedSlot);
        }

        private static void doWrite(ScopeVariables receiver, String member, Object value, WriteNode writeNode, ResolvedSlot resolvedSlot) throws UnknownIdentifierException {
            if (writeNode == null) {
                throw UnknownIdentifierException.create(member);
            }
            Frame frame = resolvedSlot.isFunctionFrame() ? receiver.functionFrame : receiver.frame;
            if (frame == null) {
                throw UnknownIdentifierException.create(member);
            }
            writeNode.executeWrite((VirtualFrame) frame, value);
        }
    }

    @SuppressWarnings("static-method")
    @ExportMessage
    boolean isMemberInsertable(@SuppressWarnings("unused") String member) {
        return false;
    }

    @ExportMessage
    @TruffleBoundary
    boolean hasSourceLocation() {
        return blockOrRoot.getEncapsulatingSourceSection() != null;
    }

    @ExportMessage
    @TruffleBoundary
    SourceSection getSourceLocation() throws UnsupportedMessageException {
        Node sourceSectionProvider = blockOrRoot;
        if (sourceSectionProvider instanceof BlockScopeNode && ((BlockScopeNode) sourceSectionProvider).isFunctionBlock()) {
            sourceSectionProvider = sourceSectionProvider.getRootNode();
        }
        SourceSection sourceLocation = sourceSectionProvider.getEncapsulatingSourceSection();
        if (sourceLocation == null) {
            throw UnsupportedMessageException.create();
        }
        return sourceLocation;
    }

    @SuppressWarnings("static-method")
    @ExportMessage
    @TruffleBoundary
    Object toDisplayString(@SuppressWarnings("unused") boolean allowSideEffects) {
        RootNode root;
        if (blockOrRoot instanceof BlockScopeNode) {
            if (((BlockScopeNode) blockOrRoot).isFunctionBlock()) {
                root = blockOrRoot.getRootNode();
            } else {
                return "block";
            }
        } else {
            root = (RootNode) blockOrRoot;
        }
        String name = root.getName();
        return (name == null) ? "" : name;
    }

    static class ResolvedSlot {
        final int slot;
        final int frameLevel;
        final int scopeLevel;
        final FrameDescriptor descriptor;

        ResolvedSlot(int slot, int frameLevel, int scopeLevel, FrameDescriptor descriptor) {
            this.slot = slot;
            this.frameLevel = frameLevel;
            this.scopeLevel = scopeLevel;
            this.descriptor = descriptor;
        }

        ResolvedSlot() {
            this(-1, -1, -1, null);
        }

        JavaScriptNode createReadNode() {
            if (!hasSlot()) {
                return JSConstantNode.createUndefined();
            }
            ScopeFrameNode scopeFrameNode = createScopeFrameNode();
            return JSReadFrameSlotNode.create(JSFrameSlot.fromIndexedFrameSlot(descriptor, slot), scopeFrameNode, JSFrameUtil.hasTemporalDeadZone(descriptor, slot));
        }

        WriteNode createWriteNode() {
            if (!hasSlot()) {
                return null;
            }
            ScopeFrameNode scopeFrameNode = createScopeFrameNode();
            return JSWriteFrameSlotNode.create(JSFrameSlot.fromIndexedFrameSlot(descriptor, slot), scopeFrameNode, null, JSFrameUtil.hasTemporalDeadZone(descriptor, slot));
        }

        ScopeFrameNode createScopeFrameNode() {
            if (isFunctionFrame()) {
                return ScopeFrameNode.createCurrent();
            }
            return ScopeFrameNode.create(frameLevel, scopeLevel, null);
        }

        boolean isModifiable() {
            return hasSlot() && !JSFrameUtil.isConst(descriptor, slot) && !JSFrameUtil.isThisSlot(descriptor, slot) && !JSFrameUtil.isImportBinding(descriptor, slot);
        }

        boolean hasSlot() {
            return slot >= 0;
        }

        boolean isFunctionFrame() {
            return scopeLevel < 0;
        }

        @Override
        public String toString() {
            if (hasSlot()) {
                return getClass().getSimpleName() + "(" + String.valueOf(descriptor.getSlotName(slot)) + ", #" + slot + ", " + frameLevel + "/" + scopeLevel + ")";
            }
            return super.toString();
        }
    }

    static class DynamicScopeResolvedSlot extends ResolvedSlot {
        final Object key;

        DynamicScopeResolvedSlot(Object key, int slot, int frameLevel, int scopeLevel, FrameDescriptor descriptor) {
            super(slot, frameLevel, scopeLevel, descriptor);
            this.key = key;
        }

        @Override
        JavaScriptNode createReadNode() {
            JavaScriptNode readDynamicScope = super.createReadNode();

            class EvalRead extends JavaScriptNode {
                @Child JavaScriptNode getDynamicScope = readDynamicScope;
                @Child DynamicObjectLibrary objectLibrary;

                @Override
                public Object execute(VirtualFrame frame) {
                    JSDynamicObject scope = (JSDynamicObject) getDynamicScope.execute(frame);
                    if (!JSRuntime.isObject(scope)) {
                        return Undefined.instance;
                    }
                    DynamicObjectLibrary lib = objectLibrary;
                    if (lib == null) {
                        CompilerDirectives.transferToInterpreterAndInvalidate();
                        if (getParent() != null) {
                            lib = insert(DynamicObjectLibrary.getFactory().createDispatched(JSConfig.PropertyCacheLimit));
                        } else {
                            lib = DynamicObjectLibrary.getUncached();
                        }
                        objectLibrary = lib;
                    }
                    return Properties.getOrDefault(lib, scope, key, Undefined.instance);
                }
            }
            return new EvalRead();
        }

        @Override
        WriteNode createWriteNode() {
            JavaScriptNode readDynamicScope = super.createReadNode();

            class EvalWrite extends JavaScriptNode implements WriteNode {
                @Child JavaScriptNode getDynamicScope = readDynamicScope;
                @Child DynamicObjectLibrary objectLibrary;

                @Override
                public Object execute(VirtualFrame frame) {
                    throw CompilerDirectives.shouldNotReachHere();
                }

                @Override
                public void executeWrite(VirtualFrame frame, Object value) {
                    JSDynamicObject scope = (JSDynamicObject) getDynamicScope.execute(frame);
                    if (!JSRuntime.isObject(scope)) {
                        return;
                    }
                    DynamicObjectLibrary lib = objectLibrary;
                    if (lib == null) {
                        CompilerDirectives.transferToInterpreterAndInvalidate();
                        if (getParent() != null) {
                            lib = insert(DynamicObjectLibrary.getFactory().createDispatched(JSConfig.PropertyCacheLimit));
                        } else {
                            lib = DynamicObjectLibrary.getUncached();
                        }
                        objectLibrary = lib;
                    }
                    lib.putIfPresent(scope, key, value);
                }

                @Override
                public JavaScriptNode getRhs() {
                    return null;
                }
            }
            return new EvalWrite();
        }
    }

    static class ResolvedThisSlot extends ResolvedSlot {
        ResolvedThisSlot(int slot, int frameLevel, int scopeLevel, FrameDescriptor descriptor) {
            super(slot, frameLevel, scopeLevel, descriptor);
        }

        ResolvedThisSlot() {
        }

        @Override
        JavaScriptNode createReadNode() {
            return new ReadThisNode(hasSlot() ? super.createReadNode() : null);
        }
    }

    static ResolvedSlot findSlot(String memberString, ScopeVariables receiver) {
        CompilerAsserts.neverPartOfCompilation();
        TruffleString member = Strings.fromJavaString(memberString);
        if (receiver.frame == null) {
            return findSlotWithoutFrame(member, receiver.blockOrRoot);
        }

        class SlotVisitor {
            Node descNode = receiver.blockOrRoot;
            int parentSlot = -1;
            int frameLevel = 0;
            int scopeLevel = 0;

            public ResolvedSlot accept(FrameDescriptor frameDescriptor, int slot, Frame targetFrame) {
                assert targetFrame.getFrameDescriptor() == frameDescriptor;
                int effectiveScopeLevel = scopeLevel;
                if (targetFrame == receiver.functionFrame) {
                    assert receiver.functionFrame.getFrameDescriptor() == frameDescriptor;
                    effectiveScopeLevel = -1;
                }
                Object slotName = frameDescriptor.getSlotName(slot);
                if (ScopeFrameNode.PARENT_SCOPE_IDENTIFIER.equals(slotName)) {
                    parentSlot = slot;
                } else if (ScopeFrameNode.EVAL_SCOPE_IDENTIFIER.equals(slotName)) {
                    JSDynamicObject evalScope = (JSDynamicObject) targetFrame.getObject(slot);
                    if (JSRuntime.isObject(evalScope)) {
                        if (DynamicObjectLibrary.getUncached().containsKey(evalScope, member)) {
                            return new DynamicScopeResolvedSlot(member, slot, frameLevel, effectiveScopeLevel, frameDescriptor);
                        }
                    }
                } else if (JSFrameUtil.isThisSlot(frameDescriptor, slot) && ScopeVariables.RECEIVER_MEMBER.equals(member)) {
                    return new ResolvedThisSlot(slot, frameLevel, effectiveScopeLevel, frameDescriptor);
                } else if (!JSFrameUtil.isInternal(frameDescriptor, slot) && member.equals(slotName)) {
                    if (JSFrameUtil.isImportBinding(frameDescriptor, slot)) {
                        return new ResolvedImportSlot(slot, frameLevel, effectiveScopeLevel, frameDescriptor);
                    }
                    return new ResolvedSlot(slot, frameLevel, effectiveScopeLevel, frameDescriptor);
                }
                return null; // continue
            }
        }

        SlotVisitor visitor = new SlotVisitor();
        Frame outerFrame = receiver.frame;
        if (receiver.functionFrame != null) {
            // traverse local frames
            FrameDescriptor rootFrameDescriptor = receiver.functionFrame.getFrameDescriptor();
            while (visitor.descNode instanceof BlockScopeNode) {
                BlockScopeNode block = (BlockScopeNode) visitor.descNode;
                visitor.parentSlot = -1;

                if (block instanceof BlockScopeNode.FrameBlockScopeNode) {
                    FrameDescriptor blockFrameDescriptor = ((BlockScopeNode.FrameBlockScopeNode) block).getFrameDescriptor();
                    // Note: If we are just entering a block scope, the frame is not available yet.
                    assert outerFrame.getFrameDescriptor() == blockFrameDescriptor || block == receiver.blockOrRoot;
                    if (outerFrame.getFrameDescriptor() == blockFrameDescriptor) {
                        for (int i = 0; i < blockFrameDescriptor.getNumberOfSlots(); i++) {
                            ResolvedSlot resolvedSlot = visitor.accept(blockFrameDescriptor, i, outerFrame);
                            if (resolvedSlot != null) {
                                return resolvedSlot;
                            }
                        }
                    }
                }
                for (int i = block.getFrameStart(); i < block.getFrameEnd(); i++) {
                    ResolvedSlot resolvedSlot = visitor.accept(rootFrameDescriptor, i, receiver.functionFrame);
                    if (resolvedSlot != null) {
                        return resolvedSlot;
                    }
                }

                visitor.descNode = JavaScriptNode.findBlockScopeNode(visitor.descNode.getParent());
                if (visitor.parentSlot >= 0) {
                    Object parent = outerFrame.getObject(visitor.parentSlot);
                    if (parent instanceof Frame) {
                        outerFrame = (Frame) parent;
                        assert outerFrame != JSFrameUtil.NULL_MATERIALIZED_FRAME;
                        visitor.scopeLevel++;
                    } else {
                        break;
                    }
                }
            }

            assert receiver.functionFrame.getFrameDescriptor() == rootFrameDescriptor && visitor.frameLevel == 0;
            visitor.scopeLevel = -1;
            for (int slot = 0; slot < rootFrameDescriptor.getNumberOfSlots(); slot++) {
                // skip hoisted block-scoped slots; only accessible within their block
                if (JSFrameUtil.isHoistedFromBlock(rootFrameDescriptor, slot)) {
                    continue;
                }
                ResolvedSlot resolvedSlot = visitor.accept(rootFrameDescriptor, slot, receiver.functionFrame);
                if (resolvedSlot != null) {
                    return resolvedSlot;
                }
            }
            outerFrame = JSArguments.getEnclosingFrame(receiver.frame.getArguments());
            visitor.frameLevel = 1;
        }

        // traverse non-local frames
        while (outerFrame != JSFrameUtil.NULL_MATERIALIZED_FRAME) {
            visitor.descNode = JSFunction.getFunctionData(JSFrameUtil.getFunctionObject(outerFrame)).getRootNode();
            visitor.scopeLevel = 0;
            for (;;) {
                visitor.parentSlot = -1;
                for (int slot = 0; slot < outerFrame.getFrameDescriptor().getNumberOfSlots(); slot++) {
                    ResolvedSlot resolvedSlot = visitor.accept(outerFrame.getFrameDescriptor(), slot, outerFrame);
                    if (resolvedSlot != null) {
                        return resolvedSlot;
                    }
                }
                if (visitor.parentSlot >= 0) {
                    Object parent = outerFrame.getObject(visitor.parentSlot);
                    if (parent instanceof Frame) {
                        outerFrame = (Frame) parent;
                        assert outerFrame != JSFrameUtil.NULL_MATERIALIZED_FRAME;
                        visitor.scopeLevel++;
                        continue;
                    }
                }
                break;
            }

            outerFrame = JSArguments.getEnclosingFrame(outerFrame.getArguments());
            visitor.frameLevel++;
        }

        // If it can't be resolved, we still provide a best-effort 'this' member.
        if (receiver.frame != null && ScopeVariables.RECEIVER_MEMBER.equals(member)) {
            return new ResolvedThisSlot();
        }

        return null; // Not found
    }

    private static ResolvedSlot findSlotWithoutFrame(TruffleString member, Node blockOrRootNode) {
        CompilerAsserts.neverPartOfCompilation();
        Node descNode = blockOrRootNode;
        while (descNode != null) {
            if (!(descNode instanceof FrameDescriptorProvider)) {
                break;
            }
            FrameDescriptor desc = ((FrameDescriptorProvider) descNode).getFrameDescriptor();
            OptionalInt slot = JSFrameUtil.findOptionalFrameSlotIndex(desc, member);
            if (slot.isPresent()) {
                if (JSFrameUtil.isInternal(desc, slot.getAsInt())) {
                    return null;
                }
                return new ResolvedSlot();
            }

            descNode = JavaScriptNode.findBlockScopeNode(descNode.getParent());
        }
        return null;
    }

    static boolean hasSlot(String member, ScopeVariables receiver) {
        return findSlot(member, receiver) != null;
    }

    static JavaScriptNode findReadNode(ResolvedSlot slot) {
        if (slot != null) {
            return slot.createReadNode();
        } else {
            return null;
        }
    }

    static WriteNode findWriteNode(ResolvedSlot slot) {
        if (slot != null && slot.isModifiable()) {
            return slot.createWriteNode();
        } else {
            return null;
        }
    }

    static Object thisFromFunctionOrArguments(Object[] args) {
        // this can be either undefined or not populated yet
        // => try to avoid returning undefined in the latter case
        Object function = JSArguments.getFunctionObject(args);
        if (JSFunction.isJSFunction(function)) {
            JSDynamicObject jsFunction = (JSDynamicObject) function;
            return isArrowFunctionWithThisCaptured(jsFunction) ? JSFunction.getLexicalThis(jsFunction) : thisFromArguments(args);
        }
        return Undefined.instance;
    }

    static Object thisFromArguments(Object[] args) {
        Object thisObject = JSArguments.getThisObject(args);
        Object function = JSArguments.getFunctionObject(args);
        if (JSFunction.isJSFunction(function) && !JSFunction.isStrict((JSDynamicObject) function)) {
            JSRealm realm = JavaScriptLanguage.getCurrentJSRealm();
            if (thisObject == Undefined.instance || thisObject == Null.instance) {
                thisObject = realm.getGlobalObject();
            } else {
                thisObject = JSRuntime.toObject(thisObject);
            }
        }
        return thisObject;
    }

    private static boolean isArrowFunctionWithThisCaptured(JSDynamicObject function) {
        return !JSFunction.isConstructor(function) && JSFunction.isClassPrototypeInitialized(function);
    }

    static final class ReadThisNode extends JavaScriptNode {
        @Child JavaScriptNode readThis;

        ReadThisNode(JavaScriptNode readThis) {
            this.readThis = readThis;
        }

        @Override
        public Object execute(VirtualFrame frame) {
            if (readThis == null) {
                return thisFromArguments(frame.getArguments());
            }
            Object thisValue = readThis.execute(frame);
            if (thisValue == Undefined.instance) {
                return thisFromFunctionOrArguments(frame.getArguments());
            }
            return thisValue;
        }
    }

    static class ResolvedImportSlot extends ResolvedSlot {
        ResolvedImportSlot(int slot, int frameLevel, int scopeLevel, FrameDescriptor descriptor) {
            super(slot, frameLevel, scopeLevel, descriptor);
        }

        @Override
        JavaScriptNode createReadNode() {
            if (!hasSlot()) {
                return JSConstantNode.createUndefined();
            }
            return ReadImportBindingNode.create(super.createReadNode());
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy