package com.oracle.truffle.js.runtime.objects;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.DynamicObjectLibrary;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.api.object.LocationFactory;
import com.oracle.truffle.api.object.Property;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.js.builtins.JSBuiltinsContainer;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.Symbol;
import com.oracle.truffle.js.runtime.builtins.Builtin;
import com.oracle.truffle.js.runtime.builtins.JSClass;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSOrdinary;
public final class JSObjectUtil {
private static final HiddenKey PROTOTYPE_DATA = new HiddenKey("PROTOTYPE_DATA");
private JSObjectUtil() {
}
@TruffleBoundary
public static String formatToString(String object) {
return "[object " + object + "]";
}
public static DynamicObject createOrdinaryPrototypeObject(JSRealm realm) {
CompilerAsserts.neverPartOfCompilation();
return createOrdinaryPrototypeObject(realm, realm.getObjectPrototype());
}
public static DynamicObject createOrdinaryPrototypeObject(JSRealm realm, DynamicObject prototype) {
CompilerAsserts.neverPartOfCompilation();
assert prototype == Null.instance || JSRuntime.isObject(prototype);
JSContext context = realm.getContext();
DynamicObject obj;
if (context.isMultiContext()) {
obj = JSOrdinary.createInitWithInstancePrototype(prototype, context);
} else {
Shape initialShape = prototype == Null.instance ? context.getEmptyShapeNullPrototype() : JSObjectUtil.getProtoChildShape(prototype, JSOrdinary.INSTANCE, context);
obj = JSOrdinaryObject.create(initialShape);
}
return obj;
}
public static void setOrVerifyPrototype(JSContext context, DynamicObject obj, DynamicObject prototype) {
CompilerAsserts.neverPartOfCompilation();
assert prototype == Null.instance || JSRuntime.isObject(prototype);
if (context.isMultiContext()) {
JSObjectUtil.putHiddenProperty(obj, JSObject.HIDDEN_PROTO, prototype);
} else {
assert JSObjectUtil.getHiddenProperty(obj, JSObject.HIDDEN_PROTO) == prototype;
}
}
public static boolean isValidPrototype(Object proto) {
return proto == Null.instance || JSRuntime.isObject(proto);
}
private static LocationFactory declaredLocationFactory() {
return (shape, val) -> shape.allocator().declaredLocation(val);
}
public static Shape shapeDefineDataProperty(JSContext context, Shape shape, Object key, Object value, int flags) {
CompilerAsserts.neverPartOfCompilation();
return shape.defineProperty(checkForNoSuchPropertyOrMethod(context, key), value, flags);
}
@SuppressWarnings("deprecation")
public static Shape shapeDefineDeclaredDataProperty(JSContext context, Shape shape, Object key, Object value, int flags) {
CompilerAsserts.neverPartOfCompilation();
checkForNoSuchPropertyOrMethod(context, key);
return shape.defineProperty(key, value, flags, declaredLocationFactory());
}
@TruffleBoundary
public static void putDataProperty(JSContext context, DynamicObject thisObj, Object key, Object value, int flags) {
assert checkForExistingProperty(thisObj, key);
defineDataProperty(context, thisObj, key, value, flags);
}
@TruffleBoundary
public static void putDataProperty(DynamicObject thisObj, Object name, Object value, int flags) {
JSContext context = JSObject.getJSContext(thisObj);
putDataProperty(context, thisObj, name, value, flags);
}
@TruffleBoundary
public static void defineDataProperty(JSContext context, DynamicObject thisObj, Object key, Object value, int flags) {
checkForNoSuchPropertyOrMethod(context, key);
DynamicObjectLibrary.getUncached().putWithFlags(thisObj, key, value, flags);
}
@TruffleBoundary
public static void defineDataProperty(DynamicObject thisObj, Object key, Object value, int flags) {
JSContext context = JSObject.getJSContext(thisObj);
defineDataProperty(context, thisObj, key, value, flags);
}
public static void putOrSetDataProperty(JSContext context, DynamicObject thisObj, Object key, Object value, int flags) {
if (!JSObject.hasOwnProperty(thisObj, key)) {
JSObjectUtil.putDataProperty(context, thisObj, key, value, flags);
} else {
JSObject.set(thisObj, key, value);
}
}
@TruffleBoundary
public static void defineAccessorProperty(DynamicObject thisObj, Object key, Accessor accessor, int flags) {
int finalFlags = flags | JSProperty.ACCESSOR;
JSContext context = JSObject.getJSContext(thisObj);
checkForNoSuchPropertyOrMethod(context, key);
DynamicObjectLibrary.getUncached().putWithFlags(thisObj, key, accessor, finalFlags);
}
@TruffleBoundary
public static void defineProxyProperty(DynamicObject thisObj, Object key, PropertyProxy proxy, int flags) {
int finalFlags = flags | JSProperty.PROXY;
JSContext context = JSObject.getJSContext(thisObj);
checkForNoSuchPropertyOrMethod(context, key);
DynamicObjectLibrary.getUncached().putConstant(thisObj, key, proxy, finalFlags);
}
@TruffleBoundary
public static void changePropertyFlags(DynamicObject thisObj, Object key, int flags) {
assert flags == (flags & JSAttributes.ATTRIBUTES_MASK);
JSDynamicObject.updatePropertyFlags(thisObj, key, (attr) -> (attr & ~JSAttributes.ATTRIBUTES_MASK) | flags);
}
public static void putDataProperty(JSContext context, DynamicObject thisObj, String name, Object value) {
putDataProperty(context, thisObj, name, value, JSAttributes.notConfigurableNotEnumerableNotWritable());
}
@TruffleBoundary
public static void putDeclaredDataProperty(JSContext context, DynamicObject thisObj, Object key, Object value, int flags) {
assert JSRuntime.isPropertyKey(key);
assert checkForExistingProperty(thisObj, key);
checkForNoSuchPropertyOrMethod(context, key);
DynamicObjectLibrary.getUncached().putConstant(thisObj, key, value, flags);
}
public static void putConstructorProperty(JSContext context, DynamicObject prototype, DynamicObject constructor) {
putDataProperty(context, prototype, JSObject.CONSTRUCTOR, constructor, JSAttributes.configurableNotEnumerableWritable());
}
public static void putConstructorPrototypeProperty(JSContext ctx, DynamicObject constructor, DynamicObject prototype) {
putDataProperty(ctx, constructor, JSObject.PROTOTYPE, prototype, JSAttributes.notConfigurableNotEnumerableNotWritable());
}
public static void putToStringTag(DynamicObject prototype, String toStringTag) {
assert checkForExistingProperty(prototype, Symbol.SYMBOL_TO_STRING_TAG);
DynamicObjectLibrary.getUncached().putWithFlags(prototype, Symbol.SYMBOL_TO_STRING_TAG, toStringTag, JSAttributes.configurableNotEnumerableNotWritable());
}
@TruffleBoundary
public static void putAccessorProperty(JSContext context, DynamicObject thisObj, Object key, DynamicObject getter, DynamicObject setter, int flags) {
Accessor accessor = new Accessor(getter, setter);
putAccessorProperty(context, thisObj, key, accessor, flags);
}
@TruffleBoundary
public static void putAccessorProperty(JSContext context, DynamicObject thisObj, Object key, Accessor accessor, int flags) {
assert JSRuntime.isPropertyKey(key);
assert checkForExistingProperty(thisObj, key);
checkForNoSuchPropertyOrMethod(context, key);
DynamicObjectLibrary.getUncached().putWithFlags(thisObj, key, accessor, flags | JSProperty.ACCESSOR);
}
public static void putBuiltinAccessorProperty(DynamicObject thisObj, Object key, DynamicObject getter, DynamicObject setter) {
putBuiltinAccessorProperty(thisObj, key, getter, setter, JSAttributes.configurableNotEnumerable());
}
@TruffleBoundary
public static void putBuiltinAccessorProperty(DynamicObject thisObj, Object key, DynamicObject getter, DynamicObject setter, int flags) {
Accessor accessor = new Accessor(getter, setter);
putBuiltinAccessorProperty(thisObj, key, accessor, flags);
}
@TruffleBoundary
public static void putBuiltinAccessorProperty(DynamicObject thisObj, Object key, Accessor accessor, int flags) {
assert JSRuntime.isPropertyKey(key) && !isNoSuchPropertyOrMethod(key);
assert checkForExistingProperty(thisObj, key);
DynamicObjectLibrary.getUncached().putWithFlags(thisObj, key, accessor, flags | JSProperty.ACCESSOR);
}
public static void putProxyProperty(DynamicObject thisObj, Object key, PropertyProxy proxy, int flags) {
assert JSRuntime.isPropertyKey(key) && !isNoSuchPropertyOrMethod(key);
assert checkForExistingProperty(thisObj, key);
defineProxyProperty(thisObj, key, proxy, flags);
}
private static boolean checkForExistingProperty(DynamicObject thisObj, Object key) {
assert !thisObj.getShape().hasProperty(key) : "Don't put a property that already exists. Use the setters.";
return true;
}
public static Shape getProtoChildShape(DynamicObject obj, JSClass jsclass, JSContext context) {
CompilerAsserts.neverPartOfCompilation();
if (obj == null) {
return context.makeEmptyShapeWithPrototypeInObject(jsclass);
}
assert JSRuntime.isObject(obj);
Shape protoChild = getProtoChildShapeMaybe(obj, jsclass);
if (protoChild != null) {
return protoChild;
}
return getProtoChildShapeSlowPath(obj, jsclass, context);
}
public static Shape getProtoChildShape(DynamicObject obj, JSClass jsclass, JSContext context, BranchProfile branchProfile) {
Shape protoChild = getProtoChildShapeMaybe(obj, jsclass);
if (protoChild != null) {
return protoChild;
}
branchProfile.enter();
return getProtoChildShapeSlowPath(obj, jsclass, context);
}
private static Shape getProtoChildShapeMaybe(DynamicObject obj, JSClass jsclass) {
Shape protoChild = JSShape.getProtoChildTree(obj, jsclass);
assert protoChild == null || JSShape.getJSClassNoCast(protoChild) == jsclass;
return protoChild;
}
@TruffleBoundary
private static Shape getProtoChildShapeSlowPath(DynamicObject obj, JSClass jsclass, JSContext context) {
JSPrototypeData prototypeData = getPrototypeData(obj);
if (prototypeData == null) {
prototypeData = putPrototypeData(obj);
}
return prototypeData.getOrAddProtoChildTree(jsclass, createChildRootShape(obj, jsclass, context));
}
private static Shape createChildRootShape(DynamicObject proto, JSClass jsclass, JSContext context) {
CompilerAsserts.neverPartOfCompilation();
assert proto != null && proto != Null.instance;
return JSShape.createObjectShape(context, jsclass, proto);
}
public static JSPrototypeData putPrototypeData(DynamicObject obj) {
CompilerAsserts.neverPartOfCompilation();
assert getPrototypeData(obj) == null;
JSPrototypeData prototypeData = new JSPrototypeData();
putPrototypeData(obj, prototypeData);
return prototypeData;
}
private static void putPrototypeData(DynamicObject obj, JSPrototypeData prototypeData) {
boolean extensible = JSShape.isExtensible(obj.getShape());
JSObjectUtil.putHiddenProperty(obj, PROTOTYPE_DATA, prototypeData);
assert extensible == JSShape.isExtensible(obj.getShape());
}
static JSPrototypeData getPrototypeData(DynamicObject obj) {
return (JSPrototypeData) JSDynamicObject.getOrNull(obj, PROTOTYPE_DATA);
}
public static Map<Object, Object> archive(DynamicObject obj) {
HashMap<Object, Object> ret = new HashMap<>();
Shape shape = obj.getShape();
for (Property prop : shape.getPropertyListInternal(false)) {
if (!(prop.getLocation().isValue()) && !ret.containsKey(prop.getKey())) {
ret.put(prop.getKey(), prop.get(obj, false));
}
}
return ret;
}
@TruffleBoundary
public static void setPrototypeImpl(DynamicObject object, DynamicObject newPrototype) {
CompilerAsserts.neverPartOfCompilation();
assert JSShape.isPrototypeInShape(object.getShape());
final JSContext context = JSObject.getJSContext(object);
final Shape oldShape = object.getShape();
JSShape.invalidatePrototypeAssumption(oldShape);
final Shape newRootShape;
JSClass jsclass = JSShape.getJSClass(oldShape);
if (newPrototype == Null.instance) {
newRootShape = context.makeEmptyShapeWithNullPrototype(jsclass);
} else {
assert JSRuntime.isObject(newPrototype) : newPrototype;
if (context.isMultiContext()) {
newRootShape = context.makeEmptyShapeWithPrototypeInObject(jsclass);
} else {
newRootShape = JSObjectUtil.getProtoChildShape(newPrototype, jsclass, context);
}
}
DynamicObjectLibrary lib = DynamicObjectLibrary.getUncached();
List<Property> allProperties = oldShape.getPropertyListInternal(true);
List<Object> archive = new ArrayList<>(allProperties.size());
for (Property prop : allProperties) {
Object value = lib.getOrDefault(object, prop.getKey(), null);
archive.add(value);
}
lib.resetShape(object, newRootShape);
for (int i = 0; i < allProperties.size(); i++) {
Property property = allProperties.get(i);
Object key = property.getKey();
if (!newRootShape.hasProperty(key)) {
Object value = archive.get(i);
int propertyFlags = property.getFlags();
if (JSObject.HIDDEN_PROTO.equals(key)) {
lib.putWithFlags(object, key, newPrototype, propertyFlags);
} else if (property.getLocation().isConstant()) {
lib.putConstant(object, key, value, propertyFlags);
} else {
lib.putWithFlags(object, key, value, propertyFlags);
}
}
}
assert JSObjectUtil.getPrototype(object) == newPrototype;
}
public static JSDynamicObject getPrototype(DynamicObject thisObj) {
JSSharedData sharedData = JSShape.getSharedData(thisObj.getShape());
JSDynamicObject proto = sharedData.getPrototype();
if (proto != null) {
assert proto == JSDynamicObject.getOrDefault(thisObj, JSObject.HIDDEN_PROTO, Null.instance);
return proto;
}
return (JSDynamicObject) JSDynamicObject.getOrDefault(thisObj, JSObject.HIDDEN_PROTO, Null.instance);
}
public static <T> T checkForNoSuchPropertyOrMethod(JSContext context, T key) {
CompilerAsserts.neverPartOfCompilation();
if (context != null && key != null && context.isOptionNashornCompatibilityMode()) {
if (context.getNoSuchPropertyUnusedAssumption().isValid() && JSObject.NO_SUCH_PROPERTY_NAME.equals(key)) {
context.getNoSuchPropertyUnusedAssumption().invalidate("NoSuchProperty is used");
}
if (context.getNoSuchMethodUnusedAssumption().isValid() && JSObject.NO_SUCH_METHOD_NAME.equals(key)) {
context.getNoSuchMethodUnusedAssumption().invalidate("NoSuchMethod is used");
}
}
return key;
}
public static boolean isNoSuchPropertyOrMethod(Object key) {
CompilerAsserts.neverPartOfCompilation();
return (key instanceof String && (key.equals(JSObject.NO_SUCH_PROPERTY_NAME) || key.equals(JSObject.NO_SUCH_METHOD_NAME)));
}
public static DynamicObject createSymbolSpeciesGetterFunction(JSRealm realm) {
return JSFunction.create(realm, JSFunctionData.createCallOnly(realm.getContext(), realm.getContext().getSpeciesGetterFunctionCallTarget(), 0, "get [Symbol.species]"));
}
public static void putFunctionsFromContainer(JSRealm realm, DynamicObject thisObj, JSBuiltinsContainer container) {
JSContext context = realm.getContext();
container.forEachBuiltin(new Consumer<Builtin>() {
@Override
public void accept(Builtin builtin) {
if (builtin.getECMAScriptVersion() > context.getEcmaScriptVersion()) {
return;
} else if (builtin.isAnnexB() && !context.isOptionAnnexB()) {
return;
}
JSFunctionData functionData = builtin.createFunctionData(context);
putDataProperty(context, thisObj, builtin.getKey(), JSFunction.create(realm, functionData), builtin.getAttributeFlags());
}
});
}
public static void putHiddenProperty(DynamicObject obj, Object key, Object value) {
assert key instanceof HiddenKey;
DynamicObjectLibrary.getUncached().put(obj, key, value);
}
public static Object getHiddenProperty(DynamicObject obj, Object key) {
assert key instanceof HiddenKey;
return DynamicObjectLibrary.getUncached().getOrDefault(obj, key, null);
}
public static boolean hasHiddenProperty(DynamicObject obj, Object key) {
assert key instanceof HiddenKey;
return DynamicObjectLibrary.getUncached().containsKey(obj, key);
}
public static DynamicObjectLibrary createCached(Object key, DynamicObject obj) {
assert key != null;
return DynamicObjectLibrary.getFactory().create(obj);
}
public static DynamicObjectLibrary createDispatched(Object key, int limit) {
assert key != null;
return DynamicObjectLibrary.getFactory().createDispatched(limit);
}
public static DynamicObjectLibrary createDispatched(Object key) {
return createDispatched(key, JSConfig.PropertyCacheLimit);
}
public static <T extends DynamicObject> T copyProperties(T target, DynamicObject source) {
DynamicObjectLibrary objectLibrary = DynamicObjectLibrary.getUncached();
for (Property property : source.getShape().getPropertyListInternal(true)) {
Object key = property.getKey();
if (objectLibrary.containsKey(target, key)) {
continue;
}
Object value = objectLibrary.getOrDefault(source, key, null);
if (property.getLocation().isConstant()) {
objectLibrary.putConstant(target, key, value, property.getFlags());
} else {
objectLibrary.putWithFlags(target, key, value, property.getFlags());
}
}
return target;
}
}