mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
593 lines
17 KiB
593 lines
17 KiB
// Copyright 2007-2008 the V8 project authors. All rights reserved.
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following
|
|
// disclaimer in the documentation and/or other materials provided
|
|
// with the distribution.
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived
|
|
// from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "v8.h"
|
|
|
|
#include "heap.h"
|
|
#include "cctest.h"
|
|
|
|
using namespace v8;
|
|
|
|
|
|
enum Expectations {
|
|
EXPECT_RESULT,
|
|
EXPECT_EXCEPTION
|
|
};
|
|
|
|
|
|
// A DeclarationContext holds a reference to a v8::Context and keeps
|
|
// track of various declaration related counters to make it easier to
|
|
// track if global declarations in the presence of interceptors behave
|
|
// the right way.
|
|
class DeclarationContext {
|
|
public:
|
|
DeclarationContext();
|
|
|
|
virtual ~DeclarationContext() {
|
|
if (is_initialized_) {
|
|
context_->Exit();
|
|
context_.Dispose();
|
|
}
|
|
}
|
|
|
|
void Check(const char* source,
|
|
int get, int set, int has,
|
|
Expectations expectations,
|
|
v8::Handle<Value> value = Local<Value>());
|
|
|
|
int get_count() const { return get_count_; }
|
|
int set_count() const { return set_count_; }
|
|
int query_count() const { return query_count_; }
|
|
|
|
protected:
|
|
virtual v8::Handle<Value> Get(Local<String> key);
|
|
virtual v8::Handle<Value> Set(Local<String> key, Local<Value> value);
|
|
virtual v8::Handle<Integer> Query(Local<String> key);
|
|
|
|
void InitializeIfNeeded();
|
|
|
|
// Get the holder for the interceptor. Default to the instance template
|
|
// but may be overwritten.
|
|
virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) {
|
|
return function->InstanceTemplate();
|
|
}
|
|
|
|
// The handlers are called as static functions that forward
|
|
// to the instance specific virtual methods.
|
|
static v8::Handle<Value> HandleGet(Local<String> key,
|
|
const AccessorInfo& info);
|
|
static v8::Handle<Value> HandleSet(Local<String> key,
|
|
Local<Value> value,
|
|
const AccessorInfo& info);
|
|
static v8::Handle<Integer> HandleQuery(Local<String> key,
|
|
const AccessorInfo& info);
|
|
|
|
private:
|
|
bool is_initialized_;
|
|
Persistent<Context> context_;
|
|
Local<String> property_;
|
|
|
|
int get_count_;
|
|
int set_count_;
|
|
int query_count_;
|
|
|
|
static DeclarationContext* GetInstance(const AccessorInfo& info);
|
|
};
|
|
|
|
|
|
DeclarationContext::DeclarationContext()
|
|
: is_initialized_(false), get_count_(0), set_count_(0), query_count_(0) {
|
|
// Do nothing.
|
|
}
|
|
|
|
|
|
void DeclarationContext::InitializeIfNeeded() {
|
|
if (is_initialized_) return;
|
|
HandleScope scope;
|
|
Local<FunctionTemplate> function = FunctionTemplate::New();
|
|
Local<Value> data = External::New(this);
|
|
GetHolder(function)->SetNamedPropertyHandler(&HandleGet,
|
|
&HandleSet,
|
|
&HandleQuery,
|
|
0, 0,
|
|
data);
|
|
context_ = Context::New(0, function->InstanceTemplate(), Local<Value>());
|
|
context_->Enter();
|
|
is_initialized_ = true;
|
|
}
|
|
|
|
|
|
void DeclarationContext::Check(const char* source,
|
|
int get, int set, int query,
|
|
Expectations expectations,
|
|
v8::Handle<Value> value) {
|
|
InitializeIfNeeded();
|
|
// A retry after a GC may pollute the counts, so perform gc now
|
|
// to avoid that.
|
|
v8::internal::Heap::CollectGarbage(v8::internal::NEW_SPACE);
|
|
HandleScope scope;
|
|
TryCatch catcher;
|
|
catcher.SetVerbose(true);
|
|
Local<Value> result = Script::Compile(String::New(source))->Run();
|
|
CHECK_EQ(get, get_count());
|
|
CHECK_EQ(set, set_count());
|
|
CHECK_EQ(query, query_count());
|
|
if (expectations == EXPECT_RESULT) {
|
|
CHECK(!catcher.HasCaught());
|
|
if (!value.IsEmpty()) {
|
|
CHECK_EQ(value, result);
|
|
}
|
|
} else {
|
|
CHECK(expectations == EXPECT_EXCEPTION);
|
|
CHECK(catcher.HasCaught());
|
|
if (!value.IsEmpty()) {
|
|
CHECK_EQ(value, catcher.Exception());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
v8::Handle<Value> DeclarationContext::HandleGet(Local<String> key,
|
|
const AccessorInfo& info) {
|
|
DeclarationContext* context = GetInstance(info);
|
|
context->get_count_++;
|
|
return context->Get(key);
|
|
}
|
|
|
|
|
|
v8::Handle<Value> DeclarationContext::HandleSet(Local<String> key,
|
|
Local<Value> value,
|
|
const AccessorInfo& info) {
|
|
DeclarationContext* context = GetInstance(info);
|
|
context->set_count_++;
|
|
return context->Set(key, value);
|
|
}
|
|
|
|
|
|
v8::Handle<Integer> DeclarationContext::HandleQuery(Local<String> key,
|
|
const AccessorInfo& info) {
|
|
DeclarationContext* context = GetInstance(info);
|
|
context->query_count_++;
|
|
return context->Query(key);
|
|
}
|
|
|
|
|
|
DeclarationContext* DeclarationContext::GetInstance(const AccessorInfo& info) {
|
|
return static_cast<DeclarationContext*>(External::Unwrap(info.Data()));
|
|
}
|
|
|
|
|
|
v8::Handle<Value> DeclarationContext::Get(Local<String> key) {
|
|
return v8::Handle<Value>();
|
|
}
|
|
|
|
|
|
v8::Handle<Value> DeclarationContext::Set(Local<String> key,
|
|
Local<Value> value) {
|
|
return v8::Handle<Value>();
|
|
}
|
|
|
|
|
|
v8::Handle<Integer> DeclarationContext::Query(Local<String> key) {
|
|
return v8::Handle<Integer>();
|
|
}
|
|
|
|
|
|
// Test global declaration of a property the interceptor doesn't know
|
|
// about and doesn't handle.
|
|
TEST(Unknown) {
|
|
HandleScope scope;
|
|
|
|
{ DeclarationContext context;
|
|
context.Check("var x; x",
|
|
1, // access
|
|
1, // declaration
|
|
2, // declaration + initialization
|
|
EXPECT_RESULT, Undefined());
|
|
}
|
|
|
|
{ DeclarationContext context;
|
|
context.Check("var x = 0; x",
|
|
1, // access
|
|
2, // declaration + initialization
|
|
2, // declaration + initialization
|
|
EXPECT_RESULT, Number::New(0));
|
|
}
|
|
|
|
{ DeclarationContext context;
|
|
context.Check("function x() { }; x",
|
|
1, // access
|
|
1, // declaration
|
|
0,
|
|
EXPECT_RESULT);
|
|
}
|
|
|
|
{ DeclarationContext context;
|
|
context.Check("const x; x",
|
|
1, // access
|
|
2, // declaration + initialization
|
|
2, // declaration + initialization
|
|
EXPECT_RESULT, Undefined());
|
|
}
|
|
|
|
{ DeclarationContext context;
|
|
context.Check("const x = 0; x",
|
|
1, // access
|
|
2, // declaration + initialization
|
|
2, // declaration + initialization
|
|
EXPECT_RESULT, Undefined()); // SB 0 - BUG 1213579
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class PresentPropertyContext: public DeclarationContext {
|
|
protected:
|
|
virtual v8::Handle<Integer> Query(Local<String> key) {
|
|
return Integer::New(v8::None);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
TEST(Present) {
|
|
HandleScope scope;
|
|
|
|
{ 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(0));
|
|
}
|
|
|
|
{ PresentPropertyContext context;
|
|
context.Check("function x() { }; x",
|
|
1, // access
|
|
1, // declaration
|
|
0,
|
|
EXPECT_RESULT);
|
|
}
|
|
|
|
{ PresentPropertyContext context;
|
|
context.Check("const x; x",
|
|
0,
|
|
0,
|
|
1, // (re-)declaration
|
|
EXPECT_EXCEPTION); // x has already been declared!
|
|
}
|
|
|
|
{ PresentPropertyContext context;
|
|
context.Check("const x = 0; x",
|
|
0,
|
|
0,
|
|
1, // (re-)declaration
|
|
EXPECT_EXCEPTION); // x has already been declared!
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class AbsentPropertyContext: public DeclarationContext {
|
|
protected:
|
|
virtual v8::Handle<Integer> Query(Local<String> key) {
|
|
return v8::Handle<Integer>();
|
|
}
|
|
};
|
|
|
|
|
|
TEST(Absent) {
|
|
HandleScope scope;
|
|
|
|
{ AbsentPropertyContext context;
|
|
context.Check("var x; x",
|
|
1, // access
|
|
1, // declaration
|
|
2, // declaration + initialization
|
|
EXPECT_RESULT, Undefined());
|
|
}
|
|
|
|
{ AbsentPropertyContext context;
|
|
context.Check("var x = 0; x",
|
|
1, // access
|
|
2, // declaration + initialization
|
|
2, // declaration + initialization
|
|
EXPECT_RESULT, Number::New(0));
|
|
}
|
|
|
|
{ AbsentPropertyContext context;
|
|
context.Check("function x() { }; x",
|
|
1, // access
|
|
1, // declaration
|
|
0,
|
|
EXPECT_RESULT);
|
|
}
|
|
|
|
{ AbsentPropertyContext context;
|
|
context.Check("const x; x",
|
|
1, // access
|
|
2, // declaration + initialization
|
|
2, // declaration + initializetion
|
|
EXPECT_RESULT, Undefined());
|
|
}
|
|
|
|
{ AbsentPropertyContext context;
|
|
context.Check("const x = 0; x",
|
|
1, // access
|
|
2, // declaration + initialization
|
|
2, // declaration + initialization
|
|
EXPECT_RESULT, Undefined()); // SB 0 - BUG 1213579
|
|
}
|
|
|
|
{ AbsentPropertyContext context;
|
|
context.Check("if (false) { var x = 0 }; x",
|
|
1, // access
|
|
1, // declaration
|
|
1, // declaration + initialization
|
|
EXPECT_RESULT, Undefined());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class AppearingPropertyContext: public DeclarationContext {
|
|
public:
|
|
enum State {
|
|
DECLARE,
|
|
INITIALIZE_IF_ASSIGN,
|
|
UNKNOWN
|
|
};
|
|
|
|
AppearingPropertyContext() : state_(DECLARE) { }
|
|
|
|
protected:
|
|
virtual v8::Handle<Integer> Query(Local<String> key) {
|
|
switch (state_) {
|
|
case DECLARE:
|
|
// Force declaration by returning that the
|
|
// property is absent.
|
|
state_ = INITIALIZE_IF_ASSIGN;
|
|
return Handle<Integer>();
|
|
case INITIALIZE_IF_ASSIGN:
|
|
// Return that the property is present so we only get the
|
|
// setter called when initializing with a value.
|
|
state_ = UNKNOWN;
|
|
return Integer::New(v8::None);
|
|
default:
|
|
CHECK(state_ == UNKNOWN);
|
|
break;
|
|
}
|
|
// Do the lookup in the object.
|
|
return v8::Handle<Integer>();
|
|
}
|
|
|
|
private:
|
|
State state_;
|
|
};
|
|
|
|
|
|
TEST(Appearing) {
|
|
HandleScope scope;
|
|
|
|
{ AppearingPropertyContext context;
|
|
context.Check("var x; x",
|
|
1, // access
|
|
1, // declaration
|
|
2, // declaration + initialization
|
|
EXPECT_RESULT, Undefined());
|
|
}
|
|
|
|
{ AppearingPropertyContext context;
|
|
context.Check("var x = 0; x",
|
|
1, // access
|
|
2, // declaration + initialization
|
|
2, // declaration + initialization
|
|
EXPECT_RESULT, Number::New(0));
|
|
}
|
|
|
|
{ AppearingPropertyContext context;
|
|
context.Check("function x() { }; x",
|
|
1, // access
|
|
1, // declaration
|
|
0,
|
|
EXPECT_RESULT);
|
|
}
|
|
|
|
{ AppearingPropertyContext context;
|
|
context.Check("const x; x",
|
|
0,
|
|
1, // declaration
|
|
2, // declaration + initialization
|
|
EXPECT_EXCEPTION); // x has already been declared!
|
|
}
|
|
|
|
{ AppearingPropertyContext context;
|
|
context.Check("const x = 0; x",
|
|
0,
|
|
1, // declaration
|
|
2, // declaration + initialization
|
|
EXPECT_EXCEPTION); // x has already been declared!
|
|
}
|
|
}
|
|
|
|
|
|
|
|
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(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) {
|
|
HandleScope scope;
|
|
|
|
{ ReappearingPropertyContext context;
|
|
context.Check("const x; var x = 0",
|
|
0,
|
|
2, // var declaration + const initialization
|
|
4, // 2 x declaration + 2 x initialization
|
|
EXPECT_EXCEPTION); // x has already been declared!
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class ExistsInPrototypeContext: public DeclarationContext {
|
|
protected:
|
|
virtual v8::Handle<Integer> Query(Local<String> key) {
|
|
// Let it seem that the property exists in the prototype object.
|
|
return Integer::New(v8::None);
|
|
}
|
|
|
|
// Use the prototype as the holder for the interceptors.
|
|
virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) {
|
|
return function->PrototypeTemplate();
|
|
}
|
|
};
|
|
|
|
|
|
TEST(ExistsInPrototype) {
|
|
HandleScope scope;
|
|
|
|
// Sanity check to make sure that the holder of the interceptor
|
|
// really is the prototype object.
|
|
{ ExistsInPrototypeContext context;
|
|
context.Check("this.x = 87; this.x",
|
|
0,
|
|
0,
|
|
0,
|
|
EXPECT_RESULT, Number::New(87));
|
|
}
|
|
|
|
{ ExistsInPrototypeContext context;
|
|
context.Check("var x; x",
|
|
1, // get
|
|
0,
|
|
1, // declaration
|
|
EXPECT_EXCEPTION);
|
|
}
|
|
|
|
{ ExistsInPrototypeContext context;
|
|
context.Check("var x = 0; x",
|
|
0,
|
|
0,
|
|
1, // declaration
|
|
EXPECT_RESULT, Number::New(0));
|
|
}
|
|
|
|
{ ExistsInPrototypeContext context;
|
|
context.Check("const x; x",
|
|
0,
|
|
0,
|
|
1, // declaration
|
|
EXPECT_RESULT, Undefined());
|
|
}
|
|
|
|
{ ExistsInPrototypeContext context;
|
|
context.Check("const x = 0; x",
|
|
0,
|
|
0,
|
|
1, // declaration
|
|
EXPECT_RESULT, Number::New(0));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class AbsentInPrototypeContext: public DeclarationContext {
|
|
protected:
|
|
virtual v8::Handle<Integer> Query(Local<String> key) {
|
|
// Let it seem that the property is absent in the prototype object.
|
|
return Handle<Integer>();
|
|
}
|
|
|
|
// Use the prototype as the holder for the interceptors.
|
|
virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) {
|
|
return function->PrototypeTemplate();
|
|
}
|
|
};
|
|
|
|
|
|
TEST(AbsentInPrototype) {
|
|
HandleScope scope;
|
|
|
|
{ AbsentInPrototypeContext context;
|
|
context.Check("if (false) { var x = 0; }; x",
|
|
0,
|
|
0,
|
|
1, // declaration
|
|
EXPECT_RESULT, Undefined());
|
|
}
|
|
}
|
|
|