Commit aa7198df authored by verwaest@chromium.org's avatar verwaest@chromium.org

This CL simplifies var / const by ensuring the behavior is consistent in...

This CL simplifies var / const by ensuring the behavior is consistent in itself, and with regular JS semantics; between regular var/const and eval-ed var/const.

Legacy const is changed so that a declaration declares a configurable, but non-writable, slot, and the initializer reconfigures it (when possible) to non-configurable non-writable. This avoids the need for "the hole" as marker value in JSContextExtensionObjects and GlobalObjects. Undefined is used instead.

BUG=
R=rossberg@chromium.org

Review URL: https://codereview.chromium.org/379893002

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@22379 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 942fe191
......@@ -855,7 +855,7 @@ void FullCodeGenerator::VisitVariableDeclaration(
__ mov(r0, Operand(Smi::FromInt(0))); // Indicates no initial value.
__ Push(cp, r2, r1, r0);
}
__ CallRuntime(Runtime::kDeclareContextSlot, 4);
__ CallRuntime(Runtime::kDeclareLookupSlot, 4);
break;
}
}
......@@ -911,7 +911,7 @@ void FullCodeGenerator::VisitFunctionDeclaration(
__ Push(cp, r2, r1);
// Push initial value for function declaration.
VisitForStackValue(declaration->fun());
__ CallRuntime(Runtime::kDeclareContextSlot, 4);
__ CallRuntime(Runtime::kDeclareLookupSlot, 4);
break;
}
}
......@@ -2458,16 +2458,6 @@ void FullCodeGenerator::EmitStoreToStackLocalOrContextSlot(
}
void FullCodeGenerator::EmitCallStoreContextSlot(
Handle<String> name, StrictMode strict_mode) {
__ push(r0); // Value.
__ mov(r1, Operand(name));
__ mov(r0, Operand(Smi::FromInt(strict_mode)));
__ Push(cp, r1, r0); // Context, name, strict mode.
__ CallRuntime(Runtime::kStoreContextSlot, 4);
}
void FullCodeGenerator::EmitVariableAssignment(Variable* var, Token::Value op) {
if (var->IsUnallocated()) {
// Global var, const, or let.
......@@ -2482,7 +2472,7 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, Token::Value op) {
__ push(r0);
__ mov(r0, Operand(var->name()));
__ Push(cp, r0); // Context and name.
__ CallRuntime(Runtime::kInitializeConstContextSlot, 3);
__ CallRuntime(Runtime::kInitializeLegacyConstLookupSlot, 3);
} else {
ASSERT(var->IsStackAllocated() || var->IsContextSlot());
Label skip;
......@@ -2496,9 +2486,7 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, Token::Value op) {
} else if (var->mode() == LET && op != Token::INIT_LET) {
// Non-initializing assignment to let variable needs a write barrier.
if (var->IsLookupSlot()) {
EmitCallStoreContextSlot(var->name(), strict_mode());
} else {
ASSERT(!var->IsLookupSlot());
ASSERT(var->IsStackAllocated() || var->IsContextSlot());
Label assign;
MemOperand location = VarOperand(var, r1);
......@@ -2511,14 +2499,20 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, Token::Value op) {
// Perform the assignment.
__ bind(&assign);
EmitStoreToStackLocalOrContextSlot(var, location);
}
} else if (!var->is_const_mode() || op == Token::INIT_CONST) {
// Assignment to var or initializing assignment to let/const
// in harmony mode.
if (var->IsLookupSlot()) {
EmitCallStoreContextSlot(var->name(), strict_mode());
ASSERT(op == Token::ASSIGN || op == Token::INIT_VAR ||
op == Token::ASSIGN_ADD);
// Assignment to var.
__ push(r0); // Value.
__ mov(r1, Operand(var->name()));
__ mov(r0, Operand(Smi::FromInt(strict_mode())));
__ Push(cp, r1, r0); // Context, name, strict mode.
__ CallRuntime(Runtime::kStoreLookupSlot, 4);
} else {
// Assignment to var or initializing assignment to let/const in harmony
// mode.
ASSERT((var->IsStackAllocated() || var->IsContextSlot()));
MemOperand location = VarOperand(var, r1);
if (generate_debug_code_ && op == Token::INIT_LET) {
......
......@@ -859,7 +859,7 @@ void FullCodeGenerator::VisitVariableDeclaration(
// Pushing 0 (xzr) indicates no initial value.
__ Push(cp, x2, x1, xzr);
}
__ CallRuntime(Runtime::kDeclareContextSlot, 4);
__ CallRuntime(Runtime::kDeclareLookupSlot, 4);
break;
}
}
......@@ -915,7 +915,7 @@ void FullCodeGenerator::VisitFunctionDeclaration(
__ Push(cp, x2, x1);
// Push initial value for function declaration.
VisitForStackValue(declaration->fun());
__ CallRuntime(Runtime::kDeclareContextSlot, 4);
__ CallRuntime(Runtime::kDeclareLookupSlot, 4);
break;
}
}
......@@ -2142,19 +2142,6 @@ void FullCodeGenerator::EmitStoreToStackLocalOrContextSlot(
}
void FullCodeGenerator::EmitCallStoreContextSlot(
Handle<String> name, StrictMode strict_mode) {
__ Mov(x11, Operand(name));
__ Mov(x10, Smi::FromInt(strict_mode));
// jssp[0] : mode.
// jssp[8] : name.
// jssp[16] : context.
// jssp[24] : value.
__ Push(x0, cp, x11, x10);
__ CallRuntime(Runtime::kStoreContextSlot, 4);
}
void FullCodeGenerator::EmitVariableAssignment(Variable* var,
Token::Value op) {
ASM_LOCATION("FullCodeGenerator::EmitVariableAssignment");
......@@ -2170,7 +2157,7 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
if (var->IsLookupSlot()) {
__ Mov(x1, Operand(var->name()));
__ Push(x0, cp, x1);
__ CallRuntime(Runtime::kInitializeConstContextSlot, 3);
__ CallRuntime(Runtime::kInitializeLegacyConstLookupSlot, 3);
} else {
ASSERT(var->IsStackLocal() || var->IsContextSlot());
Label skip;
......@@ -2183,9 +2170,7 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
} else if (var->mode() == LET && op != Token::INIT_LET) {
// Non-initializing assignment to let variable needs a write barrier.
if (var->IsLookupSlot()) {
EmitCallStoreContextSlot(var->name(), strict_mode());
} else {
ASSERT(!var->IsLookupSlot());
ASSERT(var->IsStackAllocated() || var->IsContextSlot());
Label assign;
MemOperand location = VarOperand(var, x1);
......@@ -2197,14 +2182,23 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
// Perform the assignment.
__ Bind(&assign);
EmitStoreToStackLocalOrContextSlot(var, location);
}
} else if (!var->is_const_mode() || op == Token::INIT_CONST) {
// Assignment to var or initializing assignment to let/const
// in harmony mode.
if (var->IsLookupSlot()) {
EmitCallStoreContextSlot(var->name(), strict_mode());
ASSERT(op == Token::ASSIGN || op == Token::INIT_VAR ||
op == Token::ASSIGN_ADD);
// Assignment to var.
__ Mov(x11, Operand(var->name()));
__ Mov(x10, Smi::FromInt(strict_mode()));
// jssp[0] : mode.
// jssp[8] : name.
// jssp[16] : context.
// jssp[24] : value.
__ Push(x0, cp, x11, x10);
__ CallRuntime(Runtime::kStoreLookupSlot, 4);
} else {
// Assignment to var or initializing assignment to let/const in harmony
// mode.
ASSERT(var->IsStackAllocated() || var->IsContextSlot());
MemOperand location = VarOperand(var, x1);
if (FLAG_debug_code && op == Token::INIT_LET) {
......
......@@ -550,7 +550,6 @@ class FullCodeGenerator: public AstVisitor {
// Helper functions to EmitVariableAssignment
void EmitStoreToStackLocalOrContextSlot(Variable* var,
MemOperand location);
void EmitCallStoreContextSlot(Handle<String> name, StrictMode strict_mode);
// Complete a named property assignment. The receiver is expected on top
// of the stack and the right-hand-side value in the accumulator.
......
......@@ -802,7 +802,7 @@ void FullCodeGenerator::VisitVariableDeclaration(
} else {
__ push(Immediate(Smi::FromInt(0))); // Indicates no initial value.
}
__ CallRuntime(Runtime::kDeclareContextSlot, 4);
__ CallRuntime(Runtime::kDeclareLookupSlot, 4);
break;
}
}
......@@ -855,7 +855,7 @@ void FullCodeGenerator::VisitFunctionDeclaration(
__ push(Immediate(variable->name()));
__ push(Immediate(Smi::FromInt(NONE)));
VisitForStackValue(declaration->fun());
__ CallRuntime(Runtime::kDeclareContextSlot, 4);
__ CallRuntime(Runtime::kDeclareLookupSlot, 4);
break;
}
}
......@@ -2385,16 +2385,6 @@ void FullCodeGenerator::EmitStoreToStackLocalOrContextSlot(
}
void FullCodeGenerator::EmitCallStoreContextSlot(
Handle<String> name, StrictMode strict_mode) {
__ push(eax); // Value.
__ push(esi); // Context.
__ push(Immediate(name));
__ push(Immediate(Smi::FromInt(strict_mode)));
__ CallRuntime(Runtime::kStoreContextSlot, 4);
}
void FullCodeGenerator::EmitVariableAssignment(Variable* var,
Token::Value op) {
if (var->IsUnallocated()) {
......@@ -2410,7 +2400,7 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
__ push(eax);
__ push(esi);
__ push(Immediate(var->name()));
__ CallRuntime(Runtime::kInitializeConstContextSlot, 3);
__ CallRuntime(Runtime::kInitializeLegacyConstLookupSlot, 3);
} else {
ASSERT(var->IsStackLocal() || var->IsContextSlot());
Label skip;
......@@ -2424,9 +2414,7 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
} else if (var->mode() == LET && op != Token::INIT_LET) {
// Non-initializing assignment to let variable needs a write barrier.
if (var->IsLookupSlot()) {
EmitCallStoreContextSlot(var->name(), strict_mode());
} else {
ASSERT(!var->IsLookupSlot());
ASSERT(var->IsStackAllocated() || var->IsContextSlot());
Label assign;
MemOperand location = VarOperand(var, ecx);
......@@ -2437,14 +2425,20 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
__ CallRuntime(Runtime::kThrowReferenceError, 1);
__ bind(&assign);
EmitStoreToStackLocalOrContextSlot(var, location);
}
} else if (!var->is_const_mode() || op == Token::INIT_CONST) {
// Assignment to var or initializing assignment to let/const
// in harmony mode.
if (var->IsLookupSlot()) {
EmitCallStoreContextSlot(var->name(), strict_mode());
ASSERT(op == Token::ASSIGN || op == Token::INIT_VAR ||
op == Token::ASSIGN_ADD);
// Assignment to var.
__ push(eax); // Value.
__ push(esi); // Context.
__ push(Immediate(var->name()));
__ push(Immediate(Smi::FromInt(strict_mode())));
__ CallRuntime(Runtime::kStoreLookupSlot, 4);
} else {
// Assignment to var or initializing assignment to let/const in harmony
// mode.
ASSERT(var->IsStackAllocated() || var->IsContextSlot());
MemOperand location = VarOperand(var, ecx);
if (generate_debug_code_ && op == Token::INIT_LET) {
......
......@@ -1704,7 +1704,7 @@ void Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
// same variable if it is declared several times. This is not a
// semantic issue as long as we keep the source order, but it may be
// a performance issue since it may lead to repeated
// RuntimeHidden_DeclareContextSlot calls.
// RuntimeHidden_DeclareLookupSlot calls.
declaration_scope->AddDeclaration(declaration);
if (mode == CONST_LEGACY && declaration_scope->is_global_scope()) {
......@@ -1718,7 +1718,7 @@ void Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
declaration_scope->strict_mode() == SLOPPY) {
// For variable declarations in a sloppy eval scope the proxy is bound
// to a lookup variable to force a dynamic declaration using the
// DeclareContextSlot runtime function.
// DeclareLookupSlot runtime function.
Variable::Kind kind = Variable::NORMAL;
var = new(zone()) Variable(
declaration_scope, name, mode, true, kind,
......@@ -2189,21 +2189,22 @@ Block* Parser::ParseVariableDeclarations(
if (value != NULL && !inside_with()) {
arguments->Add(value, zone());
value = NULL; // zap the value to avoid the unnecessary assignment
}
// Construct the call to Runtime_InitializeVarGlobal
// and add it to the initialization statement block.
// Note that the function does different things depending on
// the number of arguments (2 or 3).
initialize = factory()->NewCallRuntime(
ast_value_factory_->initialize_var_global_string(),
Runtime::FunctionForId(Runtime::kInitializeVarGlobal),
arguments, pos);
Runtime::FunctionForId(Runtime::kInitializeVarGlobal), arguments,
pos);
} else {
initialize = NULL;
}
}
block->AddStatement(
factory()->NewExpressionStatement(initialize, RelocInfo::kNoPosition),
if (initialize != NULL) {
block->AddStatement(factory()->NewExpressionStatement(
initialize, RelocInfo::kNoPosition),
zone());
}
} else if (needs_init) {
// Constant initializations always assign to the declared constant which
// is always at the function scope level. This is only relevant for
......
......@@ -641,8 +641,8 @@ RUNTIME_FUNCTION(Runtime_CreateGlobalPrivateSymbol) {
ASSERT(symbol->IsUndefined());
symbol = isolate->factory()->NewPrivateSymbol();
Handle<Symbol>::cast(symbol)->set_name(*name);
JSObject::SetProperty(Handle<JSObject>::cast(privates),
name, symbol, NONE, STRICT).Assert();
JSObject::SetProperty(Handle<JSObject>::cast(privates), name, symbol, NONE,
STRICT).Assert();
}
return *symbol;
}
......@@ -2110,6 +2110,50 @@ static Object* ThrowRedeclarationError(Isolate* isolate, Handle<String> name) {
}
// May throw a RedeclarationError.
static Object* DeclareGlobals(Isolate* isolate, Handle<GlobalObject> global,
Handle<String> name, Handle<Object> value,
PropertyAttributes attr, bool is_var,
bool is_const, bool is_function) {
// Do the lookup own properties only, see ES5 erratum.
LookupIterator it(global, name, LookupIterator::CHECK_HIDDEN);
PropertyAttributes old_attributes = JSReceiver::GetPropertyAttributes(&it);
if (old_attributes != ABSENT) {
// The name was declared before; check for conflicting re-declarations.
if (is_const) return ThrowRedeclarationError(isolate, name);
// Skip var re-declarations.
if (is_var) return isolate->heap()->undefined_value();
ASSERT(is_function);
if ((old_attributes & DONT_DELETE) != 0) {
// Only allow reconfiguring globals to functions in user code (no
// natives, which are marked as read-only).
ASSERT((attr & READ_ONLY) == 0);
// Check whether we can reconfigure the existing property into a
// function.
PropertyDetails old_details = it.property_details();
// TODO(verwaest): CALLBACKS invalidly includes ExecutableAccessInfo,
// which are actually data properties, not accessor properties.
if (old_details.IsReadOnly() || old_details.IsDontEnum() ||
old_details.type() == CALLBACKS) {
return ThrowRedeclarationError(isolate, name);
}
// If the existing property is not configurable, keep its attributes. Do
attr = old_attributes;
}
}
// Define or redefine own property.
RETURN_FAILURE_ON_EXCEPTION(isolate, JSObject::SetOwnPropertyIgnoreAttributes(
global, name, value, attr));
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DeclareGlobals) {
HandleScope scope(isolate);
ASSERT(args.length() == 3);
......@@ -2124,181 +2168,42 @@ RUNTIME_FUNCTION(Runtime_DeclareGlobals) {
for (int i = 0; i < length; i += 2) {
HandleScope scope(isolate);
Handle<String> name(String::cast(pairs->get(i)));
Handle<Object> value(pairs->get(i + 1), isolate);
Handle<Object> initial_value(pairs->get(i + 1), isolate);
// We have to declare a global const property. To capture we only
// assign to it when evaluating the assignment for "const x =
// <expr>" the initial value is the hole.
bool is_var = value->IsUndefined();
bool is_const = value->IsTheHole();
bool is_function = value->IsSharedFunctionInfo();
bool is_var = initial_value->IsUndefined();
bool is_const = initial_value->IsTheHole();
bool is_function = initial_value->IsSharedFunctionInfo();
ASSERT(is_var + is_const + is_function == 1);
if (is_var || is_const) {
// Lookup the property in the global object, and don't set the
// value of the variable if the property is already there.
// Do the lookup own properties only, see ES5 erratum.
LookupResult lookup(isolate);
global->LookupOwn(name, &lookup, true);
if (lookup.IsFound()) {
// We found an existing property. Unless it was an interceptor
// that claims the property is absent, skip this declaration.
if (!lookup.IsInterceptor()) continue;
if (JSReceiver::GetPropertyAttributes(global, name) != ABSENT) continue;
// Fall-through and introduce the absent property by using
// SetProperty.
}
} else if (is_function) {
Handle<Object> value;
if (is_function) {
// Copy the function and update its context. Use it as value.
Handle<SharedFunctionInfo> shared =
Handle<SharedFunctionInfo>::cast(value);
Handle<SharedFunctionInfo>::cast(initial_value);
Handle<JSFunction> function =
isolate->factory()->NewFunctionFromSharedFunctionInfo(
shared, context, TENURED);
isolate->factory()->NewFunctionFromSharedFunctionInfo(shared, context,
TENURED);
value = function;
} else {
value = isolate->factory()->undefined_value();
}
LookupResult lookup(isolate);
global->LookupOwn(name, &lookup, true);
// Compute the property attributes. According to ECMA-262,
// the property must be non-configurable except in eval.
int attr = NONE;
bool is_eval = DeclareGlobalsEvalFlag::decode(flags);
if (!is_eval) {
attr |= DONT_DELETE;
}
bool is_native = DeclareGlobalsNativeFlag::decode(flags);
if (is_const || (is_native && is_function)) {
attr |= READ_ONLY;
}
StrictMode strict_mode = DeclareGlobalsStrictMode::decode(flags);
if (!lookup.IsFound() || is_function) {
// If the own property exists, check that we can reconfigure it
// as required for function declarations.
if (lookup.IsFound() && lookup.IsDontDelete()) {
if (lookup.IsReadOnly() || lookup.IsDontEnum() ||
lookup.IsPropertyCallbacks()) {
return ThrowRedeclarationError(isolate, name);
}
// If the existing property is not configurable, keep its attributes.
attr = lookup.GetAttributes();
}
// Define or redefine own property.
RETURN_FAILURE_ON_EXCEPTION(isolate,
JSObject::SetOwnPropertyIgnoreAttributes(
global, name, value, static_cast<PropertyAttributes>(attr)));
} else {
// Do a [[Put]] on the existing (own) property.
RETURN_FAILURE_ON_EXCEPTION(
isolate,
JSObject::SetProperty(
global, name, value, static_cast<PropertyAttributes>(attr),
strict_mode));
}
}
ASSERT(!isolate->has_pending_exception());
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DeclareContextSlot) {
HandleScope scope(isolate);
ASSERT(args.length() == 4);
// Declarations are always made in a function or native context. In the
// case of eval code, the context passed is the context of the caller,
// which may be some nested context and not the declaration context.
CONVERT_ARG_HANDLE_CHECKED(Context, context_arg, 0);
Handle<Context> context(context_arg->declaration_context());
CONVERT_ARG_HANDLE_CHECKED(String, name, 1);
CONVERT_SMI_ARG_CHECKED(mode_arg, 2);
PropertyAttributes mode = static_cast<PropertyAttributes>(mode_arg);
RUNTIME_ASSERT(mode == READ_ONLY || mode == NONE);
CONVERT_ARG_HANDLE_CHECKED(Object, initial_value, 3);
int index;
PropertyAttributes attributes;
ContextLookupFlags flags = DONT_FOLLOW_CHAINS;
BindingFlags binding_flags;
Handle<Object> holder =
context->Lookup(name, flags, &index, &attributes, &binding_flags);
bool is_eval = DeclareGlobalsEvalFlag::decode(flags);
int attr = NONE;
if (is_const) attr |= READ_ONLY;
if (is_function && is_native) attr |= READ_ONLY;
if (!is_const && !is_eval) attr |= DONT_DELETE;
if (attributes != ABSENT) {
// The name was declared before; check for conflicting re-declarations.
// Note: this is actually inconsistent with what happens for globals (where
// we silently ignore such declarations).
if (((attributes & READ_ONLY) != 0) || (mode == READ_ONLY)) {
// Functions are not read-only.
ASSERT(mode != READ_ONLY || initial_value->IsTheHole());
return ThrowRedeclarationError(isolate, name);
}
// Initialize it if necessary.
if (*initial_value != NULL) {
if (index >= 0) {
ASSERT(holder.is_identical_to(context));
if (((attributes & READ_ONLY) == 0) ||
context->get(index)->IsTheHole()) {
context->set(index, *initial_value);
}
} else {
// Slow case: The property is in the context extension object of a
// function context or the global object of a native context.
Handle<JSObject> object = Handle<JSObject>::cast(holder);
RETURN_FAILURE_ON_EXCEPTION(
isolate,
JSReceiver::SetProperty(object, name, initial_value, mode, SLOPPY));
}
}
} else {
// The property is not in the function context. It needs to be
// "declared" in the function context's extension context or as a
// property of the the global object.
Handle<JSObject> object;
if (context->has_extension()) {
object = Handle<JSObject>(JSObject::cast(context->extension()));
} else {
// Context extension objects are allocated lazily.
ASSERT(context->IsFunctionContext());
object = isolate->factory()->NewJSObject(
isolate->context_extension_function());
context->set_extension(*object);
}
ASSERT(*object != NULL);
// Declare the property by setting it to the initial value if provided,
// or undefined, and use the correct mode (e.g. READ_ONLY attribute for
// constant declarations).
ASSERT(!JSReceiver::HasOwnProperty(object, name));
Handle<Object> value(isolate->heap()->undefined_value(), isolate);
if (*initial_value != NULL) value = initial_value;
// Declaring a const context slot is a conflicting declaration if
// there is a callback with that name in a prototype. It is
// allowed to introduce const variables in
// JSContextExtensionObjects. They are treated specially in
// SetProperty and no setters are invoked for those since they are
// not real JSObjects.
if (initial_value->IsTheHole() &&
!object->IsJSContextExtensionObject()) {
LookupResult lookup(isolate);
object->Lookup(name, &lookup);
if (lookup.IsPropertyCallbacks()) {
return ThrowRedeclarationError(isolate, name);
}
}
if (object->IsJSGlobalObject()) {
// Define own property on the global object.
RETURN_FAILURE_ON_EXCEPTION(isolate,
JSObject::SetOwnPropertyIgnoreAttributes(object, name, value, mode));
} else {
RETURN_FAILURE_ON_EXCEPTION(isolate,
JSReceiver::SetProperty(object, name, value, mode, SLOPPY));
}
Object* result = DeclareGlobals(isolate, global, name, value,
static_cast<PropertyAttributes>(attr),
is_var, is_const, is_function);
if (isolate->has_pending_exception()) return result;
}
return isolate->heap()->undefined_value();
......@@ -2313,60 +2218,23 @@ RUNTIME_FUNCTION(Runtime_InitializeVarGlobal) {
// Determine if we need to assign to the variable if it already
// exists (based on the number of arguments).
RUNTIME_ASSERT(args.length() == 2 || args.length() == 3);
bool assign = args.length() == 3;
RUNTIME_ASSERT(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(String, name, 0);
CONVERT_STRICT_MODE_ARG_CHECKED(strict_mode, 1);
// According to ECMA-262, section 12.2, page 62, the property must
// not be deletable.
PropertyAttributes attributes = DONT_DELETE;
// Lookup the property as own on the global object. If it isn't
// there, there is a property with this name in the prototype chain.
// We follow Safari and Firefox behavior and only set the property
// if there is an explicit initialization value that we have
// to assign to the property.
// Note that objects can have hidden prototypes, so we need to traverse
// the whole chain of hidden prototypes to do an 'own' lookup.
LookupResult lookup(isolate);
isolate->context()->global_object()->LookupOwn(name, &lookup, true);
if (lookup.IsInterceptor()) {
Handle<JSObject> holder(lookup.holder());
PropertyAttributes intercepted =
JSReceiver::GetPropertyAttributes(holder, name);
if (intercepted != ABSENT && (intercepted & READ_ONLY) == 0) {
// Found an interceptor that's not read only.
if (assign) {
CONVERT_ARG_HANDLE_CHECKED(Object, value, 2);
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result,
JSObject::SetPropertyForResult(
holder, &lookup, name, value, attributes, strict_mode));
return *result;
} else {
return isolate->heap()->undefined_value();
}
}
}
if (assign) {
CONVERT_ARG_HANDLE_CHECKED(Object, value, 2);
Handle<GlobalObject> global(isolate->context()->global_object());
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result,
JSReceiver::SetProperty(global, name, value, attributes, strict_mode));
JSReceiver::SetProperty(global, name, value, NONE, strict_mode));
return *result;
}
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_InitializeConstGlobal) {
SealHandleScope shs(isolate);
HandleScope handle_scope(isolate);
// All constants are declared with an initial value. The name
// of the constant is the first argument and the initial value
// is the second.
......@@ -2374,71 +2242,112 @@ RUNTIME_FUNCTION(Runtime_InitializeConstGlobal) {
CONVERT_ARG_HANDLE_CHECKED(String, name, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
// Get the current global object from top.
GlobalObject* global = isolate->context()->global_object();
Handle<GlobalObject> global = isolate->global_object();
// According to ECMA-262, section 12.2, page 62, the property must
// not be deletable. Since it's a const, it must be READ_ONLY too.
PropertyAttributes attributes =
static_cast<PropertyAttributes>(DONT_DELETE | READ_ONLY);
// Lookup the property as own on the global object.
LookupIterator it(global, name, LookupIterator::CHECK_HIDDEN);
PropertyAttributes old_attributes = JSReceiver::GetPropertyAttributes(&it);
// Lookup the property as own on the global object. If it isn't
// there, we add the property and take special precautions to always
// add it even in case of callbacks in the prototype chain (this rules
// out using SetProperty). We use SetOwnPropertyIgnoreAttributes instead
LookupResult lookup(isolate);
global->LookupOwn(name, &lookup);
if (!lookup.IsFound()) {
HandleScope handle_scope(isolate);
Handle<GlobalObject> global(isolate->context()->global_object());
JSObject::AddProperty(global, name, value, attributes);
PropertyAttributes attr =
static_cast<PropertyAttributes>(DONT_DELETE | READ_ONLY);
// Set the value if the property is either missing, or the property attributes
// allow setting the value without invoking an accessor.
if (it.IsFound()) {
// Ignore if we can't reconfigure the value.
if ((old_attributes & DONT_DELETE) != 0) {
if ((old_attributes & READ_ONLY) != 0 ||
it.property_kind() == LookupIterator::ACCESSOR) {
return *value;
}
attr = static_cast<PropertyAttributes>(old_attributes | READ_ONLY);
}
}
if (!lookup.IsReadOnly()) {
// Restore global object from context (in case of GC) and continue
// with setting the value.
HandleScope handle_scope(isolate);
Handle<GlobalObject> global(isolate->context()->global_object());
RETURN_FAILURE_ON_EXCEPTION(isolate, JSObject::SetOwnPropertyIgnoreAttributes(
global, name, value, attr));
// BUG 1213575: Handle the case where we have to set a read-only
// property through an interceptor and only do it if it's
// uninitialized, e.g. the hole. Nirk...
// Passing sloppy mode because the property is writable.
RETURN_FAILURE_ON_EXCEPTION(
isolate,
JSReceiver::SetProperty(global, name, value, attributes, SLOPPY));
return *value;
}
RUNTIME_FUNCTION(Runtime_DeclareLookupSlot) {
HandleScope scope(isolate);
ASSERT(args.length() == 4);
// Declarations are always made in a function, native, or global context. In
// the case of eval code, the context passed is the context of the caller,
// which may be some nested context and not the declaration context.
CONVERT_ARG_HANDLE_CHECKED(Context, context_arg, 0);
Handle<Context> context(context_arg->declaration_context());
CONVERT_ARG_HANDLE_CHECKED(String, name, 1);
CONVERT_SMI_ARG_CHECKED(attr_arg, 2);
PropertyAttributes attr = static_cast<PropertyAttributes>(attr_arg);
RUNTIME_ASSERT(attr == READ_ONLY || attr == NONE);
CONVERT_ARG_HANDLE_CHECKED(Object, initial_value, 3);
// TODO(verwaest): Unify the encoding indicating "var" with DeclareGlobals.
bool is_var = *initial_value == NULL;
bool is_const = initial_value->IsTheHole();
bool is_function = initial_value->IsJSFunction();
ASSERT(is_var + is_const + is_function == 1);
int index;
PropertyAttributes attributes;
ContextLookupFlags flags = DONT_FOLLOW_CHAINS;
BindingFlags binding_flags;
Handle<Object> holder =
context->Lookup(name, flags, &index, &attributes, &binding_flags);
Handle<JSObject> object;
Handle<Object> value =
is_function ? initial_value
: Handle<Object>::cast(isolate->factory()->undefined_value());
// TODO(verwaest): This case should probably not be covered by this function,
// but by DeclareGlobals instead.
if ((attributes != ABSENT && holder->IsJSGlobalObject()) ||
(context_arg->has_extension() &&
context_arg->extension()->IsJSGlobalObject())) {
return DeclareGlobals(isolate, Handle<JSGlobalObject>::cast(holder), name,
value, attr, is_var, is_const, is_function);
}
// Set the value, but only if we're assigning the initial value to a
// constant. For now, we determine this by checking if the
// current value is the hole.
// Strict mode handling not needed (const is disallowed in strict mode).
if (lookup.IsField()) {
FixedArray* properties = global->properties();
int index = lookup.GetFieldIndex().outobject_array_index();
if (properties->get(index)->IsTheHole() || !lookup.IsReadOnly()) {
properties->set(index, *value);
if (attributes != ABSENT) {
// The name was declared before; check for conflicting re-declarations.
if (is_const || (attributes & READ_ONLY) != 0) {
return ThrowRedeclarationError(isolate, name);
}
} else if (lookup.IsNormal()) {
if (global->GetNormalizedProperty(&lookup)->IsTheHole() ||
!lookup.IsReadOnly()) {
HandleScope scope(isolate);
JSObject::SetNormalizedProperty(Handle<JSObject>(global), &lookup, value);
// Skip var re-declarations.
if (is_var) return isolate->heap()->undefined_value();
ASSERT(is_function);
if (index >= 0) {
ASSERT(holder.is_identical_to(context));
context->set(index, *initial_value);
return isolate->heap()->undefined_value();
}
object = Handle<JSObject>::cast(holder);
} else if (context->has_extension()) {
object = handle(JSObject::cast(context->extension()));
ASSERT(object->IsJSContextExtensionObject() || object->IsJSGlobalObject());
} else {
// Ignore re-initialization of constants that have already been
// assigned a constant value.
ASSERT(lookup.IsReadOnly() && lookup.IsConstant());
ASSERT(context->IsFunctionContext());
object =
isolate->factory()->NewJSObject(isolate->context_extension_function());
context->set_extension(*object);
}
// Use the set value as the result of the operation.
return *value;
RETURN_FAILURE_ON_EXCEPTION(isolate, JSObject::SetOwnPropertyIgnoreAttributes(
object, name, value, attr));
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_InitializeConstContextSlot) {
RUNTIME_FUNCTION(Runtime_InitializeLegacyConstLookupSlot) {
HandleScope scope(isolate);
ASSERT(args.length() == 3);
......@@ -2451,85 +2360,55 @@ RUNTIME_FUNCTION(Runtime_InitializeConstContextSlot) {
int index;
PropertyAttributes attributes;
ContextLookupFlags flags = FOLLOW_CHAINS;
ContextLookupFlags flags = DONT_FOLLOW_CHAINS;
BindingFlags binding_flags;
Handle<Object> holder =
context->Lookup(name, flags, &index, &attributes, &binding_flags);
if (index >= 0) {
ASSERT(holder->IsContext());
// Property was found in a context. Perform the assignment if we
// found some non-constant or an uninitialized constant.
// Property was found in a context. Perform the assignment if the constant
// was uninitialized.
Handle<Context> context = Handle<Context>::cast(holder);
if ((attributes & READ_ONLY) == 0 || context->get(index)->IsTheHole()) {
context->set(index, *value);
}
ASSERT((attributes & READ_ONLY) != 0);
if (context->get(index)->IsTheHole()) context->set(index, *value);
return *value;
}
// The property could not be found, we introduce it as a property of the
// global object.
if (attributes == ABSENT) {
Handle<JSObject> global = Handle<JSObject>(
isolate->context()->global_object());
// Strict mode not needed (const disallowed in strict mode).
RETURN_FAILURE_ON_EXCEPTION(
isolate,
JSReceiver::SetProperty(global, name, value, NONE, SLOPPY));
return *value;
}
PropertyAttributes attr =
static_cast<PropertyAttributes>(DONT_DELETE | READ_ONLY);
// The property was present in some function's context extension object,
// as a property on the subject of a with, or as a property of the global
// object.
//
// In most situations, eval-introduced consts should still be present in
// the context extension object. However, because declaration and
// initialization are separate, the property might have been deleted
// before we reach the initialization point.
//
// Example:
//
// function f() { eval("delete x; const x;"); }
//
// In that case, the initialization behaves like a normal assignment.
Handle<JSObject> object = Handle<JSObject>::cast(holder);
// Strict mode handling not needed (legacy const is disallowed in strict
// mode).
if (*object == context->extension()) {
// This is the property that was introduced by the const declaration.
// Set it if it hasn't been set before. NOTE: We cannot use
// GetProperty() to get the current value as it 'unholes' the value.
LookupResult lookup(isolate);
object->LookupOwnRealNamedProperty(name, &lookup);
ASSERT(lookup.IsFound()); // the property was declared
ASSERT(lookup.IsReadOnly()); // and it was declared as read-only
// The declared const was configurable, and may have been deleted in the
// meanwhile. If so, re-introduce the variable in the context extension.
ASSERT(context_arg->has_extension());
if (attributes == ABSENT) {
holder = handle(context_arg->extension(), isolate);
} else {
// For JSContextExtensionObjects, the initializer can be run multiple times
// if in a for loop: for (var i = 0; i < 2; i++) { const x = i; }. Only the
// first assignment should go through. For JSGlobalObjects, additionally any
// code can run in between that modifies the declared property.
ASSERT(holder->IsJSGlobalObject() || holder->IsJSContextExtensionObject());
LookupIterator it(holder, name, LookupIterator::CHECK_HIDDEN);
PropertyAttributes old_attributes = JSReceiver::GetPropertyAttributes(&it);
if (lookup.IsField()) {
FixedArray* properties = object->properties();
FieldIndex index = lookup.GetFieldIndex();
ASSERT(!index.is_inobject());
if (properties->get(index.outobject_array_index())->IsTheHole()) {
properties->set(index.outobject_array_index(), *value);
// Ignore if we can't reconfigure the value.
if ((old_attributes & DONT_DELETE) != 0) {
if ((old_attributes & READ_ONLY) != 0 ||
it.property_kind() == LookupIterator::ACCESSOR) {
return *value;
}
} else if (lookup.IsNormal()) {
if (object->GetNormalizedProperty(&lookup)->IsTheHole()) {
JSObject::SetNormalizedProperty(object, &lookup, value);
attr = static_cast<PropertyAttributes>(old_attributes | READ_ONLY);
}
} else {
// We should not reach here. Any real, named property should be
// either a field or a dictionary slot.
UNREACHABLE();
}
} else {
// The property was found on some other object. Set it if it is not a
// read-only property.
if ((attributes & READ_ONLY) == 0) {
// Strict mode not needed (const disallowed in strict mode).
RETURN_FAILURE_ON_EXCEPTION(
isolate,
JSReceiver::SetProperty(object, name, value, attributes, SLOPPY));
}
}
isolate, JSObject::SetOwnPropertyIgnoreAttributes(
Handle<JSObject>::cast(holder), name, value, attr));
return *value;
}
......@@ -9333,7 +9212,7 @@ RUNTIME_FUNCTION_RETURN_PAIR(Runtime_LoadContextSlotNoReferenceError) {
}
RUNTIME_FUNCTION(Runtime_StoreContextSlot) {
RUNTIME_FUNCTION(Runtime_StoreLookupSlot) {
HandleScope scope(isolate);
ASSERT(args.length() == 4);
......@@ -9351,22 +9230,13 @@ RUNTIME_FUNCTION(Runtime_StoreContextSlot) {
&index,
&attributes,
&binding_flags);
// In case of JSProxy, an exception might have been thrown.
if (isolate->has_pending_exception()) return isolate->heap()->exception();
if (index >= 0) {
// The property was found in a context slot.
Handle<Context> context = Handle<Context>::cast(holder);
if (binding_flags == MUTABLE_CHECK_INITIALIZED &&
context->get(index)->IsTheHole()) {
Handle<Object> error =
isolate->factory()->NewReferenceError("not_defined",
HandleVector(&name, 1));
return isolate->Throw(*error);
}
// Ignore if read_only variable.
if (index >= 0) {
if ((attributes & READ_ONLY) == 0) {
// Context is a fixed array and set cannot fail.
context->set(index, *value);
Handle<Context>::cast(holder)->set(index, *value);
} else if (strict_mode == STRICT) {
// Setting read only property in strict mode.
Handle<Object> error =
......@@ -9381,39 +9251,22 @@ RUNTIME_FUNCTION(Runtime_StoreContextSlot) {
// context extension object, a property of the subject of a with, or a
// property of the global object.
Handle<JSReceiver> object;
if (!holder.is_null()) {
if (attributes != ABSENT) {
// The property exists on the holder.
object = Handle<JSReceiver>::cast(holder);
} else {
// The property was not found.
ASSERT(attributes == ABSENT);
if (strict_mode == STRICT) {
// Throw in strict mode (assignment to undefined variable).
Handle<Object> error =
isolate->factory()->NewReferenceError(
} else if (strict_mode == STRICT) {
// If absent in strict mode: throw.
Handle<Object> error = isolate->factory()->NewReferenceError(
"not_defined", HandleVector(&name, 1));
return isolate->Throw(*error);
}
// In sloppy mode, the property is added to the global object.
attributes = NONE;
object = Handle<JSReceiver>(isolate->context()->global_object());
} else {
// If absent in sloppy mode: add the property to the global object.
object = Handle<JSReceiver>(context->global_object());
}
// Set the property if it's not read only or doesn't yet exist.
if ((attributes & READ_ONLY) == 0 ||
(JSReceiver::GetOwnPropertyAttributes(object, name) == ABSENT)) {
RETURN_FAILURE_ON_EXCEPTION(
isolate,
JSReceiver::SetProperty(object, name, value, NONE, strict_mode));
} else if (strict_mode == STRICT && (attributes & READ_ONLY) != 0) {
// Setting read only property in strict mode.
Handle<Object> error =
isolate->factory()->NewTypeError(
"strict_cannot_assign", HandleVector(&name, 1));
return isolate->Throw(*error);
}
isolate, JSReceiver::SetProperty(object, name, value, NONE, strict_mode));
return *value;
}
......
......@@ -312,7 +312,7 @@ namespace internal {
F(GetObjectContextNotifierPerformChange, 1, 1) \
\
/* Harmony typed arrays */ \
F(ArrayBufferInitialize, 2, 1)\
F(ArrayBufferInitialize, 2, 1) \
F(ArrayBufferSliceImpl, 3, 1) \
F(ArrayBufferIsView, 1, 1) \
F(ArrayBufferNeuter, 1, 1) \
......@@ -344,7 +344,7 @@ namespace internal {
F(NewObjectFromBound, 1, 1) \
\
/* Declarations and initialization */ \
F(InitializeVarGlobal, -1 /* 2 or 3 */, 1) \
F(InitializeVarGlobal, 3, 1) \
F(OptimizeObjectForAddingMultipleProperties, 2, 1) \
\
/* Debugging */ \
......@@ -438,7 +438,7 @@ namespace internal {
F(InternalArrayConstructor, -1, 1) \
\
/* Literals */ \
F(MaterializeRegExpLiteral, 4, 1)\
F(MaterializeRegExpLiteral, 4, 1) \
F(CreateObjectLiteral, 4, 1) \
F(CreateArrayLiteral, 4, 1) \
F(CreateArrayLiteralStubBailout, 3, 1) \
......@@ -467,14 +467,14 @@ namespace internal {
F(DeleteContextSlot, 2, 1) \
F(LoadContextSlot, 2, 2) \
F(LoadContextSlotNoReferenceError, 2, 2) \
F(StoreContextSlot, 4, 1) \
F(StoreLookupSlot, 4, 1) \
\
/* Declarations and initialization */ \
F(DeclareGlobals, 3, 1) \
F(DeclareModules, 1, 1) \
F(DeclareContextSlot, 4, 1) \
F(DeclareLookupSlot, 4, 1) \
F(InitializeConstGlobal, 2, 1) \
F(InitializeConstContextSlot, 3, 1) \
F(InitializeLegacyConstLookupSlot, 3, 1) \
\
/* Eval */ \
F(ResolvePossiblyDirectEval, 5, 2) \
......
......@@ -824,7 +824,7 @@ void FullCodeGenerator::VisitVariableDeclaration(
} else {
__ Push(Smi::FromInt(0)); // Indicates no initial value.
}
__ CallRuntime(Runtime::kDeclareContextSlot, 4);
__ CallRuntime(Runtime::kDeclareLookupSlot, 4);
break;
}
}
......@@ -878,7 +878,7 @@ void FullCodeGenerator::VisitFunctionDeclaration(
__ Push(variable->name());
__ Push(Smi::FromInt(NONE));
VisitForStackValue(declaration->fun());
__ CallRuntime(Runtime::kDeclareContextSlot, 4);
__ CallRuntime(Runtime::kDeclareLookupSlot, 4);
break;
}
}
......@@ -2383,16 +2383,6 @@ void FullCodeGenerator::EmitStoreToStackLocalOrContextSlot(
}
void FullCodeGenerator::EmitCallStoreContextSlot(
Handle<String> name, StrictMode strict_mode) {
__ Push(rax); // Value.
__ Push(rsi); // Context.
__ Push(name);
__ Push(Smi::FromInt(strict_mode));
__ CallRuntime(Runtime::kStoreContextSlot, 4);
}
void FullCodeGenerator::EmitVariableAssignment(Variable* var,
Token::Value op) {
if (var->IsUnallocated()) {
......@@ -2408,7 +2398,7 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
__ Push(rax);
__ Push(rsi);
__ Push(var->name());
__ CallRuntime(Runtime::kInitializeConstContextSlot, 3);
__ CallRuntime(Runtime::kInitializeLegacyConstLookupSlot, 3);
} else {
ASSERT(var->IsStackLocal() || var->IsContextSlot());
Label skip;
......@@ -2422,9 +2412,7 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
} else if (var->mode() == LET && op != Token::INIT_LET) {
// Non-initializing assignment to let variable needs a write barrier.
if (var->IsLookupSlot()) {
EmitCallStoreContextSlot(var->name(), strict_mode());
} else {
ASSERT(!var->IsLookupSlot());
ASSERT(var->IsStackAllocated() || var->IsContextSlot());
Label assign;
MemOperand location = VarOperand(var, rcx);
......@@ -2435,14 +2423,20 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
__ CallRuntime(Runtime::kThrowReferenceError, 1);
__ bind(&assign);
EmitStoreToStackLocalOrContextSlot(var, location);
}
} else if (!var->is_const_mode() || op == Token::INIT_CONST) {
// Assignment to var or initializing assignment to let/const
// in harmony mode.
if (var->IsLookupSlot()) {
EmitCallStoreContextSlot(var->name(), strict_mode());
ASSERT(op == Token::ASSIGN || op == Token::INIT_VAR ||
op == Token::ASSIGN_ADD);
// Assignment to var.
__ Push(rax); // Value.
__ Push(rsi); // Context.
__ Push(var->name());
__ Push(Smi::FromInt(strict_mode()));
__ CallRuntime(Runtime::kStoreLookupSlot, 4);
} else {
// Assignment to var or initializing assignment to let/const in harmony
// mode.
ASSERT(var->IsStackAllocated() || var->IsContextSlot());
MemOperand location = VarOperand(var, rcx);
if (generate_debug_code_ && op == Token::INIT_LET) {
......
......@@ -236,17 +236,14 @@ TEST(Unknown) {
{ DeclarationContext context;
context.Check("var x; x",
1, // access
1, // declaration
2, // declaration + initialization
EXPECT_RESULT, Undefined(CcTest::isolate()));
0, 0, EXPECT_RESULT, Undefined(CcTest::isolate()));
}
{ DeclarationContext context;
context.Check("var x = 0; x",
1, // access
2, // declaration + initialization
2, // declaration + initialization
EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
1, // initialization
0, EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
}
{ DeclarationContext context;
......@@ -260,78 +257,19 @@ TEST(Unknown) {
{ DeclarationContext context;
context.Check("const x; x",
1, // access
2, // declaration + initialization
1, // declaration
EXPECT_RESULT, Undefined(CcTest::isolate()));
0, 0, EXPECT_RESULT, Undefined(CcTest::isolate()));
}
{ DeclarationContext context;
// SB 0 - BUG 1213579
context.Check("const x = 0; x",
1, // access
2, // declaration + initialization
1, // declaration
EXPECT_RESULT, Undefined(CcTest::isolate()));
}
}
class PresentPropertyContext: public DeclarationContext {
protected:
virtual v8::Handle<Integer> Query(Local<String> key) {
return Integer::New(isolate(), v8::None);
}
};
TEST(Present) {
HandleScope scope(CcTest::isolate());
{ PresentPropertyContext context;
context.Check("var x; x",
1, // access
0,
2, // declaration + initialization
EXPECT_EXCEPTION); // x is not defined!
}
{ PresentPropertyContext context;
context.Check("var x = 0; x",
1, // access
1, // initialization
2, // declaration + initialization
EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
}
{ PresentPropertyContext context;
context.Check("function x() { }; x",
1, // access
0,
0,
EXPECT_RESULT);
}
{ PresentPropertyContext context;
context.Check("const x; x",
1, // access
1, // initialization
1, // (re-)declaration
EXPECT_RESULT, Undefined(CcTest::isolate()));
}
{ PresentPropertyContext context;
context.Check("const x = 0; x",
1, // access
1, // initialization
1, // (re-)declaration
EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
}
}
class AbsentPropertyContext: public DeclarationContext {
protected:
virtual v8::Handle<Integer> Query(Local<String> key) {
......@@ -348,17 +286,14 @@ TEST(Absent) {
{ AbsentPropertyContext context;
context.Check("var x; x",
1, // access
1, // declaration
2, // declaration + initialization
EXPECT_RESULT, Undefined(isolate));
0, 0, EXPECT_RESULT, Undefined(isolate));
}
{ AbsentPropertyContext context;
context.Check("var x = 0; x",
1, // access
2, // declaration + initialization
2, // declaration + initialization
EXPECT_RESULT, Number::New(isolate, 0));
1, // initialization
0, EXPECT_RESULT, Number::New(isolate, 0));
}
{ AbsentPropertyContext context;
......@@ -372,25 +307,19 @@ TEST(Absent) {
{ AbsentPropertyContext context;
context.Check("const x; x",
1, // access
2, // declaration + initialization
1, // declaration
EXPECT_RESULT, Undefined(isolate));
0, 0, EXPECT_RESULT, Undefined(isolate));
}
{ AbsentPropertyContext context;
context.Check("const x = 0; x",
1, // access
2, // declaration + initialization
1, // declaration
EXPECT_RESULT, Undefined(isolate)); // SB 0 - BUG 1213579
0, 0, EXPECT_RESULT, Number::New(isolate, 0));
}
{ AbsentPropertyContext context;
context.Check("if (false) { var x = 0 }; x",
1, // access
1, // declaration
1, // declaration + initialization
EXPECT_RESULT, Undefined(isolate));
0, 0, EXPECT_RESULT, Undefined(isolate));
}
}
......@@ -439,17 +368,14 @@ TEST(Appearing) {
{ AppearingPropertyContext context;
context.Check("var x; x",
1, // access
1, // declaration
2, // declaration + initialization
EXPECT_RESULT, Undefined(CcTest::isolate()));
0, 0, EXPECT_RESULT, Undefined(CcTest::isolate()));
}
{ AppearingPropertyContext context;
context.Check("var x = 0; x",
1, // access
2, // declaration + initialization
2, // declaration + initialization
EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
1, // initialization
0, EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
}
{ AppearingPropertyContext context;
......@@ -463,78 +389,13 @@ TEST(Appearing) {
{ AppearingPropertyContext context;
context.Check("const x; x",
1, // access
2, // declaration + initialization
1, // declaration
EXPECT_RESULT, Undefined(CcTest::isolate()));
0, 0, EXPECT_RESULT, Undefined(CcTest::isolate()));
}
{ AppearingPropertyContext context;
context.Check("const x = 0; x",
1, // access
2, // declaration + initialization
1, // declaration
EXPECT_RESULT, Undefined(CcTest::isolate()));
// Result is undefined because declaration succeeded but
// initialization to 0 failed (due to context behavior).
}
}
class ReappearingPropertyContext: public DeclarationContext {
public:
enum State {
DECLARE,
DONT_DECLARE,
INITIALIZE,
UNKNOWN
};
ReappearingPropertyContext() : state_(DECLARE) { }
protected:
virtual v8::Handle<Integer> Query(Local<String> key) {
switch (state_) {
case DECLARE:
// Force the first declaration by returning that
// the property is absent.
state_ = DONT_DECLARE;
return Handle<Integer>();
case DONT_DECLARE:
// Ignore the second declaration by returning
// that the property is already there.
state_ = INITIALIZE;
return Integer::New(isolate(), v8::None);
case INITIALIZE:
// Force an initialization by returning that
// the property is absent. This will make sure
// that the setter is called and it will not
// lead to redeclaration conflicts (yet).
state_ = UNKNOWN;
return Handle<Integer>();
default:
CHECK(state_ == UNKNOWN);
break;
}
// Do the lookup in the object.
return Handle<Integer>();
}
private:
State state_;
};
TEST(Reappearing) {
v8::V8::Initialize();
HandleScope scope(CcTest::isolate());
{ ReappearingPropertyContext context;
context.Check("const x; var x = 0",
0,
3, // const declaration+initialization, var initialization
3, // 2 x declaration + var initialization
EXPECT_RESULT, Undefined(CcTest::isolate()));
0, 0, EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
}
}
......@@ -669,19 +530,13 @@ TEST(ExistsInHiddenPrototype) {
HandleScope scope(CcTest::isolate());
{ ExistsInHiddenPrototypeContext context;
context.Check("var x; x",
1, // access
0,
2, // declaration + initialization
EXPECT_EXCEPTION); // x is not defined!
context.Check("var x; x", 0, 0, 0, EXPECT_RESULT,
Undefined(CcTest::isolate()));
}
{ ExistsInHiddenPrototypeContext context;
context.Check("var x = 0; x",
1, // access
1, // initialization
2, // declaration + initialization
EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
context.Check("var x = 0; x", 0, 0, 0, EXPECT_RESULT,
Number::New(CcTest::isolate(), 0));
}
{ ExistsInHiddenPrototypeContext context;
......@@ -694,20 +549,14 @@ TEST(ExistsInHiddenPrototype) {
// TODO(mstarzinger): The semantics of global const is vague.
{ ExistsInHiddenPrototypeContext context;
context.Check("const x; x",
0,
0,
1, // (re-)declaration
EXPECT_RESULT, Undefined(CcTest::isolate()));
context.Check("const x; x", 0, 0, 0, EXPECT_RESULT,
Undefined(CcTest::isolate()));
}
// TODO(mstarzinger): The semantics of global const is vague.
{ ExistsInHiddenPrototypeContext context;
context.Check("const x = 0; x",
0,
0,
1, // (re-)declaration
EXPECT_RESULT, Number::New(CcTest::isolate(), 0));
context.Check("const x = 0; x", 0, 0, 0, EXPECT_RESULT,
Number::New(CcTest::isolate(), 0));
}
}
......@@ -768,10 +617,8 @@ TEST(CrossScriptReferences) {
EXPECT_RESULT, Number::New(isolate, 1));
context.Check("var x = 2; x",
EXPECT_RESULT, Number::New(isolate, 2));
context.Check("const x = 3; x",
EXPECT_RESULT, Number::New(isolate, 3));
context.Check("const x = 4; x",
EXPECT_RESULT, Number::New(isolate, 4));
context.Check("const x = 3; x", EXPECT_EXCEPTION);
context.Check("const x = 4; x", EXPECT_EXCEPTION);
context.Check("x = 5; x",
EXPECT_RESULT, Number::New(isolate, 5));
context.Check("var x = 6; x",
......@@ -787,8 +634,7 @@ TEST(CrossScriptReferences) {
EXPECT_RESULT, Number::New(isolate, 1));
context.Check("var x = 2; x", // assignment ignored
EXPECT_RESULT, Number::New(isolate, 1));
context.Check("const x = 3; x",
EXPECT_RESULT, Number::New(isolate, 1));
context.Check("const x = 3; x", EXPECT_EXCEPTION);
context.Check("x = 4; x", // assignment ignored
EXPECT_RESULT, Number::New(isolate, 1));
context.Check("var x = 5; x", // assignment ignored
......
......@@ -36,29 +36,29 @@ function testIntroduceGlobal() {
var source =
// Deleting 'x' removes the local const property.
"delete x;" +
// Initialization turns into assignment to global 'x'.
// Initialization redefines global 'x'.
"const x = 3; assertEquals(3, x);" +
// No constness of the global 'x'.
"x = 4; assertEquals(4, x);";
// Test constness of the global 'x'.
"x = 4; assertEquals(3, x);";
eval(source);
}
testIntroduceGlobal();
assertEquals(4, x);
assertEquals("undefined", typeof x);
function testAssignExistingGlobal() {
var source =
// Delete 'x' to remove the local const property.
"delete x;" +
// Initialization turns into assignment to global 'x'.
// Initialization redefines global 'x'.
"const x = 5; assertEquals(5, x);" +
// No constness of the global 'x'.
"x = 6; assertEquals(6, x);";
// Test constness of the global 'x'.
"x = 6; assertEquals(5, x);";
eval(source);
}
testAssignExistingGlobal();
assertEquals(6, x);
assertEquals("undefined", typeof x);
function testAssignmentArgument(x) {
function local() {
......@@ -66,7 +66,7 @@ function testAssignmentArgument(x) {
eval(source);
}
local();
assertEquals(7, x);
assertEquals("undefined", typeof x);
}
for (var i = 0; i < 5; i++) {
......@@ -74,17 +74,18 @@ for (var i = 0; i < 5; i++) {
}
%OptimizeFunctionOnNextCall(testAssignmentArgument);
testAssignmentArgument();
assertEquals(6, x);
assertEquals("undefined", typeof x);
__defineSetter__('x', function() { throw 42; });
function testAssignGlobalThrows() {
// Initialization turns into assignment to global 'x' which
// throws an exception.
var source = "delete x; const x = 8";
var finished = false;
function testRedefineGlobal() {
// Initialization redefines global 'x'.
var source = "delete x; const x = 8; finished = true;";
eval(source);
}
assertThrows("testAssignGlobalThrows()");
testRedefineGlobal();
assertTrue(finished);
function testInitFastCaseExtension() {
var source = "const x = 9; assertEquals(9, x); x = 10; assertEquals(9, x)";
......@@ -111,7 +112,7 @@ function testAssignSurroundingContextSlot() {
eval(source);
}
local();
assertEquals(13, x);
assertEquals(12, x);
}
testAssignSurroundingContextSlot();
......@@ -49,37 +49,6 @@ function TestLocal(s,e) {
}
// NOTE: TestGlobal usually only tests the given string in the context
// of a global object in dictionary mode. This is because we use
// delete to get rid of any added properties.
function TestGlobal(s,e) {
// Collect the global properties before the call.
var properties = [];
for (var key in this) properties.push(key);
// Compute the result.
var result;
try {
var code = s + (e ? "; $$$result=" + e : "");
if (this.execScript) {
execScript(code);
} else {
this.eval(code);
}
// Avoid issues if $$$result is not defined by
// reading it through this.
result = this.$$$result;
} catch (x) {
result = CheckException(x);
}
// Get rid of any introduced global properties before
// returning the result.
for (var key in this) {
if (properties.indexOf(key) == -1) delete this[key];
}
return result;
}
function TestContext(s,e) {
try {
// Use a with-statement to force the system to do dynamic
......@@ -98,8 +67,6 @@ function TestAll(expected,s,opt_e) {
var msg = s;
if (opt_e) { e = opt_e; msg += "; " + opt_e; }
assertEquals(expected, TestLocal(s,e), "local:'" + msg + "'");
// Redeclarations of global consts do not throw, they are silently ignored.
assertEquals(42, TestGlobal(s, 42), "global:'" + msg + "'");
assertEquals(expected, TestContext(s,e), "context:'" + msg + "'");
}
......@@ -112,7 +79,7 @@ function TestConflict(def0, def1) {
// Eval first definition.
TestAll("TypeError", 'eval("' + def0 +'"); ' + def1);
// Eval second definition.
TestAll("TypeError", def0 + '; eval("' + def1 + '")');
TestAll("TypeError", def0 + '; eval("' + def1 +'")');
// Eval both definitions separately.
TestAll("TypeError", 'eval("' + def0 +'"); eval("' + def1 + '")');
}
......@@ -234,47 +201,26 @@ var undefined = 1; // Should be silently ignored.
assertEquals(original_undef, undefined, "undefined got overwritten");
undefined = original_undef;
var a; const a; const a = 1;
assertEquals(1, a, "a has wrong value");
a = 2;
assertEquals(2, a, "a should be writable");
var b = 1; const b = 2;
assertEquals(2, b, "b has wrong value");
var c = 1; const c = 2; const c = 3;
assertEquals(3, c, "c has wrong value");
const d = 1; const d = 2;
assertEquals(1, d, "d has wrong value");
const e = 1; var e = 2;
const e = 1; eval('var e = 2');
assertEquals(1, e, "e has wrong value");
const f = 1; const f;
assertEquals(1, f, "f has wrong value");
var g; const g = 1;
assertEquals(1, g, "g has wrong value");
g = 2;
assertEquals(2, g, "g should be writable");
const h; var h = 1;
assertEquals(undefined,h, "h has wrong value");
const h; eval('var h = 1');
assertEquals(undefined, h, "h has wrong value");
eval("Object.defineProperty(this, 'i', { writable: true });"
+ "const i = 7;"
+ "assertEquals(7, i, \"i has wrong value\");");
var global = this;
assertThrows(function() {
Object.defineProperty(global, 'j', { writable: true })
}, TypeError);
const j = 2; // This is what makes the function above throw, because the
// const declaration gets hoisted and makes the property non-configurable.
Object.defineProperty(global, 'j', { value: 100, writable: true });
assertEquals(100, j);
// The const declaration stays configurable, so the declaration above goes
// through even though the const declaration is hoisted above.
const j = 2;
assertEquals(2, j, "j has wrong value");
var k = 1; const k;
// You could argue about the expected result here. For now, the winning
// argument is that "const k;" is equivalent to "const k = undefined;".
assertEquals(undefined, k, "k has wrong value");
var k = 1;
try { eval('const k'); } catch(e) { }
assertEquals(1, k, "k has wrong value");
try { eval('const k = 10'); } catch(e) { }
assertEquals(1, k, "k has wrong value");
......@@ -41,17 +41,20 @@ try { eval("var b"); } catch (e) { caught++; assertTrue(e instanceof TypeError);
assertEquals(0, b);
try { eval("var b = 1"); } catch (e) { caught++; assertTrue(e instanceof TypeError); }
assertEquals(0, b);
assertEquals(0, caught);
eval("var c");
try { eval("const c"); } catch (e) { caught++; assertTrue(e instanceof TypeError); }
assertTrue(typeof c == 'undefined');
assertEquals(1, caught);
try { eval("const c = 1"); } catch (e) { caught++; assertTrue(e instanceof TypeError); }
assertEquals(1, c);
assertEquals(undefined, c);
assertEquals(2, caught);
eval("var d = 0");
try { eval("const d"); } catch (e) { caught++; assertTrue(e instanceof TypeError); }
assertEquals(undefined, d);
assertEquals(0, d);
assertEquals(3, caught);
try { eval("const d = 1"); } catch (e) { caught++; assertTrue(e instanceof TypeError); }
assertEquals(1, d);
assertEquals(0, caught);
assertEquals(0, d);
assertEquals(4, caught);
......@@ -25,8 +25,6 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --es52_globals
var setter_value = 0;
this.__defineSetter__("a", function(v) { setter_value = v; });
......@@ -35,8 +33,9 @@ assertEquals(1, setter_value);
assertFalse("value" in Object.getOwnPropertyDescriptor(this, "a"));
eval("with({}) { eval('var a = 2') }");
assertEquals(2, setter_value);
assertTrue("get" in Object.getOwnPropertyDescriptor(this, "a"));
assertFalse("value" in Object.getOwnPropertyDescriptor(this, "a"));
assertEquals(2, setter_value);
// Function declarations are treated specially to match Safari. We do
// not call setters for them.
......@@ -47,10 +46,8 @@ assertTrue("value" in Object.getOwnPropertyDescriptor(this, "a"));
this.__defineSetter__("b", function(v) { setter_value = v; });
try {
eval("const b = 3");
} catch(e) {
assertUnreachable();
}
assertEquals(3, setter_value);
} catch(e) { }
assertEquals(2, setter_value);
try {
eval("with({}) { eval('const b = 23') }");
......
......@@ -37,4 +37,4 @@ try {
assertTrue(e instanceof TypeError);
caught = true;
}
assertFalse(caught);
assertTrue(caught);
......@@ -2,6 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
__defineSetter__('x', function() { });
Object.defineProperty(this, 'x', {set: function() { }});
Object.freeze(this);
eval('const x = 1');
eval('"use strict"; x = 20;');
......@@ -642,10 +642,6 @@
# We do not correctly handle assignments within "with"
'ecma_3/Statements/12.10-01': [FAIL],
# We do not throw an exception when a const is redeclared.
# (We only fail section 1 of the test.)
'js1_5/Regress/regress-103602': [FAIL],
##################### MOZILLA EXTENSION TESTS #####################
'ecma/extensions/15.1.2.1-1': [FAIL_OK],
......
......@@ -49,7 +49,7 @@ EXPAND_MACROS = [
# to parse them!
EXPECTED_FUNCTION_COUNT = 417
EXPECTED_FUZZABLE_COUNT = 332
EXPECTED_CCTEST_COUNT = 6
EXPECTED_CCTEST_COUNT = 9
EXPECTED_UNKNOWN_COUNT = 4
EXPECTED_BUILTINS_COUNT = 810
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment