/**
* 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.builtins;
import static com.github.anba.es6draft.runtime.types.Null.NULL;
import static com.github.anba.es6draft.runtime.types.builtins.OrdinaryFunction.FunctionInitialize;
import java.util.ArrayList;
import java.util.List;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.LexicalEnvironment;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.CompatibilityOption;
import com.github.anba.es6draft.runtime.internal.RuntimeInfo;
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;
/**
* <h1>9 Ordinary and Exotic Objects Behaviours</h1><br>
* <ul>
* <li>9.2 ECMAScript Function Objects
* </ul>
*/
public final class LegacyConstructorFunction extends FunctionObject implements Constructor {
private FunctionObject caller;
private Arguments arguments;
private boolean callerWritable = true;
private boolean argumentsWritable = true;
public static final class Arguments {
private final Object[] arguments;
public Arguments(Object[] arguments) {
this.arguments = arguments;
}
ArgumentsObject createArgumentsObject(ExecutionContext cx, LegacyConstructorFunction callee) {
return ArgumentsObject.CreateMappedArgumentsObject(cx, callee, arguments);
}
}
/**
* Constructs a new legacy Constructor Function object.
*
* @param realm
* the realm object
*/
public LegacyConstructorFunction(Realm realm) {
super(realm);
}
@Override
protected LegacyConstructorFunction allocateNew() {
return FunctionAllocate(getRealm().defaultContext(), getPrototype());
}
private static boolean isNonStrictFunctionOrNull(FunctionObject v) {
return v == null || !v.isStrict();
}
/**
* Returns {@code true} if legacy .arguments property is available for this function object.
*
* @return {@code true} if legacy .arguments is supported
*/
private boolean hasArguments() {
return !isClone() && getRealm().isEnabled(CompatibilityOption.FunctionArguments);
}
/**
* Returns {@code true} if legacy .caller property is available for this function object.
*
* @return {@code true} if legacy .caller is supported
*/
private boolean hasCaller() {
return !isClone() && getRealm().isEnabled(CompatibilityOption.FunctionCaller);
}
/**
* [Called from generated code]
*
* @return the legacy caller value
*/
public FunctionObject getLegacyCaller() {
return caller;
}
/**
* [Called from generated code]
*
* @return the legacy arguments value
*/
public Arguments getLegacyArguments() {
return arguments;
}
/**
* [Called from generated code]
*
* @param caller
* the new caller value
*/
public void setLegacyCaller(FunctionObject caller) {
if (callerWritable) {
this.caller = isNonStrictFunctionOrNull(caller) ? caller : null;
}
}
/**
* [Called from generated code]
*
* @param arguments
* the new arguments value
*/
public void setLegacyArguments(Arguments arguments) {
if (argumentsWritable) {
this.arguments = arguments;
}
}
private Property callerProperty() {
Object callerObj = caller != null ? caller : NULL;
return new Property(callerObj, callerWritable, false, false);
}
private Property argumentsProperty(ExecutionContext cx) {
Object argumentsObj = arguments != null ? arguments.createArgumentsObject(cx, this) : NULL;
return new Property(argumentsObj, argumentsWritable, false, false);
}
@Override
protected boolean has(ExecutionContext cx, String propertyKey) {
if (("arguments".equals(propertyKey) && hasArguments()) || ("caller".equals(propertyKey) && hasCaller())) {
return true;
}
return super.has(cx, propertyKey);
}
@Override
protected boolean hasOwnProperty(ExecutionContext cx, String propertyKey) {
if (("arguments".equals(propertyKey) && hasArguments()) || ("caller".equals(propertyKey) && hasCaller())) {
return true;
}
return super.hasOwnProperty(cx, propertyKey);
}
@Override
protected Property getProperty(ExecutionContext cx, String propertyKey) {
if ("arguments".equals(propertyKey) && hasArguments()) {
return argumentsProperty(cx);
}
if ("caller".equals(propertyKey) && hasCaller()) {
assert isNonStrictFunctionOrNull(caller);
return callerProperty();
}
return ordinaryGetOwnProperty(propertyKey);
}
@Override
protected boolean defineProperty(ExecutionContext cx, String propertyKey, PropertyDescriptor desc) {
// If the property descriptor is compatible and the [[Writable]] field is present, assume
// this call to [[DefineOwnProperty]] is meant to freeze the property value. Also reset the
// property value by setting its value to `null`, so we won't leak previous .arguments or
// .caller objects.
if ("arguments".equals(propertyKey) && hasArguments()) {
boolean compatible = IsCompatiblePropertyDescriptor(isExtensible(), desc, argumentsProperty(cx));
if (compatible && desc.hasWritable() && !desc.isWritable()) {
arguments = null;
argumentsWritable = false;
}
return compatible;
}
if ("caller".equals(propertyKey) && hasCaller()) {
boolean compatible = IsCompatiblePropertyDescriptor(isExtensible(), desc, callerProperty());
if (compatible && desc.hasWritable() && !desc.isWritable()) {
caller = null;
callerWritable = false;
}
return compatible;
}
return super.defineProperty(cx, propertyKey, desc);
}
@Override
protected boolean setPropertyValue(ExecutionContext cx, String propertyKey, Object value, Property current) {
// Disallow direct [[Set]] on .arguments and .caller, but still return `true` so the
// result value is consistent with [[DefineOwnProperty]].
if (("arguments".equals(propertyKey) && hasArguments()) || "caller".equals(propertyKey) && hasCaller()) {
return true;
}
return super.setPropertyValue(cx, propertyKey, value, current);
}
@Override
protected List<Object> getOwnPropertyKeys(ExecutionContext cx) {
boolean hasArguments = hasArguments(), hasCaller = hasCaller();
int extraSlots = 0;
if (hasArguments) {
++extraSlots;
}
if (hasCaller) {
++extraSlots;
}
int totalSize = countProperties(true) + extraSlots;
ArrayList<Object> ownKeys = new ArrayList<>(totalSize);
appendIndexedProperties(ownKeys);
// TODO: add test case for property order
if (hasArguments) {
ownKeys.add("arguments");
}
if (hasCaller) {
ownKeys.add("caller");
}
appendProperties(ownKeys);
appendSymbolProperties(ownKeys);
return ownKeys;
}
/**
* 9.2.1 [[Call]] (thisArgument, argumentsList)
*/
@Override
public Object call(ExecutionContext callerContext, Object thisValue, Object... argumentsList) {
try {
return getCallMethod().invokeExact(this, callerContext, thisValue, argumentsList);
} catch (Throwable e) {
throw FunctionObject.<RuntimeException> rethrow(e);
}
}
/**
* 9.2.1 [[Call]] (thisArgument, argumentsList)
*/
@Override
public Object tailCall(ExecutionContext callerContext, Object thisValue, Object... argumentsList) throws Throwable {
return getTailCallMethod().invokeExact(this, callerContext, thisValue, argumentsList);
}
/**
* 9.2.2 [[Construct]] (argumentsList)
*/
@Override
public ScriptObject construct(ExecutionContext callerContext, Constructor newTarget, Object... argumentsList) {
try {
return (ScriptObject) getConstructMethod().invokeExact(this, callerContext, newTarget, argumentsList);
} catch (Throwable e) {
throw FunctionObject.<RuntimeException> rethrow(e);
}
}
/**
* 9.2.3 FunctionAllocate (functionPrototype, strict [,functionKind] )
*
* @param cx
* the execution context
* @param functionPrototype
* the function prototype
* @return the new function object
*/
public static LegacyConstructorFunction FunctionAllocate(ExecutionContext cx, ScriptObject functionPrototype) {
Realm realm = cx.getRealm();
/* steps 1-5 (implicit) */
/* steps 6-9 */
LegacyConstructorFunction f = new LegacyConstructorFunction(realm);
/* steps 10-14 */
f.allocate(realm, functionPrototype, false, FunctionKind.Normal, ConstructorKind.Base);
/* step 15 */
return f;
}
/**
* 9.2.5 FunctionCreate (kind, ParameterList, Body, Scope, Strict)
*
* @param cx
* the execution context
* @param function
* the function code
* @param scope
* the lexical environment
* @return the new function object
*/
public static LegacyConstructorFunction LegacyFunctionCreate(ExecutionContext cx, RuntimeInfo.Function function,
LexicalEnvironment<?> scope) {
/* step 1 */
ScriptObject prototype = cx.getIntrinsic(Intrinsics.FunctionPrototype);
/* steps 2-3 */
assert !function.isGenerator() && !function.isAsync();
/* step 4 */
LegacyConstructorFunction f = FunctionAllocate(cx, prototype);
/* step 5 */
FunctionInitialize(f, FunctionKind.Normal, function, scope, cx.getCurrentExecutable());
return f;
}
}