// 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. 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 0, 0, EXPECT_RESULT); } { DeclarationContext context; context.Check("const x; x", 1, // access 2, // declaration + initialization 1, // declaration EXPECT_RESULT, Undefined()); } { DeclarationContext context; context.Check("const x = 0; x", 1, // access 2, // declaration + initialization 1, // declaration 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 0, 0, EXPECT_RESULT); } { PresentPropertyContext context; context.Check("const x; x", 1, // access 1, // initialization 1, // (re-)declaration EXPECT_RESULT, Undefined()); } { PresentPropertyContext context; context.Check("const x = 0; x", 1, // access 1, // initialization 1, // (re-)declaration EXPECT_RESULT, Number::New(0)); } } 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 0, 0, EXPECT_RESULT); } { AbsentPropertyContext context; context.Check("const x; x", 1, // access 2, // declaration + initialization 1, // declaration EXPECT_RESULT, Undefined()); } { AbsentPropertyContext context; context.Check("const x = 0; x", 1, // access 2, // declaration + initialization 1, // declaration 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 0, 0, EXPECT_RESULT); } { AppearingPropertyContext context; context.Check("const x; x", 1, // access 2, // declaration + initialization 1, // declaration EXPECT_RESULT, Undefined()); } { AppearingPropertyContext context; context.Check("const x = 0; x", 1, // access 2, // declaration + initialization 1, // declaration EXPECT_RESULT, Undefined()); // 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(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, 3, // const declaration+initialization, var initialization 3, // 2 x declaration + var initialization EXPECT_RESULT, Undefined()); } } 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()); } }