package com.oracle.truffle.js.runtime.interop;
import java.util.ArrayList;
import java.util.List;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.BlockNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeVisitor;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.js.nodes.FrameDescriptorProvider;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.access.JSWriteFrameSlotNode;
import com.oracle.truffle.js.nodes.access.ScopeFrameNode;
import com.oracle.truffle.js.nodes.function.JSBuiltin;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSFrameUtil;
import com.oracle.truffle.js.runtime.objects.Dead;
@ExportLibrary(InteropLibrary.class)
final class ScopeMembers implements TruffleObject {
private final Frame frame;
private final Node blockOrRoot;
private Object[] members;
ScopeMembers(Frame frame, Node blockOrRoot) {
this.frame = frame;
this.blockOrRoot = blockOrRoot;
}
@SuppressWarnings("static-method")
@ExportMessage
boolean hasArrayElements() {
return true;
}
@ExportMessage
Object readArrayElement(long index) throws InvalidArrayIndexException {
Object[] allMembers = getAllMembers();
if (0 <= index && index < allMembers.length) {
return allMembers[(int) index];
} else {
throw InvalidArrayIndexException.create(index);
}
}
@ExportMessage
long getArraySize() {
return getAllMembers().length;
}
@ExportMessage
boolean isArrayElementReadable(long index) {
return 0 <= index && index < getAllMembers().length;
}
private Object[] getAllMembers() {
if (CompilerDirectives.injectBranchProbability(CompilerDirectives.SLOWPATH_PROBABILITY, members == null)) {
members = collectAllMembers();
}
return members;
}
@TruffleBoundary
private Object[] collectAllMembers() {
List<Object> membersList = new ArrayList<>();
if (frame == null) {
Node descNode = blockOrRoot;
while (descNode != null) {
if (!(descNode instanceof FrameDescriptorProvider)) {
break;
}
FrameDescriptor desc = ((FrameDescriptorProvider) descNode).getFrameDescriptor();
for (FrameSlot slot : desc.getSlots()) {
if (JSFrameUtil.isInternal(slot)) {
continue;
}
membersList.add(new Key(slot.getIdentifier().toString(), descNode, slot));
}
descNode = JavaScriptNode.findBlockScopeNode(descNode.getParent());
}
} else {
Node descNode = blockOrRoot;
Frame outerFrame = frame;
for (;;) {
Frame outerScope = outerFrame;
for (;;) {
for (FrameSlot slot : outerScope.getFrameDescriptor().getSlots()) {
if (JSFrameUtil.isInternal(slot)) {
continue;
}
if (isUnsetFrameSlot(outerScope, slot)) {
continue;
}
membersList.add(new Key(slot.getIdentifier().toString(), descNode, slot));
}
FrameSlot parentSlot = outerScope.getFrameDescriptor().findFrameSlot(ScopeFrameNode.PARENT_SCOPE_IDENTIFIER);
if (parentSlot == null) {
membersList.add(new Key(ScopeVariables.RECEIVER_MEMBER, descNode, null));
break;
}
outerScope = (Frame) FrameUtil.getObjectSafe(outerScope, parentSlot);
if (descNode != null) {
descNode = JavaScriptNode.findBlockScopeNode(descNode.getParent());
}
}
outerFrame = JSArguments.getEnclosingFrame(outerFrame.getArguments());
if (outerFrame == JSFrameUtil.NULL_MATERIALIZED_FRAME) {
break;
}
}
}
return membersList.toArray();
}
static boolean isUnsetFrameSlot(Frame frame, FrameSlot slot) {
if (frame != null && frame.isObject(slot)) {
Object value = FrameUtil.getObjectSafe(frame, slot);
if (value == null || value == Dead.instance() || value instanceof Frame) {
return true;
}
}
return false;
}
@ExportLibrary(InteropLibrary.class)
static final class Key implements TruffleObject {
private final String name;
private final Node blockOrRoot;
private final FrameSlot slot;
private SourceSection sourceLocation;
Key(String name, Node blockOrRoot, FrameSlot slot) {
this.name = name;
this.slot = slot;
this.blockOrRoot = blockOrRoot;
}
@ExportMessage
@SuppressWarnings("static-method")
boolean isString() {
return true;
}
@ExportMessage
String asString() {
return name;
}
@Override
public String toString() {
return asString();
}
@ExportMessage
@TruffleBoundary
boolean hasSourceLocation() {
return getOrFindSourceLocation().isAvailable();
}
@ExportMessage
@TruffleBoundary
SourceSection getSourceLocation() throws UnsupportedMessageException {
if (!hasSourceLocation()) {
throw UnsupportedMessageException.create();
}
return sourceLocation;
}
private SourceSection getOrFindSourceLocation() {
CompilerAsserts.neverPartOfCompilation();
if (sourceLocation == null && blockOrRoot != null) {
sourceLocation = findSourceLocation();
}
if (sourceLocation == null) {
sourceLocation = JSBuiltin.createSourceSection();
}
return sourceLocation;
}
private SourceSection findSourceLocation() {
if (slot != null) {
class DeclarationFinder implements NodeVisitor {
JavaScriptNode found;
@Override
public boolean visit(Node node) {
if (node instanceof JavaScriptNode) {
if (node instanceof JSWriteFrameSlotNode) {
JSWriteFrameSlotNode write = (JSWriteFrameSlotNode) node;
if (write.getFrameSlot() == slot && write.hasSourceSection()) {
found = write;
return false;
}
}
return true;
} else if (node == blockOrRoot) {
return true;
} else if (node instanceof BlockNode) {
return true;
} else {
return false;
}
}
}
DeclarationFinder finder = new DeclarationFinder();
blockOrRoot.accept(finder);
if (finder.found != null) {
return finder.found.getSourceSection();
}
}
return blockOrRoot.getEncapsulatingSourceSection();
}
}
}