/** * Copyright (c) 2012-2016 André Bargull * Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms. * * <https://github.com/anba/es6draft> */ package com.github.anba.es6draft.runtime.internal; import static com.github.anba.es6draft.runtime.types.Null.NULL; import static com.github.anba.es6draft.runtime.types.PropertyDescriptor.AccessorPropertyDescriptor; import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED; import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; import java.util.LinkedHashMap; import java.util.Map.Entry; import java.util.Objects; import com.github.anba.es6draft.Script; import com.github.anba.es6draft.repl.global.StopExecutionException; import com.github.anba.es6draft.runtime.AbstractOperations; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.Realm; import com.github.anba.es6draft.runtime.types.BuiltinSymbol; import com.github.anba.es6draft.runtime.types.Callable; import com.github.anba.es6draft.runtime.types.Constructor; import com.github.anba.es6draft.runtime.types.Intrinsics; import com.github.anba.es6draft.runtime.types.Property; import com.github.anba.es6draft.runtime.types.PropertyDescriptor; import com.github.anba.es6draft.runtime.types.ScriptObject; import com.github.anba.es6draft.runtime.types.Symbol; import com.github.anba.es6draft.runtime.types.Type; import com.github.anba.es6draft.runtime.types.builtins.BuiltinFunction; import com.github.anba.es6draft.runtime.types.builtins.NativeConstructor; import com.github.anba.es6draft.runtime.types.builtins.NativeFunction; import com.github.anba.es6draft.runtime.types.builtins.NativeTailCallFunction; import com.github.anba.es6draft.runtime.types.builtins.OrdinaryConstructorFunction; import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject; /** * Utility class to set-up initial properties for objects */ public final class Properties { private Properties() { } /** * Compatiblity extension marker */ @Documented @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public static @interface CompatibilityExtension { CompatibilityOption value(); } /** * Built-in prototype */ @Documented @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public static @interface Prototype { } /** * Built-in function property */ @Documented @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public static @interface Function { /** * Returns the function name. * * @return the function name */ String name(); /** * Returns the function symbol. * * @return the function symbol */ BuiltinSymbol symbol() default BuiltinSymbol.NONE; /** * Returns the function arity. * * @return the function arity */ int arity(); /** * Returns the function attributes, defaults to <code>{[[Writable]]: true, [[Enumerable]]: * false, [[Configurable]]: true}</code>. * * @return the function attributes */ Attributes attributes() default @Attributes(writable = true, enumerable = false, configurable = true); /** * Returns the optional native function identifier. * * @return the native function identifier */ Class<?> nativeId() default void.class; } /** * Built-in value property */ @Documented @Target({ ElementType.FIELD, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public static @interface Value { /** * Returns the value name. * * @return the value name */ String name(); /** * Returns the value symbol. * * @return the value symbol */ BuiltinSymbol symbol() default BuiltinSymbol.NONE; /** * Returns the value attributes, defaults to <code>{[[Writable]]: true, [[Enumerable]]: * false, [[Configurable]]: true}</code>. * * @return the value attributes */ Attributes attributes() default @Attributes(writable = true, enumerable = false, configurable = true); } /** * Built-in accessor property */ @Documented @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public static @interface Accessor { /** * Returns the accessor name. * * @return the accessor name */ String name(); /** * Returns the accessor symbol. * * @return the accessor symbol */ BuiltinSymbol symbol() default BuiltinSymbol.NONE; enum Type { Getter, Setter } /** * Returns the accessor type. * * @return the accessor type */ Type type(); /** * Returns the accessor attributes, defaults to <code>{[[Enumerable]]: * false, [[Configurable]]: true}</code>. * * @return the accessor attributes */ Attributes attributes() default @Attributes(writable = false /*unused*/, enumerable = false, configurable = true); /** * Returns the optional native function identifier. * * @return the native function identifier */ Class<?> nativeId() default void.class; } /** * Built-in function property as an alias function */ @Documented @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Repeatable(AliasFunctions.class) public static @interface AliasFunction { /** * Returns the function name. * * @return the function name */ String name(); /** * Returns the function symbol. * * @return the function symbol */ BuiltinSymbol symbol() default BuiltinSymbol.NONE; /** * Returns the function attributes, defaults to <code>{[[Writable]]: true, [[Enumerable]]: * false, [[Configurable]]: true}</code>. * * @return the function attributes */ Attributes attributes() default @Attributes(writable = true, enumerable = false, configurable = true); } /** * Built-in function property as an alias function */ @Documented @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public static @interface AliasFunctions { AliasFunction[] value(); } /** * Built-in function property with tail-call */ @Documented @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public static @interface TailCall { } @Documented @Target({ ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) public static @interface Attributes { boolean writable(); boolean enumerable(); boolean configurable(); } @Documented @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public static @interface Optional { /** * Returns the default runtime type. * * @return the default type */ Default value() default Default.Undefined; /** * Returns the default boolean value, only applicable if {@code value()} is * {@link Default#Boolean}. * * @return the default boolean value */ boolean booleanValue() default false; /** * Returns the default string value, only applicable if {@code value()} is * {@link Default#String}. * * @return the default string value */ String stringValue() default ""; /** * Returns the default number value, only applicable if {@code value()} is * {@link Default#Number}. * * @return the default number value */ double numberValue() default Double.NaN; enum Default { NONE, Undefined, Null, Boolean, String, Number; static Object defaultValue(Optional optional) { switch (optional.value()) { case Undefined: return UNDEFINED; case Null: return NULL; case Boolean: return optional.booleanValue(); case Number: return optional.numberValue(); case String: return optional.stringValue(); case NONE: return null; default: throw new AssertionError(); } } } } private static final ClassValue<CompactLayout> internalLayouts = new ClassValue<CompactLayout>() { @Override protected CompactLayout computeValue(Class<?> type) { return createInternalObjectLayout(type); } }; // TODO: Consider adding SoftReference to avoid mem-leaks private static final ClassValue<ObjectLayout> externalLayouts = new ClassValue<ObjectLayout>() { @Override protected ObjectLayout computeValue(Class<?> type) { return createExternalObjectLayout(type, false); } }; private static final ClassValue<ObjectLayout> externalClassLayouts = new ClassValue<ObjectLayout>() { @Override protected ObjectLayout computeValue(Class<?> type) { return createExternalObjectLayout(type, true); } }; private static final class ObjectLayout { LinkedHashMap<Value, Object> values; LinkedHashMap<Function, MethodHandle> functions; LinkedHashMap<Accessor, MethodHandle> accessors; } private enum Tag { Value, Function, Accessor, Alias } private static class PropertyLayout { static final int WRITABLE = 0x01; static final int ENUMERABLE = 0x02; static final int CONFIGURABLE = 0x04; static final int TAILCALL = 0x08; static final int ACCESSOR = 0x00; static final int FUNCTION = 0x10; static final int VALUE = 0x20; static final int ALIAS = 0x30; final int attributes; final String name; final Symbol symbol; PropertyLayout(int tag, Attributes attributes, String name, BuiltinSymbol builtin) { this.attributes = tag | toAttributes(attributes); this.name = name; this.symbol = builtin != BuiltinSymbol.NONE ? builtin.get() : null; } final boolean writable() { return (attributes & WRITABLE) != 0; } final boolean enumerable() { return (attributes & ENUMERABLE) != 0; } final boolean configurable() { return (attributes & CONFIGURABLE) != 0; } final Tag tag() { switch ((attributes >> 4) & 0b11) { case 0: return Tag.Accessor; case 1: return Tag.Function; case 2: return Tag.Value; case 3: return Tag.Alias; } throw new AssertionError(); } private static int toAttributes(Attributes attributes) { int attrs = 0; attrs |= (attributes.writable() ? WRITABLE : 0); attrs |= (attributes.enumerable() ? ENUMERABLE : 0); attrs |= (attributes.configurable() ? CONFIGURABLE : 0); return attrs; } } private static final class AccessorLayout extends PropertyLayout { final Accessor.Type type; final Class<?> nativeId; final MethodHandle methodHandle; final String accessorName; AccessorLayout(Accessor accessor, MethodHandle methodHandle) { super(ACCESSOR, accessor.attributes(), accessor.name(), accessor.symbol()); this.type = accessor.type(); this.nativeId = accessor.nativeId(); this.methodHandle = methodHandle; this.accessorName = accessorName(type, name, symbol); } } private static final class FunctionLayout extends PropertyLayout { final int arity; final Class<?> nativeId; final MethodHandle methodHandle; FunctionLayout(Function function, MethodHandle methodHandle) { super(FUNCTION, function.attributes(), function.name(), function.symbol()); this.arity = function.arity(); this.nativeId = function.nativeId(); this.methodHandle = methodHandle; } FunctionLayout(Function function, TailCall tailCall, MethodHandle methodHandle) { super(FUNCTION | TAILCALL, function.attributes(), function.name(), function.symbol()); this.arity = function.arity(); this.nativeId = function.nativeId(); this.methodHandle = methodHandle; } boolean isTailCall() { return (attributes & TAILCALL) != 0; } } private static final class AliasFunctionLayout extends PropertyLayout { final Object propertyKey; // String|BuiltinSymbol AliasFunctionLayout(AliasFunction alias, Function function) { super(ALIAS, alias.attributes(), alias.name(), alias.symbol()); this.propertyKey = propertyKey(function); } private static Object propertyKey(Function function) { BuiltinSymbol sym = function.symbol(); return sym != BuiltinSymbol.NONE ? sym : function.name(); } } private static final class ValueLayout extends PropertyLayout { final Object rawValue; // Object|MethodHandle ValueLayout(Value value, Object rawValue) { super(VALUE, value.attributes(), value.name(), value.symbol()); this.rawValue = rawValue; } } private static final class CompactLayout { static final Object EMPTY = new Object(); final Object prototype; final ArrayList<PropertyLayout> properties; final CompatibilityOption option; CompactLayout(Object prototype, ArrayList<PropertyLayout> properties, CompatibilityOption option) { this.prototype = prototype; this.properties = properties; this.option = option; } } /** * Sets the {@link Prototype} and creates own properties for {@link Value}, {@link Function} and * {@link Accessor} fields. * * @param realm * the realm instance * @param target * the object instance * @param holder * the class which holds the properties */ public static void createProperties(Realm realm, OrdinaryObject target, Class<?> holder) { assert holder.getName().startsWith(INTERNAL_PACKAGE); createInternalProperties(realm, target, holder); } /** * Sets the {@link Prototype} and creates own properties for {@link Value}, {@link Function} and * {@link Accessor} fields. * * @param <OWNER> * the owner class type * @param cx * the execution context * @param target * the target object instance * @param owner * the owner object instance * @param holder * the class which holds the properties */ public static <OWNER> void createProperties(ExecutionContext cx, ScriptObject target, OWNER owner, Class<OWNER> holder) { assert !holder.getName().startsWith(INTERNAL_PACKAGE); createExternalProperties(cx, target, owner, holder); } /** * Creates a new native function. * * @param <OWNER> * the owner class type * @param cx * the execution context * @param owner * the owner object instance * @param holder * the class which holds the properties * @return the new function object */ public static <OWNER> Callable createFunction(ExecutionContext cx, OWNER owner, Class<OWNER> holder) { assert !holder.getName().startsWith(INTERNAL_PACKAGE); return createExternalFunction(cx, owner, holder); } /** * Creates a new native script class. * * @param cx * the execution context * @param className * the class-name * @param constructorProperties * the class which holds the constructor properties * @param prototypeProperties * the class which holds the prototype properties * @return the new native script class */ public static Constructor createClass(ExecutionContext cx, String className, Class<?> constructorProperties, Class<?> prototypeProperties) { assert !constructorProperties.getName().startsWith(INTERNAL_PACKAGE); assert !prototypeProperties.getName().startsWith(INTERNAL_PACKAGE); return createExternalClass(cx, className, constructorProperties, prototypeProperties); } private static final String INTERNAL_PACKAGE = "com.github.anba.es6draft.runtime.objects."; @SuppressWarnings("unused") private static final class Converter { private final ExecutionContext cx; private final MethodHandle toScriptException; Converter(ExecutionContext cx) { this.cx = cx; this.toScriptException = MethodHandles.insertArguments(ToScriptExceptionMH, 0, cx); } MethodHandle toScriptException() { return toScriptException; } MethodHandle filterFor(Class<?> c) { if (c == Object.class) { return null; } else if (c == Double.TYPE) { return MethodHandles.insertArguments(ToNumberMH, 0, cx); } else if (c == Integer.TYPE) { return MethodHandles.insertArguments(ToInt32MH, 0, cx); } else if (c == Boolean.TYPE) { return ToBooleanMH; } else if (c == String.class) { return MethodHandles.insertArguments(ToFlatStringMH, 0, cx); } else if (c == CharSequence.class) { return MethodHandles.insertArguments(ToStringMH, 0, cx); } else if (c == ScriptObject.class) { return MethodHandles.insertArguments(ToObjectMH, 0, cx); } else if (c == Callable.class) { return MethodHandles.insertArguments(ToCallableMH, 0, cx); } else if (ScriptObject.class.isAssignableFrom(c)) { MethodHandle test = IsInstanceMH.bindTo(c); MethodHandle target = MethodHandles.identity(c); target = target.asType(target.type().changeParameterType(0, Object.class)); MethodHandle fallback = MethodHandles.insertArguments(ThrowTypeErrorIncompatibleMH, 0, cx); fallback = fallback.asType(fallback.type().changeReturnType(c)); return MethodHandles.guardWithTest(test, target, fallback); } throw new IllegalArgumentException(c.toString()); } MethodHandle arrayFilterFor(Class<?> c) { assert c.isArray(); c = c.getComponentType(); if (c == Object.class) { return null; } else if (c == Double.TYPE) { return MethodHandles.insertArguments(ToNumberArrayMH, 0, cx); } else if (c == Boolean.TYPE) { return ToBooleanArrayMH; } else if (c == String.class) { return MethodHandles.insertArguments(ToFlatStringArrayMH, 0, cx); } else if (c == CharSequence.class) { return MethodHandles.insertArguments(ToStringArrayMH, 0, cx); } else if (c == ScriptObject.class) { return MethodHandles.insertArguments(ToObjectArrayMH, 0, cx); } else if (c == Callable.class) { return MethodHandles.insertArguments(ToCallableArrayMH, 0, cx); } throw new IllegalArgumentException(c.toString()); } MethodHandle returnHandle(MethodHandle handle, Class<?> returnType) { if (returnType == Double.TYPE || returnType == Integer.TYPE || returnType == Boolean.TYPE) { return MethodHandles.explicitCastArguments(handle, handle.type().changeReturnType(Object.class)); } else if (returnType == String.class || ScriptObject.class.isAssignableFrom(returnType)) { return MethodHandles.filterReturnValue( MethodHandles.explicitCastArguments(handle, handle.type().changeReturnType(Object.class)), nullFilter()); } else if (returnType == Void.TYPE) { return MethodHandles.filterReturnValue(handle, MethodHandles.constant(Object.class, UNDEFINED)); } else if (returnType == Object.class) { return MethodHandles.filterReturnValue(handle, typeFilter()); } throw new IllegalArgumentException(returnType.toString()); } MethodHandle constructReturnHandle(MethodHandle handle, Class<?> returnType) { if (ScriptObject.class.isAssignableFrom(returnType)) { return MethodHandles.filterReturnValue( handle.asType(handle.type().changeReturnType(ScriptObject.class)), nullObjectFilter()); } else if (returnType == Object.class) { return MethodHandles.filterReturnValue(handle, objectFilter()); } throw new IllegalArgumentException(returnType.toString()); } private MethodHandle nullFilter() { MethodHandle fallback = MethodHandles.dropArguments( MethodHandles.constant(Object.class, NULL), 0, Object.class); return MethodHandles.guardWithTest(isNotNullMH, MethodHandles.identity(Object.class), fallback); } private MethodHandle nullObjectFilter() { MethodHandle fallback = MethodHandles.insertArguments(ThrowTypeErrorNotObjectMH, 0, cx); fallback = fallback.asType(fallback.type().changeParameterType(0, ScriptObject.class)); fallback = fallback.asType(fallback.type().changeReturnType(ScriptObject.class)); return MethodHandles.guardWithTest(isNotNullMH.asType(isNotNullMH.type() .changeParameterType(0, ScriptObject.class)), MethodHandles .identity(ScriptObject.class), fallback); } private MethodHandle typeFilter() { MethodHandle fallback = MethodHandles.insertArguments(ThrowTypeErrorIncompatibleMH, 0, cx); fallback = fallback.asType(fallback.type().changeReturnType(Object.class)); return MethodHandles.guardWithTest(isTypeMH, MethodHandles.identity(Object.class), fallback); } private MethodHandle objectFilter() { MethodHandle target = MethodHandles.identity(ScriptObject.class); target = target.asType(target.type().changeParameterType(0, Object.class)); MethodHandle fallback = MethodHandles.insertArguments(ThrowTypeErrorNotObjectMH, 0, cx); fallback = fallback.asType(fallback.type().changeReturnType(ScriptObject.class)); return MethodHandles.guardWithTest(isObjectMH, target, fallback); } private static final MethodHandle ToBooleanMH, ToNumberMH, ToInt32MH, ToStringMH, ToFlatStringMH, ToObjectMH, ToCallableMH, IsInstanceMH; static { MethodLookup lookup = new MethodLookup(MethodHandles.publicLookup()); ToBooleanMH = lookup.findStatic(AbstractOperations.class, "ToBoolean", MethodType.methodType(Boolean.TYPE, Object.class)); ToNumberMH = lookup.findStatic(AbstractOperations.class, "ToNumber", MethodType.methodType(Double.TYPE, ExecutionContext.class, Object.class)); ToInt32MH = lookup.findStatic(AbstractOperations.class, "ToInt32", MethodType.methodType(Integer.TYPE, ExecutionContext.class, Object.class)); ToStringMH = lookup .findStatic(AbstractOperations.class, "ToString", MethodType.methodType( CharSequence.class, ExecutionContext.class, Object.class)); ToFlatStringMH = lookup.findStatic(AbstractOperations.class, "ToFlatString", MethodType.methodType(String.class, ExecutionContext.class, Object.class)); ToObjectMH = lookup .findStatic(AbstractOperations.class, "ToObject", MethodType.methodType( ScriptObject.class, ExecutionContext.class, Object.class)); ToCallableMH = MethodHandles.permuteArguments(lookup.findStatic(ScriptRuntime.class, "CheckCallable", MethodType.methodType(Callable.class, Object.class, ExecutionContext.class)), MethodType.methodType(Callable.class, ExecutionContext.class, Object.class), 1, 0); IsInstanceMH = lookup.findVirtual(Class.class, "isInstance", MethodType.methodType(Boolean.TYPE, Object.class)); } private static final MethodHandle ToBooleanArrayMH, ToStringArrayMH, ToFlatStringArrayMH, ToNumberArrayMH, ToObjectArrayMH, ToCallableArrayMH, ToScriptExceptionMH, ThrowTypeErrorIncompatibleMH, ThrowTypeErrorNotObjectMH, isNotNullMH, isTypeMH, isObjectMH; static { MethodLookup lookup = new MethodLookup(MethodHandles.lookup()); ToStringArrayMH = lookup.findStatic("ToString", MethodType.methodType( CharSequence[].class, ExecutionContext.class, Object[].class)); ToFlatStringArrayMH = lookup.findStatic("ToFlatString", MethodType.methodType(String[].class, ExecutionContext.class, Object[].class)); ToNumberArrayMH = lookup.findStatic("ToNumber", MethodType.methodType(double[].class, ExecutionContext.class, Object[].class)); ToBooleanArrayMH = lookup.findStatic("ToBoolean", MethodType.methodType(boolean[].class, Object[].class)); ToObjectArrayMH = lookup.findStatic("ToObject", MethodType.methodType( ScriptObject[].class, ExecutionContext.class, Object[].class)); ToCallableArrayMH = lookup .findStatic("ToCallable", MethodType.methodType(Callable[].class, ExecutionContext.class, Object[].class)); ToScriptExceptionMH = lookup.findStatic("ToScriptException", MethodType.methodType( ScriptException.class, ExecutionContext.class, Exception.class)); ThrowTypeErrorIncompatibleMH = lookup.findStatic("throwTypeErrorIncompatible", MethodType.methodType(void.class, ExecutionContext.class, Object.class)); ThrowTypeErrorNotObjectMH = lookup.findStatic("throwTypeErrorNotObject", MethodType.methodType(void.class, ExecutionContext.class, Object.class)); isNotNullMH = lookup.findStatic("isNotNull", MethodType.methodType(boolean.class, Object.class)); isTypeMH = lookup.findStatic(Type.class, "isType", MethodType.methodType(boolean.class, Object.class)); isObjectMH = lookup.findStatic(Type.class, "isObject", MethodType.methodType(boolean.class, Object.class)); } private static boolean[] ToBoolean(Object[] source) { boolean[] target = new boolean[source.length]; for (int i = 0; i < target.length; i++) { target[i] = AbstractOperations.ToBoolean(source[i]); } return target; } private static CharSequence[] ToString(ExecutionContext cx, Object[] source) { CharSequence[] target = new CharSequence[source.length]; for (int i = 0; i < target.length; i++) { target[i] = AbstractOperations.ToString(cx, source[i]); } return target; } private static String[] ToFlatString(ExecutionContext cx, Object[] source) { String[] target = new String[source.length]; for (int i = 0; i < target.length; i++) { target[i] = AbstractOperations.ToFlatString(cx, source[i]); } return target; } private static double[] ToNumber(ExecutionContext cx, Object[] source) { double[] target = new double[source.length]; for (int i = 0; i < target.length; i++) { target[i] = AbstractOperations.ToNumber(cx, source[i]); } return target; } private static ScriptObject[] ToObject(ExecutionContext cx, Object[] source) { ScriptObject[] target = new ScriptObject[source.length]; for (int i = 0; i < target.length; i++) { target[i] = AbstractOperations.ToObject(cx, source[i]); } return target; } private static Callable[] ToCallable(ExecutionContext cx, Object[] source) { Callable[] target = new Callable[source.length]; for (int i = 0; i < target.length; i++) { target[i] = ScriptRuntime.CheckCallable(source[i], cx); } return target; } private static ScriptException ToScriptException(ExecutionContext cx, Exception cause) { if (cause instanceof StopExecutionException) throw (StopExecutionException) cause; if (cause instanceof InternalThrowable) return ((InternalThrowable) cause).toScriptException(cx); String info = Objects.toString(cause.getMessage(), cause.getClass().getSimpleName()); return Errors.newInternalError(cx, cause, Messages.Key.InternalError, info); } private static void throwTypeErrorIncompatible(ExecutionContext cx, Object ignore) { throw Errors.newTypeError(cx, Messages.Key.IncompatibleObject); } private static void throwTypeErrorNotObject(ExecutionContext cx, Object ignore) { throw Errors.newTypeError(cx, Messages.Key.NotObjectType); } private static boolean isNotNull(Object o) { return o != null; } } private static <OWNER> void createExternalProperties(ExecutionContext cx, ScriptObject target, OWNER owner, Class<OWNER> holder) { ObjectLayout layout = externalLayouts.get(holder); Converter converter = new Converter(cx); if (layout.values != null) { createExternalValues(cx, target, owner, layout, converter); } if (layout.functions != null) { createExternalFunctions(cx, target, owner, layout, converter); } if (layout.accessors != null) { createExternalAccessors(cx, target, owner, layout, converter); } } private static <OWNER> NativeFunction createExternalFunction(ExecutionContext cx, OWNER owner, Class<OWNER> holder) { ObjectLayout layout = externalLayouts.get(holder); if (layout.functions == null || layout.functions.size() != 1) { throw new IllegalArgumentException(); } Converter converter = new Converter(cx); Entry<Function, MethodHandle> entry = layout.functions.entrySet().iterator().next(); Function function = entry.getKey(); MethodHandle handle = getInstanceMethodHandle(cx, converter, entry.getValue(), owner); return new NativeFunction(cx.getRealm(), function.name(), function.arity(), handle); } private static <OWNER> void createExternalValues(ExecutionContext cx, ScriptObject target, OWNER owner, ObjectLayout layout, Converter converter) { for (Entry<Value, Object> entry : layout.values.entrySet()) { Value val = entry.getKey(); assert entry.getValue() instanceof MethodHandle; Object value = resolveValue(cx, converter, (MethodHandle) entry.getValue(), owner); defineProperty(cx, target, val.name(), val.symbol(), val.attributes(), value); } } private static <OWNER> void createExternalFunctions(ExecutionContext cx, ScriptObject target, OWNER owner, ObjectLayout layout, Converter converter) { for (Entry<Function, MethodHandle> entry : layout.functions.entrySet()) { MethodHandle handle = getInstanceMethodHandle(cx, converter, entry.getValue(), owner); createExternalFunction(cx, target, entry.getKey(), handle); } } private static <OWNER> void createExternalAccessors(ExecutionContext cx, ScriptObject target, OWNER owner, ObjectLayout layout, Converter converter) { LinkedHashMap<String, PropertyDescriptor> stringProps = new LinkedHashMap<>(); EnumMap<BuiltinSymbol, PropertyDescriptor> symbolProps = new EnumMap<>(BuiltinSymbol.class); for (Entry<Accessor, MethodHandle> entry : layout.accessors.entrySet()) { MethodHandle handle = getInstanceMethodHandle(cx, converter, entry.getValue(), owner); createExternalAccessor(cx, entry.getKey(), handle, stringProps, symbolProps); } defineProperties(cx, target, stringProps, symbolProps); } private static Constructor createExternalClass(ExecutionContext cx, String className, Class<?> constructorProperties, Class<?> prototypeProperties) { ObjectLayout ctorLayout = externalClassLayouts.get(constructorProperties); ObjectLayout protoLayout = externalClassLayouts.get(prototypeProperties); Converter converter = new Converter(cx); ScriptObject[] objects = ScriptRuntime.getDefaultClassProto(cx); ScriptObject constructorParent = objects[0]; OrdinaryObject proto = (OrdinaryObject) objects[1]; assert constructorParent == cx.getIntrinsic(Intrinsics.FunctionPrototype); OrdinaryObject constructor = createConstructor(cx, className, proto, converter, protoLayout); assert constructor instanceof Constructor; if (ctorLayout.functions != null) { createExternalFunctions(cx, constructor, ctorLayout, converter); } if (ctorLayout.accessors != null) { createExternalAccessors(cx, constructor, ctorLayout, converter); } if (protoLayout.functions != null) { createExternalFunctions(cx, proto, protoLayout, converter); } if (protoLayout.accessors != null) { createExternalAccessors(cx, proto, protoLayout, converter); } return (Constructor) constructor; } private static OrdinaryObject createConstructor(ExecutionContext cx, String className, OrdinaryObject proto, Converter converter, ObjectLayout layout) { Entry<Function, MethodHandle> constructorEntry = findConstructor(layout); if (constructorEntry != null) { // User supplied method, perform manual ClassDefinitionEvaluation for constructors Function function = constructorEntry.getKey(); MethodHandle unreflect = constructorEntry.getValue(); MethodHandle callMethod = getConstructorStaticMethodHandle(cx, converter, unreflect, MethodKind.Call); MethodHandle constructMethod = getConstructorStaticMethodHandle(cx, converter, unreflect, MethodKind.Construct); NativeConstructor constructor = new NativeConstructor(cx.getRealm(), className, function.arity(), callMethod, constructMethod); constructor.defineOwnProperty(cx, "prototype", new PropertyDescriptor(proto, false, false, false)); proto.defineOwnProperty(cx, "constructor", new PropertyDescriptor(constructor, true, false, true)); return constructor; } // Create default constructor String sourceText = String.format("(class %s { })", sanitizeName(className)); ScriptLoader scriptLoader = cx.getRealm().getScriptLoader(); Script script = scriptLoader.script(new Source("<Constructor>", 1), sourceText); Object constructor = script.evaluate(cx); assert constructor instanceof OrdinaryConstructorFunction : constructor.getClass(); return (OrdinaryConstructorFunction) constructor; } private static String sanitizeName(String className) { if (className.isEmpty()) { return ""; } StringBuilder sb = new StringBuilder(className.length()); for (int i = 0, codePoint; i < className.length(); i += Character.charCount(codePoint)) { codePoint = className.codePointAt(i); sb.appendCodePoint((i == 0 ? Character.isJavaIdentifierStart(codePoint) : Character .isJavaIdentifierPart(codePoint)) ? codePoint : '_'); } return sb.toString(); } private static Entry<Function, MethodHandle> findConstructor(ObjectLayout layout) { if (layout.functions != null) { for (Entry<Function, MethodHandle> entry : layout.functions.entrySet()) { if ("constructor".equals(entry.getKey().name())) { return entry; } } } return null; } private static void createExternalFunctions(ExecutionContext cx, OrdinaryObject target, ObjectLayout layout, Converter converter) { for (Entry<Function, MethodHandle> entry : layout.functions.entrySet()) { MethodHandle handle = getStaticMethodHandle(cx, converter, entry.getValue()); createExternalFunction(cx, target, entry.getKey(), handle); } } private static void createExternalAccessors(ExecutionContext cx, OrdinaryObject target, ObjectLayout layout, Converter converter) { LinkedHashMap<String, PropertyDescriptor> stringProps = new LinkedHashMap<>(); EnumMap<BuiltinSymbol, PropertyDescriptor> symbolProps = new EnumMap<>(BuiltinSymbol.class); for (Entry<Accessor, MethodHandle> entry : layout.accessors.entrySet()) { MethodHandle handle = getStaticMethodHandle(cx, converter, entry.getValue()); createExternalAccessor(cx, entry.getKey(), handle, stringProps, symbolProps); } defineProperties(cx, target, stringProps, symbolProps); } private static <OWNER> Object resolveValue(ExecutionContext cx, Converter converter, MethodHandle handle, OWNER owner) { if (handle.type().parameterCount() == 1) { try { return converter.returnHandle(handle, handle.type().returnType()).invoke(owner); } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new RuntimeException(e); } } if (handle.type().parameterCount() == 2 && handle.type().parameterType(1) == ExecutionContext.class) { try { return converter.returnHandle(handle, handle.type().returnType()).invoke(owner, cx); } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new RuntimeException(e); } } throw new IllegalArgumentException(handle.type().toString()); } private static void createExternalFunction(ExecutionContext cx, ScriptObject target, Function function, MethodHandle handle) { String name = function.name(); NativeFunction fun = new NativeFunction(cx.getRealm(), name, function.arity(), handle); defineProperty(cx, target, name, function.symbol(), function.attributes(), fun); } private static void createExternalAccessor(ExecutionContext cx, Accessor accessor, MethodHandle mh, LinkedHashMap<String, PropertyDescriptor> stringProps, EnumMap<BuiltinSymbol, PropertyDescriptor> symbolProps) { Accessor.Type type = accessor.type(); String name = accessor.name(); BuiltinSymbol symbol = accessor.symbol(); Attributes attributes = accessor.attributes(); String functionName = accessorName(type, name, symbol); int arity = accessorArity(type); NativeFunction fun = new NativeFunction(cx.getRealm(), functionName, arity, mh); PropertyDescriptor existing; if (symbol == BuiltinSymbol.NONE) { existing = stringProps.get(name); } else { existing = symbolProps.get(symbol); } PropertyDescriptor desc; if (existing != null) { if (attributes.enumerable() != existing.isEnumerable() || attributes.configurable() != existing.isConfigurable()) { throw new IllegalArgumentException(); } if (type == Accessor.Type.Getter ? existing.getGetter() != null : existing.getSetter() != null) { throw new IllegalArgumentException(); } desc = existing; } else { desc = propertyDescriptor(null, null, attributes); } if (type == Accessor.Type.Getter) { desc.setGetter(fun); } else { desc.setSetter(fun); } if (symbol == BuiltinSymbol.NONE) { stringProps.put(name, desc); } else { symbolProps.put(symbol, desc); } } private static ObjectLayout createExternalObjectLayout(Class<?> holder, boolean staticMethods) { try { ObjectLayout layout = new ObjectLayout(); Lookup lookup = MethodHandles.publicLookup(); for (Method method : holder.getDeclaredMethods()) { if (Modifier.isStatic(method.getModifiers()) != staticMethods) continue; Function function = method.getAnnotation(Function.class); Accessor accessor = method.getAnnotation(Accessor.class); Value value = method.getAnnotation(Value.class); if (function != null) { if (accessor != null || value != null) { throw new IllegalArgumentException(); } if (layout.functions == null) { layout.functions = new LinkedHashMap<>(); } layout.functions.put(function, lookup.unreflect(method)); } if (accessor != null) { if (value != null) { throw new IllegalArgumentException(); } if (layout.accessors == null) { layout.accessors = new LinkedHashMap<>(); } layout.accessors.put(accessor, lookup.unreflect(method)); } if (value != null) { if (layout.values == null) { layout.values = new LinkedHashMap<>(); } layout.values.put(value, lookup.unreflect(method)); } } return layout; } catch (IllegalAccessException e) { throw new IllegalArgumentException(e); } } private static CompactLayout createInternalObjectLayout(Class<?> holder) { try { Lookup lookup = MethodHandles.publicLookup(); CompatibilityExtension extension = holder.getAnnotation(CompatibilityExtension.class); CompatibilityOption option = extension != null ? extension.value() : null; Object prototypeValue = CompactLayout.EMPTY; ArrayList<PropertyLayout> properties = new ArrayList<>(); boolean hasProto = false; for (Field field : holder.getDeclaredFields()) { if (!Modifier.isStatic(field.getModifiers())) continue; Value value = field.getAnnotation(Value.class); Prototype prototype = field.getAnnotation(Prototype.class); assert value == null || prototype == null; if (value != null) { properties.add(new ValueLayout(value, getRawValue(field))); } if (prototype != null) { assert !hasProto && (hasProto = true); prototypeValue = getRawValue(field); } } for (Method method : holder.getDeclaredMethods()) { if (!Modifier.isStatic(method.getModifiers())) continue; Function function = method.getAnnotation(Function.class); Accessor accessor = method.getAnnotation(Accessor.class); AliasFunction[] aliases = method.getAnnotationsByType(AliasFunction.class); TailCall tailCall = method.getAnnotation(TailCall.class); Value value = method.getAnnotation(Value.class); assert function == null || (accessor == null && value == null); assert accessor == null || (function == null && value == null); assert value == null || (function == null && accessor == null); assert aliases.length == 0 || function != null; assert tailCall == null || function != null; if (value != null) { MethodHandle mh = getComputedValueMethodHandle(lookup, method); properties.add(new ValueLayout(value, mh)); } else if (accessor != null) { MethodHandle mh = getStaticMethodHandle(lookup, method); properties.add(new AccessorLayout(accessor, mh)); } else if (function != null) { MethodHandle mh = getStaticMethodHandle(lookup, method); if (tailCall == null) { properties.add(new FunctionLayout(function, mh)); } else { properties.add(new FunctionLayout(function, tailCall, mh)); } for (AliasFunction a : aliases) { properties.add(new AliasFunctionLayout(a, function)); } } } return new CompactLayout(prototypeValue, properties, option); } catch (IllegalAccessException e) { throw new IllegalArgumentException(e); } } private static Object getRawValue(Field field) throws IllegalAccessException { assert Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()); return field.get(null); } private static <OWNER> MethodHandle getInstanceMethodHandle(ExecutionContext cx, Converter converter, MethodHandle unreflect, OWNER owner) { // var-args collector flag is not preserved when applying method handle combinators boolean varargs = unreflect.isVarargsCollector(); MethodHandle handle = unreflect.bindTo(owner); boolean callerContext = isCallerSensitive(handle); final int fixedArguments = callerContext ? 1 : 0; handle = bindContext(handle, cx); handle = convertArguments(handle, fixedArguments, varargs, converter); handle = convertReturn(handle, converter); handle = catchExceptions(handle, converter); handle = toCanonical(handle, fixedArguments, varargs, null); if (!callerContext) { handle = MethodHandles.dropArguments(handle, 0, ExecutionContext.class, Object.class); } else { handle = MethodHandles.dropArguments(handle, 1, Object.class); } assert handle.type().parameterCount() == 3; assert handle.type().parameterType(0) == ExecutionContext.class; assert handle.type().parameterType(1) == Object.class; assert handle.type().parameterType(2) == Object[].class; assert handle.type().returnType() == Object.class; return handle; } private enum MethodKind { Default, Call, Construct } private static MethodHandle getStaticMethodHandle(ExecutionContext cx, Converter converter, MethodHandle unreflect) { return getStaticMethodHandle(cx, converter, unreflect, MethodKind.Default); } private static MethodHandle getConstructorStaticMethodHandle(ExecutionContext cx, Converter converter, MethodHandle unreflect, MethodKind methodKind) { return getStaticMethodHandle(cx, converter, unreflect, methodKind); } private static MethodHandle getStaticMethodHandle(ExecutionContext cx, Converter converter, MethodHandle unreflect, MethodKind methodKind) { // var-args collector flag is not preserved when applying method handle combinators boolean varargs = unreflect.isVarargsCollector(); MethodHandle handle = unreflect; boolean callerContext = isCallerSensitive(handle); final int fixedArguments = callerContext ? 2 : 1; final int constructorArgument = callerContext ? 1 : 0; handle = bindContext(handle, cx); if (methodKind == MethodKind.Default) { handle = convertThis(handle, callerContext, converter); } else if (methodKind == MethodKind.Call) { handle = MethodHandles.insertArguments(handle, constructorArgument, (Constructor) null); handle = convertThis(handle, callerContext, converter); } else { assert methodKind == MethodKind.Construct; handle = MethodHandles.insertArguments(handle, constructorArgument + 1, (Object) null); } handle = convertArguments(handle, fixedArguments, varargs, converter); if (methodKind != MethodKind.Construct) { handle = convertReturn(handle, converter); } else { handle = convertConstructReturn(handle, converter); } handle = catchExceptions(handle, converter); handle = toCanonical(handle, fixedArguments, varargs, null); if (!callerContext) { handle = MethodHandles.dropArguments(handle, 0, ExecutionContext.class); } if (methodKind != MethodKind.Construct) { assert handle.type().parameterCount() == 3; assert handle.type().parameterType(0) == ExecutionContext.class; assert handle.type().parameterType(1) == Object.class; assert handle.type().parameterType(2) == Object[].class; assert handle.type().returnType() == Object.class; } else { assert handle.type().parameterCount() == 3; assert handle.type().parameterType(0) == ExecutionContext.class; assert handle.type().parameterType(1) == Constructor.class; assert handle.type().parameterType(2) == Object[].class; assert handle.type().returnType() == ScriptObject.class; } return handle; } private static MethodHandle bindContext(MethodHandle handle, ExecutionContext cx) { MethodType type = handle.type(); if (type.parameterCount() > 0 && ExecutionContext.class.equals(type.parameterType(0))) { handle = MethodHandles.insertArguments(handle, 0, cx); } return handle; } private static boolean isCallerSensitive(MethodHandle handle) { MethodType type = handle.type(); return type.parameterCount() > 1 && ExecutionContext.class.equals(type.parameterType(1)); } private static MethodHandle convertThis(MethodHandle handle, boolean callerContext, Converter converter) { int thisArgument = callerContext ? 1 : 0; if (handle.type().parameterCount() == thisArgument) { handle = MethodHandles.dropArguments(handle, thisArgument, Object.class); } else if (handle.type().parameterType(thisArgument) != Object.class) { handle = MethodHandles.filterArguments(handle, thisArgument, converter.filterFor(handle.type().parameterType(thisArgument))); } return handle; } private static MethodHandle convertArguments(MethodHandle handle, int fixedArguments, boolean varargs, Converter converter) { MethodType type = handle.type(); int pcount = type.parameterCount(); int actual = pcount - fixedArguments - (varargs ? 1 : 0); Class<?>[] params = type.parameterArray(); MethodHandle[] filters = new MethodHandle[pcount]; for (int p = 0; p < actual; ++p) { filters[fixedArguments + p] = converter.filterFor(params[fixedArguments + p]); } if (varargs) { filters[pcount - 1] = converter.arrayFilterFor(params[pcount - 1]); } handle = MethodHandles.filterArguments(handle, 0, filters); return handle; } private static MethodHandle convertReturn(MethodHandle handle, Converter converter) { return converter.returnHandle(handle, handle.type().returnType()); } private static MethodHandle convertConstructReturn(MethodHandle handle, Converter converter) { return converter.constructReturnHandle(handle, handle.type().returnType()); } private static MethodHandle catchExceptions(MethodHandle handle, Converter converter) { MethodHandle thrower = MethodHandles.throwException(handle.type().returnType(), ScriptException.class); thrower = MethodHandles.filterArguments(thrower, 0, converter.toScriptException()); return MethodHandles.catchException(handle, Exception.class, thrower); } private static MethodHandle toCanonical(MethodHandle handle, int fixedArguments, boolean varargs, Method method) { assert !handle.isVarargsCollector(); MethodType type = handle.type(); int actual = type.parameterCount() - fixedArguments - (varargs ? 1 : 0); Object[] defaults = method != null ? methodDefaults(method, fixedArguments, actual) : null; MethodHandle filter = Parameters.filter(actual, varargs, defaults); MethodHandle spreader = MethodHandles.spreadInvoker(type, fixedArguments); spreader = MethodHandles.insertArguments(spreader, 0, handle); spreader = MethodHandles.filterArguments(spreader, fixedArguments, filter); return spreader; } private static MethodHandle getStaticMethodHandle(Lookup lookup, Method method) throws IllegalAccessException { MethodHandle handle = lookup.unreflect(method); MethodType type = handle.type(); Class<?>[] params = type.parameterArray(); int p = 0, pcount = type.parameterCount(); boolean callerContext = false; // First three parameters are (ExecutionContext, ExecutionContext?, Object=ThisValue) if (!(p < pcount && ExecutionContext.class.equals(params[p++]))) { throw new IllegalArgumentException(type.toString()); } if (p < pcount && ExecutionContext.class.equals(params[p])) { callerContext = true; p++; } if (!(p < pcount && Object.class.equals(params[p++]))) { throw new IllegalArgumentException(type.toString()); } // Always required to return Object (for now at least) if (!Object.class.equals(type.returnType())) { throw new IllegalArgumentException(type.toString()); } // Collect remaining arguments into Object[] if (!(p + 1 == pcount && Object[].class.equals(params[p]))) { final int fixedArguments = callerContext ? 3 : 2; final boolean varargs = handle.isVarargsCollector(); // Otherwise all trailing arguments need to be of type Object or Object[] for (; p < pcount; ++p) { if (Object.class.equals(params[p])) { continue; } if (p + 1 == pcount && Object[].class.equals(params[p]) && varargs) { continue; } throw new IllegalArgumentException(type.toString()); } // Trailing Object[] arguments are no longer spread in var-args methods (jdk8u40, jdk9). if (varargs) { handle = handle.asFixedArity(); } // Convert to (ExecutionContext, Object, ...) -> Object handle handle = toCanonical(handle, fixedArguments, varargs, method); } if (!callerContext) { handle = MethodHandles.dropArguments(handle, 1, ExecutionContext.class); } // assert handle.type().parameterCount() == 4; // assert handle.type().parameterType(0) == ExecutionContext.class; // assert handle.type().parameterType(1) == ExecutionContext.class; // assert handle.type().parameterType(2) == Object.class; // assert handle.type().parameterType(3) == Object[].class; // assert handle.type().returnType() == Object.class; return handle; } private static MethodHandle getComputedValueMethodHandle(Lookup lookup, Method method) throws IllegalAccessException { // check: (ExecutionContext) -> Object MethodHandle handle = lookup.unreflect(method); MethodType type = handle.type(); if (type.parameterCount() != 1 || !ExecutionContext.class.equals(type.parameterType(0))) { throw new IllegalArgumentException(handle.toString()); } if (!Object.class.equals(type.returnType())) { throw new IllegalArgumentException(handle.toString()); } return handle; } @SuppressWarnings("unused") private static final class Parameters { private Parameters() { } static MethodHandle filter(int actual, boolean varargs, Object[] defaults) { assert actual >= 0; if (defaults != null && varargs) { return MethodHandles.insertArguments(filterVarArgsDefaults, 0, actual, defaults); } if (defaults != null) { return MethodHandles.insertArguments(filterDefaults, 0, actual, defaults); } if (varargs) { return MethodHandles.insertArguments(filterVarArgs, 0, actual); } if (actual < filters.length) { MethodHandle f = filters[actual]; return f != null ? f : (filters[actual] = MethodHandles.insertArguments(filter, 0, actual)); } return MethodHandles.insertArguments(filter, 0, actual); } private static final MethodHandle filters[] = new MethodHandle[5]; private static final MethodHandle filterVarArgsDefaults; private static final MethodHandle filterDefaults; private static final MethodHandle filterVarArgs; private static final MethodHandle filter; static { MethodLookup lookup = new MethodLookup(MethodHandles.lookup()); filterVarArgsDefaults = lookup.findStatic(Parameters.class, "filterVarArgsDefaults", MethodType.methodType(Object[].class, Integer.TYPE, Object[].class, Object[].class)); filterDefaults = lookup.findStatic(Parameters.class, "filterDefaults", MethodType .methodType(Object[].class, Integer.TYPE, Object[].class, Object[].class)); filterVarArgs = lookup.findStatic(Parameters.class, "filterVarArgs", MethodType.methodType(Object[].class, Integer.TYPE, Object[].class)); filter = lookup.findStatic(Parameters.class, "filter", MethodType.methodType(Object[].class, Integer.TYPE, Object[].class)); } private static final Object[] EMPTY_ARRAY = new Object[] {}; private static Object[] filterVarArgsDefaults(int n, Object[] defaultValues, Object[] args) { assert n == defaultValues.length; Object[] arguments = Arrays.copyOf(args, n + 1, Object[].class); if (args.length == n) { arguments[n] = EMPTY_ARRAY; } else if (args.length > n) { arguments[n] = Arrays.copyOfRange(args, n, args.length, Object[].class); } else { int argslen = args.length; System.arraycopy(defaultValues, argslen, arguments, argslen, (n - argslen)); arguments[n] = EMPTY_ARRAY; } return arguments; } private static Object[] filterDefaults(int n, Object[] defaultValues, Object[] args) { assert n == defaultValues.length; if (args.length == n) { return args; } Object[] arguments = Arrays.copyOf(args, n, Object[].class); if (args.length < n) { int argslen = args.length; System.arraycopy(defaultValues, argslen, arguments, argslen, (n - argslen)); } return arguments; } private static Object[] filterVarArgs(int n, Object[] args) { Object[] arguments = Arrays.copyOf(args, n + 1, Object[].class); if (args.length == n) { arguments[n] = EMPTY_ARRAY; } else if (args.length > n) { arguments[n] = Arrays.copyOfRange(args, n, args.length, Object[].class); } else { Arrays.fill(arguments, args.length, n, UNDEFINED); arguments[n] = EMPTY_ARRAY; } return arguments; } private static Object[] filter(int n, Object[] args) { if (args.length == n) { return args; } Object[] arguments = Arrays.copyOf(args, n, Object[].class); if (args.length < n) { Arrays.fill(arguments, args.length, n, UNDEFINED); } return arguments; } } private static void createInternalProperties(Realm realm, OrdinaryObject target, Class<?> holder) { CompactLayout layout = internalLayouts.get(holder); if (layout.option != null && !realm.isEnabled(layout.option)) { // return if extension is not enabled return; } if (layout.prototype != CompactLayout.EMPTY) { createPrototype(realm, target, layout.prototype); } for (PropertyLayout property : layout.properties) { switch (property.tag()) { case Value: createValue(realm, target, (ValueLayout) property); break; case Function: createFunction(realm, target, (FunctionLayout) property); break; case Accessor: createAccessor(realm, target, (AccessorLayout) property); break; case Alias: createAliasFunction(realm, target, (AliasFunctionLayout) property); break; default: throw new AssertionError(); } } } private static void createPrototype(Realm realm, OrdinaryObject target, Object rawValue) { Object value = resolveValue(realm, rawValue); assert value == null || value instanceof ScriptObject; target.infallibleSetPrototype((ScriptObject) value); } private static void createValue(Realm realm, OrdinaryObject target, ValueLayout layout) { Object value = resolveValue(realm, layout.rawValue); defineProperty(target, layout, valueProperty(layout, value)); } private static void createFunction(Realm realm, OrdinaryObject target, FunctionLayout layout) { BuiltinFunction fun; if (layout.isTailCall()) { fun = new NativeTailCallFunction(realm, layout.name, layout.arity, layout.methodHandle); } else { fun = new NativeFunction(realm, layout.name, layout.arity, layout.nativeId, layout.methodHandle); } defineProperty(target, layout, valueProperty(layout, fun)); } private static void createAccessor(Realm realm, OrdinaryObject target, AccessorLayout layout) { int arity = accessorArity(layout.type); NativeFunction fun = new NativeFunction(realm, layout.accessorName, arity, layout.nativeId, layout.methodHandle); Property accessorProperty = lookupOwnProperty(target, layout); if (accessorProperty == null) { defineProperty(target, layout, accessorProperty(layout, fun)); } else { assert accessorProperty.isAccessorDescriptor(); assert accessorProperty.isConfigurable() == layout.configurable(); assert accessorProperty.isEnumerable() == layout.enumerable(); assert (layout.type == Accessor.Type.Getter ? accessorProperty.getGetter() : accessorProperty.getSetter()) == null; accessorProperty.apply(accessorPropertyDescriptor(layout, fun)); } } private static void createAliasFunction(Realm realm, OrdinaryObject target, AliasFunctionLayout layout) { Object propertyKey = layout.propertyKey; Property fun; if (propertyKey instanceof String) { fun = target.lookupOwnProperty((String) propertyKey); } else { fun = target.lookupOwnProperty(((BuiltinSymbol) propertyKey).get()); } assert fun != null : "property not found: " + propertyKey; defineProperty(target, layout, valueProperty(layout, fun.getValue())); } private static Object resolveValue(Realm realm, Object value) { Object resolvedValue; if (value instanceof Intrinsics) { resolvedValue = realm.getIntrinsic((Intrinsics) value); assert resolvedValue != null : "intrinsic not defined: " + value; } else if (value instanceof MethodHandle) { try { resolvedValue = (Object) ((MethodHandle) value).invokeExact(realm.defaultContext()); } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new RuntimeException(e); } } else { resolvedValue = value; } return resolvedValue; } private static Object[] methodDefaults(Method method, int fixedArguments, int actual) { Object[] defaults = null; Annotation[][] parameterAnnotations = method.getParameterAnnotations(); for (int parameter = 0; parameter < actual; ++parameter) { for (Annotation annotation : parameterAnnotations[parameter + fixedArguments]) { if (annotation.annotationType() == Optional.class) { Optional optional = (Optional) annotation; if (defaults == null) { defaults = new Object[actual]; Arrays.fill(defaults, UNDEFINED); } defaults[parameter] = Optional.Default.defaultValue(optional); } } } return defaults; } private static void defineProperty(OrdinaryObject target, PropertyLayout layout, Property property) { if (layout.symbol == null) { target.infallibleDefineOwnProperty(layout.name, property); } else { target.infallibleDefineOwnProperty(layout.symbol, property); } } private static Property lookupOwnProperty(OrdinaryObject target, PropertyLayout layout) { if (layout.symbol == null) { return target.lookupOwnProperty(layout.name); } else { return target.lookupOwnProperty(layout.symbol); } } private static void defineProperty(ExecutionContext cx, ScriptObject target, String name, BuiltinSymbol sym, Attributes attrs, Object value) { if (sym == BuiltinSymbol.NONE) { target.defineOwnProperty(cx, name, propertyDescriptor(value, attrs)); } else { target.defineOwnProperty(cx, sym.get(), propertyDescriptor(value, attrs)); } } private static void defineProperties(ExecutionContext cx, ScriptObject target, LinkedHashMap<String, PropertyDescriptor> stringProperties, EnumMap<BuiltinSymbol, PropertyDescriptor> symbolProperties) { for (Entry<String, PropertyDescriptor> entry : stringProperties.entrySet()) { target.defineOwnProperty(cx, entry.getKey(), entry.getValue()); } for (Entry<BuiltinSymbol, PropertyDescriptor> entry : symbolProperties.entrySet()) { target.defineOwnProperty(cx, entry.getKey().get(), entry.getValue()); } } private static Property valueProperty(PropertyLayout layout, Object value) { return new Property(value, layout.writable(), layout.enumerable(), layout.configurable()); } private static Property accessorProperty(AccessorLayout layout, NativeFunction accessor) { if (layout.type == Accessor.Type.Getter) { return new Property(accessor, null, layout.enumerable(), layout.configurable()); } else { return new Property(null, accessor, layout.enumerable(), layout.configurable()); } } private static PropertyDescriptor propertyDescriptor(Object value, Attributes attrs) { return new PropertyDescriptor(value, attrs.writable(), attrs.enumerable(), attrs.configurable()); } private static PropertyDescriptor propertyDescriptor(Callable getter, Callable setter, Attributes attrs) { return new PropertyDescriptor(getter, setter, attrs.enumerable(), attrs.configurable()); } private static PropertyDescriptor accessorPropertyDescriptor(AccessorLayout layout, NativeFunction accessor) { if (layout.type == Accessor.Type.Getter) { return AccessorPropertyDescriptor(accessor, null, layout.enumerable(), layout.configurable()); } else { return AccessorPropertyDescriptor(null, accessor, layout.enumerable(), layout.configurable()); } } private static String accessorName(Accessor.Type type, String name, BuiltinSymbol symbol) { return symbol == BuiltinSymbol.NONE ? (type == Accessor.Type.Getter ? "get " : "set ") + name : name; } private static String accessorName(Accessor.Type type, String name, Symbol symbol) { return symbol == null ? (type == Accessor.Type.Getter ? "get " : "set ") + name : name; } private static int accessorArity(Accessor.Type type) { return type == Accessor.Type.Getter ? 0 : 1; } }