package com.oracle.truffle.js.nodes.access;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.NodeCost;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.Property;
import com.oracle.truffle.api.object.Shape;
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.JSRuntime;
import com.oracle.truffle.js.runtime.builtins.JSAdapter;
import com.oracle.truffle.js.runtime.builtins.JSArrayBufferView;
import com.oracle.truffle.js.runtime.builtins.JSModuleNamespace;
import com.oracle.truffle.js.runtime.builtins.JSProxy;
import com.oracle.truffle.js.runtime.java.JavaImporter;
import com.oracle.truffle.js.runtime.java.JavaPackage;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.util.JSClassProfile;
public class HasPropertyCacheNode extends PropertyCacheNode<HasPropertyCacheNode.HasCacheNode> {
private final boolean hasOwnProperty;
private boolean propertyAssumptionCheckEnabled = true;
public static HasPropertyCacheNode create(Object key, JSContext context, boolean hasOwnProperty) {
return new HasPropertyCacheNode(key, context, hasOwnProperty);
}
public static HasPropertyCacheNode create(Object key, JSContext context) {
return create(key, context, false);
}
protected HasPropertyCacheNode(Object key, JSContext context, boolean hasOwnProperty) {
super(key, context);
this.hasOwnProperty = hasOwnProperty;
}
@ExplodeLoop
public boolean hasProperty(Object thisObj) {
for (HasCacheNode c = cacheNode; c != null; c = c.next) {
if (c.isGeneric()) {
return c.hasProperty(thisObj, this);
}
if (!c.isValid()) {
CompilerDirectives.transferToInterpreterAndInvalidate();
break;
}
boolean guard = c.accepts(thisObj);
if (guard) {
return c.hasProperty(thisObj, this);
}
}
deoptimize();
return hasPropertyAndSpecialize(thisObj);
}
@TruffleBoundary
private boolean hasPropertyAndSpecialize(Object thisObj) {
HasCacheNode node = specialize(thisObj);
if (node.accepts(thisObj)) {
return node.hasProperty(thisObj, this);
} else {
CompilerDirectives.transferToInterpreter();
throw new AssertionError("Inconsistent guards.");
}
}
public abstract static class HasCacheNode extends PropertyCacheNode.CacheNode<HasCacheNode> {
protected HasCacheNode(ReceiverCheckNode receiverCheck) {
super(receiverCheck);
}
protected abstract boolean hasProperty(Object thisObj, HasPropertyCacheNode root);
}
public abstract static class LinkedHasPropertyCacheNode extends HasCacheNode {
protected LinkedHasPropertyCacheNode(ReceiverCheckNode receiverCheckNode) {
super(receiverCheckNode);
}
}
public static final class PresentHasPropertyCacheNode extends LinkedHasPropertyCacheNode {
public PresentHasPropertyCacheNode(ReceiverCheckNode shapeCheck) {
super(shapeCheck);
}
@Override
protected boolean hasProperty(Object thisObj, HasPropertyCacheNode root) {
return true;
}
}
public static final class AbsentHasPropertyCacheNode extends LinkedHasPropertyCacheNode {
public AbsentHasPropertyCacheNode(ReceiverCheckNode shapeCheckNode) {
super(shapeCheckNode);
}
@Override
protected boolean hasProperty(Object thisObj, HasPropertyCacheNode root) {
return false;
}
}
public static final class ArrayBufferViewHasNonIntegerIndexNode extends LinkedHasPropertyCacheNode {
public ArrayBufferViewHasNonIntegerIndexNode(ReceiverCheckNode shapeCheckNode) {
super(shapeCheckNode);
}
@Override
protected boolean hasProperty(Object thisObj, HasPropertyCacheNode root) {
if (JSArrayBufferView.hasDetachedBuffer((DynamicObject) thisObj)) {
throw Errors.createTypeErrorDetachedBuffer();
} else {
return false;
}
}
}
public static final class JSAdapterHasPropertyCacheNode extends LinkedHasPropertyCacheNode {
public JSAdapterHasPropertyCacheNode(Object key, ReceiverCheckNode receiverCheckNode) {
super(receiverCheckNode);
assert JSRuntime.isPropertyKey(key);
}
@Override
protected boolean hasProperty(Object thisObj, HasPropertyCacheNode root) {
return JSObject.hasOwnProperty((DynamicObject) thisObj, root.getKey());
}
}
public static final class JSProxyDispatcherPropertyHasNode extends LinkedHasPropertyCacheNode {
private final boolean hasOwnProperty;
@Child private JSProxyHasPropertyNode proxyGet;
@Child private JSGetOwnPropertyNode getOwnPropertyNode;
public JSProxyDispatcherPropertyHasNode(JSContext context, Object key, ReceiverCheckNode receiverCheck, boolean hasOwnProperty) {
super(receiverCheck);
this.hasOwnProperty = hasOwnProperty;
assert JSRuntime.isPropertyKey(key);
this.proxyGet = hasOwnProperty ? null : JSProxyHasPropertyNodeGen.create(context);
this.getOwnPropertyNode = hasOwnProperty ? JSGetOwnPropertyNode.create() : null;
}
@Override
protected boolean hasProperty(Object thisObj, HasPropertyCacheNode root) {
Object key = root.getKey();
if (hasOwnProperty) {
return getOwnPropertyNode.execute(receiverCheck.getStore(thisObj), key) != null;
} else {
return proxyGet.executeWithTargetAndKeyBoolean(receiverCheck.getStore(thisObj), key);
}
}
}
public static final class UnspecializedHasPropertyCacheNode extends LinkedHasPropertyCacheNode {
public UnspecializedHasPropertyCacheNode(ReceiverCheckNode receiverCheckNode) {
super(receiverCheckNode);
}
@Override
protected boolean hasProperty(Object thisObj, HasPropertyCacheNode root) {
Object key = root.getKey();
if (root.isOwnProperty()) {
return JSObject.hasOwnProperty((DynamicObject) thisObj, key);
} else {
return JSObject.hasProperty((DynamicObject) thisObj, key);
}
}
}
@NodeInfo(cost = NodeCost.MEGAMORPHIC)
public static final class GenericHasPropertyCacheNode extends HasCacheNode {
@Child private InteropLibrary interop;
private final JSClassProfile jsclassProfile = JSClassProfile.create();
public GenericHasPropertyCacheNode() {
super(null);
this.interop = InteropLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit);
}
@Override
protected boolean hasProperty(Object thisObj, HasPropertyCacheNode root) {
if (JSDynamicObject.isJSDynamicObject(thisObj)) {
Object key = root.getKey();
if (root.isOwnProperty()) {
return JSObject.hasOwnProperty((DynamicObject) thisObj, key, jsclassProfile);
} else {
return JSObject.hasProperty((DynamicObject) thisObj, key, jsclassProfile);
}
} else {
assert JSRuntime.isForeignObject(thisObj);
Object key = root.getKey();
if (key instanceof String) {
return interop.isMemberExisting(thisObj, (String) key);
} else {
return false;
}
}
}
}
public static final class ForeignHasPropertyCacheNode extends LinkedHasPropertyCacheNode {
@Child private InteropLibrary interop;
public ForeignHasPropertyCacheNode() {
super(new ForeignLanguageCheckNode());
this.interop = InteropLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit);
}
@Override
protected boolean hasProperty(Object thisObj, HasPropertyCacheNode root) {
assert JSRuntime.isForeignObject(thisObj);
Object key = root.getKey();
if (key instanceof String) {
return interop.isMemberExisting(thisObj, (String) key);
} else {
return false;
}
}
}
@Override
protected HasCacheNode createCachedPropertyNode(Property property, Object thisObj, int depth, Object value, HasCacheNode currentHead) {
assert !isOwnProperty() || depth == 0;
ReceiverCheckNode check;
if (JSDynamicObject.isJSDynamicObject(thisObj)) {
JSDynamicObject thisJSObj = (JSDynamicObject) thisObj;
Shape cacheShape = thisJSObj.getShape();
check = createShapeCheckNode(cacheShape, thisJSObj, depth, false, false);
} else {
check = createPrimitiveReceiverCheck(thisObj, depth);
}
return new PresentHasPropertyCacheNode(check);
}
@Override
protected HasCacheNode createUndefinedPropertyNode(Object thisObj, Object store, int depth, Object value) {
HasCacheNode specialized = createJavaPropertyNodeMaybe(thisObj, depth);
if (specialized != null) {
return specialized;
}
if (JSDynamicObject.isJSDynamicObject(thisObj)) {
JSDynamicObject thisJSObj = (JSDynamicObject) thisObj;
Shape cacheShape = thisJSObj.getShape();
AbstractShapeCheckNode shapeCheck = createShapeCheckNode(cacheShape, thisJSObj, depth, false, false);
ReceiverCheckNode receiverCheck = (depth == 0) ? new JSClassCheckNode(JSObject.getJSClass(thisJSObj)) : shapeCheck;
if (JSAdapter.isJSAdapter(store)) {
return new JSAdapterHasPropertyCacheNode(key, receiverCheck);
} else if (JSProxy.isJSProxy(store)) {
return new JSProxyDispatcherPropertyHasNode(context, key, receiverCheck, isOwnProperty());
} else if (JSModuleNamespace.isJSModuleNamespace(store)) {
return new UnspecializedHasPropertyCacheNode(receiverCheck);
} else if (JSArrayBufferView.isJSArrayBufferView(store) && isNonIntegerIndex(key)) {
return new ArrayBufferViewHasNonIntegerIndexNode(shapeCheck);
} else {
return new AbsentHasPropertyCacheNode(shapeCheck);
}
} else {
return new AbsentHasPropertyCacheNode(new InstanceofCheckNode(thisObj.getClass(), context));
}
}
@Override
protected HasCacheNode createJavaPropertyNodeMaybe(Object thisObj, int depth) {
if (JavaPackage.isJavaPackage(thisObj)) {
return new PresentHasPropertyCacheNode(new JSClassCheckNode(JSObject.getJSClass((DynamicObject) thisObj)));
} else if (JavaImporter.isJavaImporter(thisObj)) {
return new UnspecializedHasPropertyCacheNode(new JSClassCheckNode(JSObject.getJSClass((DynamicObject) thisObj)));
}
return null;
}
@Override
protected HasCacheNode createGenericPropertyNode() {
return new GenericHasPropertyCacheNode();
}
@Override
protected boolean isPropertyAssumptionCheckEnabled() {
return propertyAssumptionCheckEnabled && context.isSingleRealm();
}
@Override
protected void setPropertyAssumptionCheckEnabled(boolean value) {
this.propertyAssumptionCheckEnabled = value;
}
@Override
protected boolean isGlobal() {
return false;
}
@Override
protected boolean isOwnProperty() {
return hasOwnProperty;
}
@Override
protected HasCacheNode createTruffleObjectPropertyNode() {
return new ForeignHasPropertyCacheNode();
}
}