/** * 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.types; import static com.github.anba.es6draft.runtime.AbstractOperations.Set; import static com.github.anba.es6draft.runtime.AbstractOperations.ToObject; import static com.github.anba.es6draft.runtime.AbstractOperations.ToString; import static com.github.anba.es6draft.runtime.internal.Errors.newReferenceError; import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError; import com.github.anba.es6draft.runtime.DeclarativeEnvironmentRecord; import com.github.anba.es6draft.runtime.EnvironmentRecord; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.internal.Messages; import com.github.anba.es6draft.runtime.internal.Strings; import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject; /** * <h1>6 ECMAScript Data Types and Values</h1><br> * <h2>6.2 ECMAScript Specification Types</h2> * <ul> * <li>6.2.3 The Reference Specification Type * </ul> */ public abstract class Reference<BASE, NAME> { private Reference() { } /** * GetBase(V) * * @return the reference base */ public abstract BASE getBase(); /** * GetReferencedName(V) * * @return the reference name */ public abstract NAME getReferencedName(); /** * IsStrictReference(V) * * @return {@code true} if is strict mode reference */ public abstract boolean isStrictReference(); /** * HasPrimitiveBase(V) * * @return {@code true} if the base is a primitive value */ public abstract boolean hasPrimitiveBase(); /** * IsPropertyReference(V) * * @return {@code true} if this is a property reference */ public abstract boolean isPropertyReference(); /** * IsUnresolvableReference(V) * * @return {@code true} if the reference is unresolvable */ public abstract boolean isUnresolvableReference(); /** * IsSuperReference(V) * * @return {@code true} if this is a super reference */ public abstract boolean isSuperReference(); /** * [6.2.3.1] GetValue (V) * * @param cx * the execution context * @return the reference value */ public abstract Object getValue(ExecutionContext cx); /** * [6.2.3.2] PutValue (V, W) * * @param w * the new reference value * @param cx * the execution context */ public abstract void putValue(Object w, ExecutionContext cx); /** * [6.2.3.3] GetThisValue (V) * * @return the reference this value */ public abstract Object getThisValue(); /** * [6.2.3.4] InitializeReferencedBinding (V, W) * * * @param w * the new reference value */ public abstract void initializeReferencedBinding(Object w); /** * 12.5.4 The delete Operator<br> * Runtime Semantics: Evaluation 12.5.4.2 * * @param cx * the execution context * @return {@code true} on success */ public abstract boolean delete(ExecutionContext cx); /** * [6.2.3.1] GetValue (V) * * @param v * the reference * @param cx * the execution context * @return the reference value */ public static Object GetValue(Object v, ExecutionContext cx) { /* step 1 (not applicable) */ /* step 2 */ if (!(v instanceof Reference)) return v; /* steps 3-6 */ return ((Reference<?, ?>) v).getValue(cx); } /** * [6.2.3.1] GetValue (V) * * @param v * the reference * @param cx * the execution context * @return the reference value */ public static Object GetValue(Reference<?, ?> v, ExecutionContext cx) { /* steps 1-2 (not applicable) */ /* steps 3-6 */ return v.getValue(cx); } /** * [6.2.3.2] PutValue (V, W) * * @param v * the reference * @param w * the new reference value * @param cx * the execution context */ public static void PutValue(Object v, Object w, ExecutionContext cx) { /* steps 1-2 (not applicable) */ /* step 3 */ if (!(v instanceof Reference)) { throw newReferenceError(cx, Messages.Key.InvalidReference); } /* steps 4-8 */ ((Reference<?, ?>) v).putValue(w, cx); } /** * [6.2.3.2] PutValue (V, W) * * @param v * the reference * @param w * the new reference value * @param cx * the execution context */ public static void PutValue(Reference<?, ?> v, Object w, ExecutionContext cx) { /* steps 1-3 (not applicable) */ /* steps 4-8 */ v.putValue(w, cx); } /** * [6.2.3.3] GetThisValue (V) * * @param v * the reference * @param cx * the execution context * @return the reference this value */ public static Object GetThisValue(ExecutionContext cx, Object v) { /* step 1 */ assert v instanceof Reference && ((Reference<?, ?>) v).isPropertyReference(); /* steps 2-3 */ return ((Reference<?, ?>) v).getThisValue(); } /** * Reference specialization for binding references. */ public static final class BindingReference extends Reference<DeclarativeEnvironmentRecord, String> { private final DeclarativeEnvironmentRecord base; private final DeclarativeEnvironmentRecord.Binding binding; private final String referencedName; private final boolean strictReference; public BindingReference(DeclarativeEnvironmentRecord base, DeclarativeEnvironmentRecord.Binding binding, String referencedName, boolean strictReference) { this.base = base; this.binding = binding; this.referencedName = referencedName; this.strictReference = strictReference; } @Override public DeclarativeEnvironmentRecord getBase() { return base; } @Override public String getReferencedName() { return referencedName; } @Override public boolean isStrictReference() { return strictReference; } @Override public boolean hasPrimitiveBase() { return false; } @Override public boolean isPropertyReference() { return false; } @Override public boolean isUnresolvableReference() { return false; } @Override public boolean isSuperReference() { return false; } @Override public Object getValue(ExecutionContext cx) { if (!binding.isInitialized()) { throw newReferenceError(cx, Messages.Key.UninitializedBinding, referencedName); } return binding.getValue(); } @Override public void putValue(Object w, ExecutionContext cx) { if (!binding.isInitialized()) { throw newReferenceError(cx, Messages.Key.UninitializedBinding, referencedName); } else if (binding.isMutable()) { binding.setValue(w); } else if (strictReference || binding.isStrict()) { throw newTypeError(cx, Messages.Key.ImmutableBinding, referencedName); } } @Override public boolean delete(ExecutionContext cx) { return false; } @Override public Object getThisValue() { throw new AssertionError(); } @Override public void initializeReferencedBinding(Object w) { binding.initialize(w); } } /** * Reference specialization for identifier references. */ public static final class IdentifierReference<RECORD extends EnvironmentRecord> extends Reference<RECORD, String> { private final RECORD base; private final String referencedName; private final boolean strictReference; public IdentifierReference(RECORD base, String referencedName, boolean strictReference) { this.base = base; this.referencedName = referencedName; this.strictReference = strictReference; } @Override public RECORD getBase() { return base; } @Override public String getReferencedName() { return referencedName; } @Override public boolean isStrictReference() { return strictReference; } @Override public boolean hasPrimitiveBase() { return false; } @Override public boolean isPropertyReference() { return false; } @Override public boolean isUnresolvableReference() { return false; } @Override public boolean isSuperReference() { return false; } @Override public Object getValue(ExecutionContext cx) { /* steps 4-5 (not applicable) */ /* steps 3, 6 */ return getBase().getBindingValue(getReferencedName(), isStrictReference()); } @Override public void putValue(Object w, ExecutionContext cx) { assert Type.isType(w) : "invalid value type"; /* steps 5-6 (not applicable) */ /* steps 4, 7 */ getBase().setMutableBinding(getReferencedName(), w, isStrictReference()); } @Override public boolean delete(ExecutionContext cx) { /* steps 1-3 (generated code) */ /* steps 4-5 (not applicable) */ /* step 6 */ return getBase().deleteBinding(getReferencedName()); } @Override public EnvironmentRecord getThisValue() { throw new AssertionError(); } @Override public void initializeReferencedBinding(Object w) { /* steps 1-4 (not applicable) */ /* steps 5-7 */ getBase().initializeBinding(getReferencedName(), w); } } /** * Reference specialization for unresolvable references. */ public static final class UnresolvableReference extends Reference<Void, String> { private final String referencedName; private final boolean strictReference; public UnresolvableReference(String referencedName, boolean strictReference) { this.referencedName = referencedName; this.strictReference = strictReference; } @Override public Void getBase() { return null; } @Override public String getReferencedName() { return referencedName; } @Override public boolean isStrictReference() { return strictReference; } @Override public boolean hasPrimitiveBase() { return false; } @Override public boolean isPropertyReference() { return false; } @Override public boolean isUnresolvableReference() { return true; } @Override public boolean isSuperReference() { return false; } @Override public Object getValue(ExecutionContext cx) { /* step 4 */ throw newReferenceError(cx, Messages.Key.UnresolvableReference, getReferencedName()); } @Override public void putValue(Object w, ExecutionContext cx) { assert Type.isType(w) : "invalid value type"; /* step 5 */ if (isStrictReference()) { throw newReferenceError(cx, Messages.Key.UnresolvableReference, getReferencedName()); } Set(cx, cx.getGlobalObject(), getReferencedName(), w, false); } @Override public boolean delete(ExecutionContext cx) { /* steps 1-3 (generated code) */ /* steps 5-6 (not applicable) */ /* step 4 */ assert !isStrictReference(); return true; } @Override public EnvironmentRecord getThisValue() { throw new AssertionError(); } @Override public void initializeReferencedBinding(Object w) { throw new AssertionError(); } } /** * Reference specialization for property references. * * @param <NAME> * the reference name type */ protected static abstract class PropertyReference<NAME> extends Reference<Object, NAME> { protected final Object base; protected final Type type; protected final boolean strictReference; /** * Constructs a new property reference. * * @param base * the base object * @param strictReference * the strict mode flag */ protected PropertyReference(Object base, boolean strictReference) { this.base = base; this.type = Type.of(base); this.strictReference = strictReference; assert !(type == Type.Undefined || type == Type.Null); } @Override public final Object getBase() { return base; } @Override public final boolean isStrictReference() { return strictReference; } @Override public final boolean hasPrimitiveBase() { return type != Type.Object; } @Override public final boolean isPropertyReference() { return true; } @Override public final boolean isUnresolvableReference() { return false; } @Override public final boolean isSuperReference() { return false; } @Override public final Object getThisValue() { /* steps 1-2 (not applicable) */ /* step 3 */ return getBase(); } @Override public final void initializeReferencedBinding(Object w) { throw new AssertionError(); } protected final OrdinaryObject getPrimitiveBaseProto(ExecutionContext cx) { switch (type) { case Boolean: return cx.getIntrinsic(Intrinsics.BooleanPrototype); case Number: return cx.getIntrinsic(Intrinsics.NumberPrototype); case String: return cx.getIntrinsic(Intrinsics.StringPrototype); case Symbol: return cx.getIntrinsic(Intrinsics.SymbolPrototype); case SIMD: return cx.getIntrinsic(getSIMDBaseProto(base)); default: throw new AssertionError(); } } protected static final OrdinaryObject getPrimitiveBaseProto(ExecutionContext cx, Object base) { switch (Type.of(base)) { case Boolean: return cx.getIntrinsic(Intrinsics.BooleanPrototype); case Number: return cx.getIntrinsic(Intrinsics.NumberPrototype); case String: return cx.getIntrinsic(Intrinsics.StringPrototype); case Symbol: return cx.getIntrinsic(Intrinsics.SymbolPrototype); case SIMD: return cx.getIntrinsic(getSIMDBaseProto(base)); default: throw new AssertionError(); } } private static Intrinsics getSIMDBaseProto(Object base) { switch (Type.simdValue(base).getType()) { case Float64x2: return Intrinsics.SIMD_Float64x2Prototype; case Float32x4: return Intrinsics.SIMD_Float32x4Prototype; case Int32x4: return Intrinsics.SIMD_Int32x4Prototype; case Int16x8: return Intrinsics.SIMD_Int16x8Prototype; case Int8x16: return Intrinsics.SIMD_Int8x16Prototype; case Uint32x4: return Intrinsics.SIMD_Uint32x4Prototype; case Uint16x8: return Intrinsics.SIMD_Uint16x8Prototype; case Uint8x16: return Intrinsics.SIMD_Uint8x16Prototype; case Bool64x2: return Intrinsics.SIMD_Bool64x2Prototype; case Bool32x4: return Intrinsics.SIMD_Bool32x4Prototype; case Bool16x8: return Intrinsics.SIMD_Bool16x8Prototype; case Bool8x16: return Intrinsics.SIMD_Bool8x16Prototype; default: throw new AssertionError(); } } } /** * Reference specialization for indexed property references. */ public static final class PropertyIndexReference extends PropertyReference<String> { private final long referencedName; /** * Constructs a new property reference. * * @param base * the base object * @param referencedName * the referenced name * @param strictReference * the strict mode flag */ public PropertyIndexReference(Object base, long referencedName, boolean strictReference) { super(base, strictReference); this.referencedName = referencedName; } @Override public String getReferencedName() { return ToString(referencedName); } @Override public Object getValue(ExecutionContext cx) { /* steps 4, 6 (not applicable) */ /* steps 3, 5.a */ if (hasPrimitiveBase()) { // base = ToObject(realm, base); return GetValuePrimitive(cx); } /* steps 3, 5.b */ return ((ScriptObject) getBase()).get(cx, referencedName, getThisValue()); } @Override public void putValue(Object w, ExecutionContext cx) { assert Type.isType(w) : "invalid value type"; ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase(); boolean succeeded = base.set(cx, referencedName, w, getThisValue()); if (!succeeded && isStrictReference()) { throw newTypeError(cx, Messages.Key.PropertyNotModifiable, getReferencedName()); } } @Override public boolean delete(ExecutionContext cx) { /* steps 1-3 (generated code) */ /* steps 4, 6 (not applicable) */ /* step 5 */ ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase(); boolean deleteStatus = base.delete(cx, referencedName); if (!deleteStatus && isStrictReference()) { throw newTypeError(cx, Messages.Key.PropertyNotDeletable, getReferencedName()); } return deleteStatus; } private Object GetValuePrimitive(ExecutionContext cx) { long refName = referencedName; if (type == Type.String && 0 <= refName && refName < 0x7FFF_FFFFL) { int index = (int) refName; CharSequence str = Type.stringValue(getBase()); if (index < str.length()) { return String.valueOf(str.charAt(index)); } } return getPrimitiveBaseProto(cx).get(cx, refName, getBase()); } public static Object GetValue(ExecutionContext cx, Object base, long referencedName) { assert !Type.isUndefinedOrNull(base); if (base instanceof ScriptObject) { return ((ScriptObject) base).get(cx, referencedName, base); } return GetValuePrimitive(cx, base, referencedName); } private static Object GetValuePrimitive(ExecutionContext cx, Object base, long referencedName) { if (Type.isString(base) && 0 <= referencedName && referencedName < 0x7FFF_FFFFL) { int index = (int) referencedName; CharSequence str = Type.stringValue(base); if (index < str.length()) { return String.valueOf(str.charAt(index)); } } return getPrimitiveBaseProto(cx, base).get(cx, referencedName, base); } public static void PutValue(ExecutionContext cx, Object base, long referencedName, Object value, boolean strict) { assert !Type.isUndefinedOrNull(base); boolean succeeded; if (base instanceof ScriptObject) { succeeded = ((ScriptObject) base).set(cx, referencedName, value, base); } else { succeeded = ToObject(cx, base).set(cx, referencedName, value, base); } if (!succeeded && strict) { throw newTypeError(cx, Messages.Key.PropertyNotModifiable, ToString(referencedName)); } } } /** * Reference specialization for string-valued property references. */ public static final class PropertyNameReference extends PropertyReference<String> { private final String referencedName; /** * Constructs a new property reference. * * @param base * the base object * @param referencedName * the referenced name * @param strictReference * the strict mode flag */ public PropertyNameReference(Object base, String referencedName, boolean strictReference) { super(base, strictReference); this.referencedName = referencedName; } @Override public String getReferencedName() { return referencedName; } @Override public Object getValue(ExecutionContext cx) { /* steps 4, 6 (not applicable) */ /* steps 3, 5.a */ if (hasPrimitiveBase()) { // base = ToObject(realm, base); return GetValuePrimitive(cx); } /* steps 3, 5.b */ return ((ScriptObject) getBase()).get(cx, referencedName, getThisValue()); } @Override public void putValue(Object w, ExecutionContext cx) { assert Type.isType(w) : "invalid value type"; ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase(); boolean succeeded = base.set(cx, referencedName, w, getThisValue()); if (!succeeded && isStrictReference()) { throw newTypeError(cx, Messages.Key.PropertyNotModifiable, getReferencedName()); } } @Override public boolean delete(ExecutionContext cx) { /* steps 1-3 (generated code) */ /* steps 4, 6 (not applicable) */ /* step 5 */ ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase(); boolean deleteStatus = base.delete(cx, referencedName); if (!deleteStatus && isStrictReference()) { throw newTypeError(cx, Messages.Key.PropertyNotDeletable, getReferencedName()); } return deleteStatus; } private Object GetValuePrimitive(ExecutionContext cx) { if (type == Type.String) { if ("length".equals(referencedName)) { CharSequence str = Type.stringValue(getBase()); return str.length(); } int index = Strings.toStringIndex(referencedName); if (index >= 0) { CharSequence str = Type.stringValue(getBase()); if (index < str.length()) { return String.valueOf(str.charAt(index)); } } } return getPrimitiveBaseProto(cx).get(cx, referencedName, getBase()); } public static Object GetValue(ExecutionContext cx, Object base, String referencedName) { assert !Type.isUndefinedOrNull(base); if (base instanceof ScriptObject) { return ((ScriptObject) base).get(cx, referencedName, base); } return GetValuePrimitive(cx, base, referencedName); } private static Object GetValuePrimitive(ExecutionContext cx, Object base, String referencedName) { if (Type.isString(base)) { if ("length".equals(referencedName)) { CharSequence str = Type.stringValue(base); return str.length(); } int index = Strings.toStringIndex(referencedName); if (index >= 0) { CharSequence str = Type.stringValue(base); if (index < str.length()) { return String.valueOf(str.charAt(index)); } } } return getPrimitiveBaseProto(cx, base).get(cx, referencedName, base); } public static void PutValue(ExecutionContext cx, Object base, String referencedName, Object value, boolean strict) { assert !Type.isUndefinedOrNull(base); boolean succeeded; if (base instanceof ScriptObject) { succeeded = ((ScriptObject) base).set(cx, referencedName, value, base); } else { succeeded = ToObject(cx, base).set(cx, referencedName, value, base); } if (!succeeded && strict) { throw newTypeError(cx, Messages.Key.PropertyNotModifiable, referencedName); } } } /** * Reference specialization for symbol-valued property references. */ public static final class PropertySymbolReference extends PropertyReference<Symbol> { private final Symbol referencedName; /** * Constructs a new property reference. * * @param base * the base object * @param referencedName * the referenced name * @param strictReference * the strict mode flag */ public PropertySymbolReference(Object base, Symbol referencedName, boolean strictReference) { super(base, strictReference); this.referencedName = referencedName; } @Override public Symbol getReferencedName() { return referencedName; } @Override public Object getValue(ExecutionContext cx) { /* steps 4, 6 (not applicable) */ /* steps 3, 5.a */ if (hasPrimitiveBase()) { // base = ToObject(realm, base); return GetValuePrimitive(cx); } /* steps 3, 5.b */ return ((ScriptObject) getBase()).get(cx, referencedName, getThisValue()); } @Override public void putValue(Object w, ExecutionContext cx) { assert Type.isType(w) : "invalid value type"; ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase(); boolean succeeded = base.set(cx, referencedName, w, getThisValue()); if (!succeeded && isStrictReference()) { throw newTypeError(cx, Messages.Key.PropertyNotModifiable, getReferencedName().toString()); } } @Override public boolean delete(ExecutionContext cx) { /* steps 1-3 (generated code) */ /* steps 4, 6 (not applicable) */ /* step 5 */ ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase(); boolean deleteStatus = base.delete(cx, referencedName); if (!deleteStatus && isStrictReference()) { throw newTypeError(cx, Messages.Key.PropertyNotDeletable, getReferencedName().toString()); } return deleteStatus; } private Object GetValuePrimitive(ExecutionContext cx) { return getPrimitiveBaseProto(cx).get(cx, referencedName, getBase()); } public static Object GetValue(ExecutionContext cx, Object base, Symbol referencedName) { assert !Type.isUndefinedOrNull(base); if (base instanceof ScriptObject) { return ((ScriptObject) base).get(cx, referencedName, base); } return GetValuePrimitive(cx, base, referencedName); } private static Object GetValuePrimitive(ExecutionContext cx, Object base, Symbol referencedName) { return getPrimitiveBaseProto(cx, base).get(cx, referencedName, base); } public static void PutValue(ExecutionContext cx, Object base, Symbol referencedName, Object value, boolean strict) { assert !Type.isUndefinedOrNull(base); boolean succeeded; if (base instanceof ScriptObject) { succeeded = ((ScriptObject) base).set(cx, referencedName, value, base); } else { succeeded = ToObject(cx, base).set(cx, referencedName, value, base); } if (!succeeded && strict) { throw newTypeError(cx, Messages.Key.PropertyNotModifiable, referencedName.toString()); } } } /** * Reference specialization for <code>super</code> references. * * @param <NAME> * the reference name type */ protected static abstract class SuperReference<NAME> extends Reference<ScriptObject, NAME> { private final ScriptObject base; private final boolean strictReference; private final Object thisValue; /** * Constructs a new <code>super</code> reference. * * @param base * the base object * @param strictReference * the strict mode flag * @param thisValue * the this-binding */ protected SuperReference(ScriptObject base, boolean strictReference, Object thisValue) { this.base = base; this.strictReference = strictReference; this.thisValue = thisValue; } @Override public final ScriptObject getBase() { return base; } @Override public final boolean isStrictReference() { return strictReference; } @Override public final boolean hasPrimitiveBase() { return false; } @Override public final boolean isPropertyReference() { return true; } @Override public final boolean isUnresolvableReference() { return false; } @Override public final boolean isSuperReference() { return true; } @Override public final Object getThisValue() { /* steps 1, 3 (not applicable) */ /* step 2 */ return thisValue; } @Override public final boolean delete(ExecutionContext cx) { throw newReferenceError(cx, Messages.Key.SuperDelete); } @Override public final void initializeReferencedBinding(Object w) { throw new AssertionError(); } } /** * Reference specialization for string-valued <code>super</code> references. */ public static final class SuperNameReference extends SuperReference<String> { private final String referencedName; /** * Constructs a new <code>super</code> reference. * * @param base * the base object * @param referencedName * the referenced name * @param strictReference * the strict mode flag * @param thisValue * the this-binding */ public SuperNameReference(ScriptObject base, String referencedName, boolean strictReference, Object thisValue) { super(base, strictReference, thisValue); this.referencedName = referencedName; } @Override public String getReferencedName() { return referencedName; } @Override public Object getValue(ExecutionContext cx) { return getBase().get(cx, getReferencedName(), getThisValue()); } @Override public void putValue(Object w, ExecutionContext cx) { assert Type.isType(w) : "invalid value type"; boolean succeeded = getBase().set(cx, getReferencedName(), w, getThisValue()); if (!succeeded && isStrictReference()) { throw newTypeError(cx, Messages.Key.PropertyNotModifiable, getReferencedName()); } } } /** * Reference specialization for symbol-valued <code>super</code> references. */ public static final class SuperSymbolReference extends SuperReference<Symbol> { private final Symbol referencedName; /** * Constructs a new <code>super</code> reference. * * @param base * the base object * @param referencedName * the referenced name * @param strictReference * the strict mode flag * @param thisValue * the this-binding */ public SuperSymbolReference(ScriptObject base, Symbol referencedName, boolean strictReference, Object thisValue) { super(base, strictReference, thisValue); this.referencedName = referencedName; } @Override public Symbol getReferencedName() { return referencedName; } @Override public Object getValue(ExecutionContext cx) { return getBase().get(cx, getReferencedName(), getThisValue()); } @Override public void putValue(Object w, ExecutionContext cx) { assert Type.isType(w) : "invalid value type"; boolean succeeded = getBase().set(cx, getReferencedName(), w, getThisValue()); if (!succeeded && isStrictReference()) { throw newTypeError(cx, Messages.Key.PropertyNotModifiable, getReferencedName().toString()); } } } }