package jdk.nashorn.internal.runtime;
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_FIELD_TYPE;
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createGetter;
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createSetter;
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getFieldCount;
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getFieldName;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.lookup.MethodHandleFactory.stripName;
import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex;
import static jdk.nashorn.internal.runtime.JSType.getNumberOfAccessorTypes;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.SwitchPoint;
import java.util.function.Supplier;
import java.util.logging.Level;
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.lookup.Lookup;
import jdk.nashorn.internal.objects.Global;
public class AccessorProperty extends Property {
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final MethodHandle REPLACE_MAP = findOwnMH_S("replaceMap", Object.class, Object.class, PropertyMap.class);
private static final MethodHandle INVALIDATE_SP = findOwnMH_S("invalidateSwitchPoint", Object.class, AccessorProperty.class, Object.class);
private static final int NOOF_TYPES = getNumberOfAccessorTypes();
private static final long serialVersionUID = 3371720170182154920L;
private static ClassValue<Accessors> GETTERS_SETTERS = new ClassValue<Accessors>() {
@Override
protected Accessors computeValue(final Class<?> structure) {
return new Accessors(structure);
}
};
private static class Accessors {
final MethodHandle[] objectGetters;
final MethodHandle[] objectSetters;
final MethodHandle[] primitiveGetters;
final MethodHandle[] primitiveSetters;
Accessors(final Class<?> structure) {
final int fieldCount = getFieldCount(structure);
objectGetters = new MethodHandle[fieldCount];
objectSetters = new MethodHandle[fieldCount];
primitiveGetters = new MethodHandle[fieldCount];
primitiveSetters = new MethodHandle[fieldCount];
for (int i = 0; i < fieldCount; i++) {
final String fieldName = getFieldName(i, Type.OBJECT);
final Class<?> typeClass = Type.OBJECT.getTypeClass();
objectGetters[i] = MH.asType(MH.getter(LOOKUP, structure, fieldName, typeClass), Lookup.GET_OBJECT_TYPE);
objectSetters[i] = MH.asType(MH.setter(LOOKUP, structure, fieldName, typeClass), Lookup.SET_OBJECT_TYPE);
}
if (!StructureLoader.isSingleFieldStructure(structure.getName())) {
for (int i = 0; i < fieldCount; i++) {
final String fieldNamePrimitive = getFieldName(i, PRIMITIVE_FIELD_TYPE);
final Class<?> typeClass = PRIMITIVE_FIELD_TYPE.getTypeClass();
primitiveGetters[i] = MH.asType(MH.getter(LOOKUP, structure, fieldNamePrimitive, typeClass), Lookup.GET_PRIMITIVE_TYPE);
primitiveSetters[i] = MH.asType(MH.setter(LOOKUP, structure, fieldNamePrimitive, typeClass), Lookup.SET_PRIMITIVE_TYPE);
}
}
}
}
private transient MethodHandle[] GETTER_CACHE = new MethodHandle[NOOF_TYPES];
public static AccessorProperty create(final Object key, final int propertyFlags, final MethodHandle getter, final MethodHandle setter) {
return new AccessorProperty(key, propertyFlags, -1, getter, setter);
}
transient MethodHandle primitiveGetter;
transient MethodHandle primitiveSetter;
transient MethodHandle objectGetter;
transient MethodHandle objectSetter;
AccessorProperty(final AccessorProperty property, final Object delegate) {
super(property, property.getFlags() | IS_BOUND);
this.primitiveGetter = bindTo(property.primitiveGetter, delegate);
this.primitiveSetter = bindTo(property.primitiveSetter, delegate);
this.objectGetter = bindTo(property.objectGetter, delegate);
this.objectSetter = bindTo(property.objectSetter, delegate);
property.GETTER_CACHE = new MethodHandle[NOOF_TYPES];
setType(property.getType());
}
protected AccessorProperty(
final Object key,
final int flags,
final int slot,
final MethodHandle primitiveGetter,
final MethodHandle primitiveSetter,
final MethodHandle objectGetter,
final MethodHandle objectSetter) {
super(key, flags, slot);
assert getClass() != AccessorProperty.class;
this.primitiveGetter = primitiveGetter;
this.primitiveSetter = primitiveSetter;
this.objectGetter = objectGetter;
this.objectSetter = objectSetter;
initializeType();
}
private AccessorProperty(final Object key, final int flags, final int slot, final MethodHandle getter, final MethodHandle setter) {
super(key, flags | IS_BUILTIN | DUAL_FIELDS | (getter.type().returnType().isPrimitive() ? IS_NASGEN_PRIMITIVE : 0), slot);
assert !isSpill();
final Class<?> getterType = getter.type().returnType();
final Class<?> setterType = setter == null ? null : setter.type().parameterType(1);
assert setterType == null || setterType == getterType;
if (getterType == int.class) {
primitiveGetter = MH.asType(getter, Lookup.GET_PRIMITIVE_TYPE);
primitiveSetter = setter == null ? null : MH.asType(setter, Lookup.SET_PRIMITIVE_TYPE);
} else if (getterType == double.class) {
primitiveGetter = MH.asType(MH.filterReturnValue(getter, ObjectClassGenerator.PACK_DOUBLE), Lookup.GET_PRIMITIVE_TYPE);
primitiveSetter = setter == null ? null : MH.asType(MH.filterArguments(setter, 1, ObjectClassGenerator.UNPACK_DOUBLE), Lookup.SET_PRIMITIVE_TYPE);
} else {
primitiveGetter = primitiveSetter = null;
}
assert primitiveGetter == null || primitiveGetter.type() == Lookup.GET_PRIMITIVE_TYPE : primitiveGetter + "!=" + Lookup.GET_PRIMITIVE_TYPE;
assert primitiveSetter == null || primitiveSetter.type() == Lookup.SET_PRIMITIVE_TYPE : primitiveSetter;
objectGetter = getter.type() != Lookup.GET_OBJECT_TYPE ? MH.asType(getter, Lookup.GET_OBJECT_TYPE) : getter;
objectSetter = setter != null && setter.type() != Lookup.SET_OBJECT_TYPE ? MH.asType(setter, Lookup.SET_OBJECT_TYPE) : setter;
setType(getterType);
}
public AccessorProperty(final Object key, final int flags, final Class<?> structure, final int slot) {
super(key, flags, slot);
initGetterSetter(structure);
initializeType();
}
private void initGetterSetter(final Class<?> structure) {
final int slot = getSlot();
if (isParameter() && hasArguments()) {
final MethodHandle arguments = MH.getter(LOOKUP, structure, "arguments", ScriptObject.class);
objectGetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.GET_OBJECT_TYPE);
objectSetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.SET_OBJECT_TYPE);
primitiveGetter = null;
primitiveSetter = null;
} else {
final Accessors gs = GETTERS_SETTERS.get(structure);
objectGetter = gs.objectGetters[slot];
primitiveGetter = gs.primitiveGetters[slot];
objectSetter = gs.objectSetters[slot];
primitiveSetter = gs.primitiveSetters[slot];
}
assert hasDualFields() != StructureLoader.isSingleFieldStructure(structure.getName());
}
protected AccessorProperty(final Object key, final int flags, final int slot, final ScriptObject owner, final Object initialValue) {
this(key, flags, owner.getClass(), slot);
setInitialValue(owner, initialValue);
}
public AccessorProperty(final Object key, final int flags, final Class<?> structure, final int slot, final Class<?> initialType) {
this(key, flags, structure, slot);
setType(hasDualFields() ? initialType : Object.class);
}
protected AccessorProperty(final AccessorProperty property, final Class<?> newType) {
super(property, property.getFlags());
this.GETTER_CACHE = newType != property.getLocalType() ? new MethodHandle[NOOF_TYPES] : property.GETTER_CACHE;
this.primitiveGetter = property.primitiveGetter;
this.primitiveSetter = property.primitiveSetter;
this.objectGetter = property.objectGetter;
this.objectSetter = property.objectSetter;
setType(newType);
}
protected AccessorProperty(final AccessorProperty property) {
this(property, property.getLocalType());
}
protected final void setInitialValue(final ScriptObject owner, final Object initialValue) {
setType(hasDualFields() ? JSType.unboxedFieldType(initialValue) : Object.class);
if (initialValue instanceof Integer) {
invokeSetter(owner, ((Integer)initialValue).intValue());
} else if (initialValue instanceof Long) {
invokeSetter(owner, ((Long)initialValue).longValue());
} else if (initialValue instanceof Double) {
invokeSetter(owner, ((Double)initialValue).doubleValue());
} else {
invokeSetter(owner, initialValue);
}
}
protected final void initializeType() {
setType(!hasDualFields() ? Object.class : null);
}
private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
GETTER_CACHE = new MethodHandle[NOOF_TYPES];
}
private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) {
if (mh == null) {
return null;
}
return MH.dropArguments(MH.bindTo(mh, receiver), 0, Object.class);
}
@Override
public Property copy() {
return new AccessorProperty(this);
}
@Override
public Property copy(final Class<?> newType) {
return new AccessorProperty(this, newType);
}
@Override
public int getIntValue(final ScriptObject self, final ScriptObject owner) {
try {
return (int)getGetter(int.class).invokeExact((Object)self);
} catch (final Error | RuntimeException e) {
throw e;
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public double getDoubleValue(final ScriptObject self, final ScriptObject owner) {
try {
return (double)getGetter(double.class).invokeExact((Object)self);
} catch (final Error | RuntimeException e) {
throw e;
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public Object getObjectValue(final ScriptObject self, final ScriptObject owner) {
try {
return getGetter(Object.class).invokeExact((Object)self);
} catch (final Error | RuntimeException e) {
throw e;
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
protected final void invokeSetter(final ScriptObject self, final int value) {
try {
getSetter(int.class, self.getMap()).invokeExact((Object)self, value);
} catch (final Error | RuntimeException e) {
throw e;
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
protected final void invokeSetter(final ScriptObject self, final double value) {
try {
getSetter(double.class, self.getMap()).invokeExact((Object)self, value);
} catch (final Error | RuntimeException e) {
throw e;
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
protected final void invokeSetter(final ScriptObject self, final Object value) {
try {
getSetter(Object.class, self.getMap()).invokeExact((Object)self, value);
} catch (final Error | RuntimeException e) {
throw e;
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public void setValue(final ScriptObject self, final ScriptObject owner, final int value, final boolean strict) {
assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable";
invokeSetter(self, value);
}
@Override
public void setValue(final ScriptObject self, final ScriptObject owner, final double value, final boolean strict) {
assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable";
invokeSetter(self, value);
}
@Override
public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) {
invokeSetter(self, value);
}
@Override
void initMethodHandles(final Class<?> structure) {
if (!ScriptObject.class.isAssignableFrom(structure) || !StructureLoader.isStructureClass(structure.getName())) {
throw new IllegalArgumentException();
}
assert !isSpill();
initGetterSetter(structure);
}
@Override
public MethodHandle getGetter(final Class<?> type) {
final int i = getAccessorTypeIndex(type);
assert type == int.class ||
type == double.class ||
type == Object.class :
"invalid getter type " + type + " for " + getKey();
checkUndeclared();
final MethodHandle[] getterCache = GETTER_CACHE;
final MethodHandle cachedGetter = getterCache[i];
final MethodHandle getter;
if (cachedGetter != null) {
getter = cachedGetter;
} else {
getter = debug(
createGetter(
getLocalType(),
type,
primitiveGetter,
objectGetter,
INVALID_PROGRAM_POINT),
getLocalType(),
type,
"get");
getterCache[i] = getter;
}
assert getter.type().returnType() == type && getter.type().parameterType(0) == Object.class;
return getter;
}
@Override
public MethodHandle getOptimisticGetter(final Class<?> type, final int programPoint) {
if (objectGetter == null) {
return getOptimisticPrimitiveGetter(type, programPoint);
}
checkUndeclared();
return debug(
createGetter(
getLocalType(),
type,
primitiveGetter,
objectGetter,
programPoint),
getLocalType(),
type,
"get");
}
private MethodHandle getOptimisticPrimitiveGetter(final Class<?> type, final int programPoint) {
final MethodHandle g = getGetter(getLocalType());
return MH.asType(OptimisticReturnFilters.filterOptimisticReturnValue(g, type, programPoint), g.type().changeReturnType(type));
}
private Property getWiderProperty(final Class<?> type) {
return copy(type);
}
private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) {
final PropertyMap newMap = oldMap.replaceProperty(this, newProperty);
assert oldMap.size() > 0;
assert newMap.size() == oldMap.size();
return newMap;
}
private void checkUndeclared() {
if ((getFlags() & NEEDS_DECLARATION) != 0) {
throw ECMAErrors.referenceError("not.defined", getKey().toString());
}
}
@SuppressWarnings("unused")
private static Object replaceMap(final Object sobj, final PropertyMap newMap) {
((ScriptObject)sobj).setMap(newMap);
return sobj;
}
@SuppressWarnings("unused")
private static Object invalidateSwitchPoint(final AccessorProperty property, final Object obj) {
if (!property.builtinSwitchPoint.hasBeenInvalidated()) {
SwitchPoint.invalidateAll(new SwitchPoint[] { property.builtinSwitchPoint });
}
return obj;
}
private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) {
return debug(createSetter(forType, type, primitiveSetter, objectSetter), getLocalType(), type, "set");
}
protected final boolean isUndefined() {
return getLocalType() == null;
}
@Override
public boolean hasNativeSetter() {
return objectSetter != null;
}
@Override
public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) {
checkUndeclared();
final int typeIndex = getAccessorTypeIndex(type);
final int currentTypeIndex = getAccessorTypeIndex(getLocalType());
MethodHandle mh;
if (needsInvalidator(typeIndex, currentTypeIndex)) {
final Property newProperty = getWiderProperty(type);
final PropertyMap newMap = getWiderMap(currentMap, newProperty);
final MethodHandle widerSetter = newProperty.getSetter(type, newMap);
final Class<?> ct = getLocalType();
mh = MH.filterArguments(widerSetter, 0, MH.insertArguments(debugReplace(ct, type, currentMap, newMap) , 1, newMap));
if (ct != null && ct.isPrimitive() && !type.isPrimitive()) {
mh = ObjectClassGenerator.createGuardBoxedPrimitiveSetter(ct, generateSetter(ct, ct), mh);
}
} else {
final Class<?> forType = isUndefined() ? type : getLocalType();
mh = generateSetter(!forType.isPrimitive() ? Object.class : forType, type);
}
if (isBuiltin()) {
mh = MH.filterArguments(mh, 0, debugInvalidate(MH.insertArguments(INVALIDATE_SP, 0, this), getKey().toString()));
}
assert mh.type().returnType() == void.class : mh.type();
return mh;
}
@Override
public final boolean canChangeType() {
if (!hasDualFields()) {
return false;
}
return getLocalType() == null || (getLocalType() != Object.class && (isConfigurable() || isWritable()));
}
private boolean needsInvalidator(final int typeIndex, final int currentTypeIndex) {
return canChangeType() && typeIndex > currentTypeIndex;
}
private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) {
if (!Context.DEBUG || !Global.hasInstance()) {
return mh;
}
final Context context = Context.getContextTrusted();
assert context != null;
return context.addLoggingToHandle(
ObjectClassGenerator.class,
Level.INFO,
mh,
0,
true,
new Supplier<String>() {
@Override
public String get() {
return tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", slot=" + getSlot() + " " + getClass().getSimpleName() + " forType=" + stripName(forType) + ", type=" + stripName(type) + ')';
}
});
}
private MethodHandle debugReplace(final Class<?> oldType, final Class<?> newType, final PropertyMap oldMap, final PropertyMap newMap) {
if (!Context.DEBUG || !Global.hasInstance()) {
return REPLACE_MAP;
}
final Context context = Context.getContextTrusted();
assert context != null;
MethodHandle mh = context.addLoggingToHandle(
ObjectClassGenerator.class,
REPLACE_MAP,
new Supplier<String>() {
@Override
public String get() {
return "Type change for '" + getKey() + "' " + oldType + "=>" + newType;
}
});
mh = context.addLoggingToHandle(
ObjectClassGenerator.class,
Level.FINEST,
mh,
Integer.MAX_VALUE,
false,
new Supplier<String>() {
@Override
public String get() {
return "Setting map " + Debug.id(oldMap) + " => " + Debug.id(newMap) + " " + oldMap + " => " + newMap;
}
});
return mh;
}
private static MethodHandle debugInvalidate(final MethodHandle invalidator, final String key) {
if (!Context.DEBUG || !Global.hasInstance()) {
return invalidator;
}
final Context context = Context.getContextTrusted();
assert context != null;
return context.addLoggingToHandle(
ObjectClassGenerator.class,
invalidator,
new Supplier<String>() {
@Override
public String get() {
return "Field change callback for " + key + " triggered ";
}
});
}
private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) {
return MH.findStatic(LOOKUP, AccessorProperty.class, name, MH.type(rtype, types));
}
}