v8/test/cctest/test-api.cc

6611 lines
208 KiB
C++
Raw Normal View History

// 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 "api.h"
#include "compilation-cache.h"
#include "snapshot.h"
#include "platform.h"
#include "top.h"
#include "cctest.h"
static bool IsNaN(double x) {
#ifdef WIN32
return _isnan(x);
#else
return isnan(x);
#endif
}
using ::v8::ObjectTemplate;
using ::v8::Value;
using ::v8::Context;
using ::v8::Local;
using ::v8::String;
using ::v8::Script;
using ::v8::Function;
using ::v8::AccessorInfo;
using ::v8::Extension;
namespace i = ::v8::internal;
static Local<Value> v8_num(double x) {
return v8::Number::New(x);
}
static Local<String> v8_str(const char* x) {
return String::New(x);
}
static Local<Script> v8_compile(const char* x) {
return Script::Compile(v8_str(x));
}
// A LocalContext holds a reference to a v8::Context.
class LocalContext {
public:
LocalContext(v8::ExtensionConfiguration* extensions = 0,
v8::Handle<ObjectTemplate> global_template =
v8::Handle<ObjectTemplate>(),
v8::Handle<Value> global_object = v8::Handle<Value>())
: context_(Context::New(extensions, global_template, global_object)) {
context_->Enter();
}
virtual ~LocalContext() {
context_->Exit();
context_.Dispose();
}
Context* operator->() { return *context_; }
Context* operator*() { return *context_; }
Local<Context> local() { return Local<Context>::New(context_); }
bool IsReady() { return !context_.IsEmpty(); }
private:
v8::Persistent<Context> context_;
};
// Switches between all the Api tests using the threading support.
// In order to get a surprising but repeatable pattern of thread
// switching it has extra semaphores to control the order in which
// the tests alternate, not relying solely on the big V8 lock.
//
// A test is augmented with calls to ApiTestFuzzer::Fuzz() in its
// callbacks. This will have no effect when we are not running the
// thread fuzzing test. In the thread fuzzing test it will
// pseudorandomly select a successor thread and switch execution
// to that thread, suspending the current test.
class ApiTestFuzzer: public v8::internal::Thread {
public:
void CallTest();
explicit ApiTestFuzzer(int num)
: test_number_(num),
gate_(v8::internal::OS::CreateSemaphore(0)),
active_(true) {
}
~ApiTestFuzzer() { delete gate_; }
// The ApiTestFuzzer is also a Thread, so it has a Run method.
virtual void Run();
enum PartOfTest { FIRST_PART, SECOND_PART };
static void Setup(PartOfTest part);
static void RunAllTests();
static void TearDown();
// This method switches threads if we are running the Threading test.
// Otherwise it does nothing.
static void Fuzz();
private:
static bool fuzzing_;
static int tests_being_run_;
static int current_;
static int active_tests_;
static bool NextThread();
int test_number_;
v8::internal::Semaphore* gate_;
bool active_;
void ContextSwitch();
static int GetNextTestNumber();
static v8::internal::Semaphore* all_tests_done_;
};
#define THREADED_TEST(Name) \
static void Test##Name(); \
RegisterThreadedTest register_##Name(Test##Name); \
/* */ TEST(Name)
class RegisterThreadedTest {
public:
explicit RegisterThreadedTest(CcTest::TestFunction* callback)
: fuzzer_(NULL), callback_(callback) {
prev_ = first_;
first_ = this;
count_++;
}
static int count() { return count_; }
static RegisterThreadedTest* nth(int i) {
CHECK(i < count());
RegisterThreadedTest* current = first_;
while (i > 0) {
i--;
current = current->prev_;
}
return current;
}
CcTest::TestFunction* callback() { return callback_; }
ApiTestFuzzer* fuzzer_;
private:
static RegisterThreadedTest* first_;
static int count_;
CcTest::TestFunction* callback_;
RegisterThreadedTest* prev_;
};
RegisterThreadedTest *RegisterThreadedTest::first_ = NULL;
int RegisterThreadedTest::count_ = 0;
static int signature_callback_count;
static v8::Handle<Value> IncrementingSignatureCallback(
const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
signature_callback_count++;
v8::Handle<v8::Array> result = v8::Array::New(args.Length());
for (int i = 0; i < args.Length(); i++)
result->Set(v8::Integer::New(i), args[i]);
return result;
}
static v8::Handle<Value> SignatureCallback(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
v8::Handle<v8::Array> result = v8::Array::New(args.Length());
for (int i = 0; i < args.Length(); i++) {
result->Set(v8::Integer::New(i), args[i]);
}
return result;
}
THREADED_TEST(Handles) {
v8::HandleScope scope;
Local<Context> local_env;
{
LocalContext env;
local_env = env.local();
}
// Local context should still be live.
CHECK(!local_env.IsEmpty());
local_env->Enter();
v8::Handle<v8::Primitive> undef = v8::Undefined();
CHECK(!undef.IsEmpty());
CHECK(undef->IsUndefined());
const char* c_source = "1 + 2 + 3";
Local<String> source = String::New(c_source);
Local<Script> script = Script::Compile(source);
CHECK_EQ(6, script->Run()->Int32Value());
local_env->Exit();
}
// Helper function that compiles and runs the source.
static Local<Value> CompileRun(const char* source) {
return Script::Compile(String::New(source))->Run();
}
THREADED_TEST(ReceiverSignature) {
v8::HandleScope scope;
LocalContext env;
v8::Handle<v8::FunctionTemplate> fun = v8::FunctionTemplate::New();
v8::Handle<v8::Signature> sig = v8::Signature::New(fun);
fun->PrototypeTemplate()->Set(
v8_str("m"),
v8::FunctionTemplate::New(IncrementingSignatureCallback,
v8::Handle<Value>(),
sig));
env->Global()->Set(v8_str("Fun"), fun->GetFunction());
signature_callback_count = 0;
CompileRun(
"var o = new Fun();"
"o.m();");
CHECK_EQ(1, signature_callback_count);
v8::Handle<v8::FunctionTemplate> sub_fun = v8::FunctionTemplate::New();
sub_fun->Inherit(fun);
env->Global()->Set(v8_str("SubFun"), sub_fun->GetFunction());
CompileRun(
"var o = new SubFun();"
"o.m();");
CHECK_EQ(2, signature_callback_count);
v8::TryCatch try_catch;
CompileRun(
"var o = { };"
"o.m = Fun.prototype.m;"
"o.m();");
CHECK_EQ(2, signature_callback_count);
CHECK(try_catch.HasCaught());
try_catch.Reset();
v8::Handle<v8::FunctionTemplate> unrel_fun = v8::FunctionTemplate::New();
sub_fun->Inherit(fun);
env->Global()->Set(v8_str("UnrelFun"), unrel_fun->GetFunction());
CompileRun(
"var o = new UnrelFun();"
"o.m = Fun.prototype.m;"
"o.m();");
CHECK_EQ(2, signature_callback_count);
CHECK(try_catch.HasCaught());
}
THREADED_TEST(ArgumentSignature) {
v8::HandleScope scope;
LocalContext env;
v8::Handle<v8::FunctionTemplate> cons = v8::FunctionTemplate::New();
cons->SetClassName(v8_str("Cons"));
v8::Handle<v8::Signature> sig =
v8::Signature::New(v8::Handle<v8::FunctionTemplate>(), 1, &cons);
v8::Handle<v8::FunctionTemplate> fun =
v8::FunctionTemplate::New(SignatureCallback, v8::Handle<Value>(), sig);
env->Global()->Set(v8_str("Cons"), cons->GetFunction());
env->Global()->Set(v8_str("Fun1"), fun->GetFunction());
v8::Handle<Value> value1 = CompileRun("Fun1(4) == '';");
CHECK(value1->IsTrue());
v8::Handle<Value> value2 = CompileRun("Fun1(new Cons()) == '[object Cons]';");
CHECK(value2->IsTrue());
v8::Handle<Value> value3 = CompileRun("Fun1() == '';");
CHECK(value3->IsTrue());
v8::Handle<v8::FunctionTemplate> cons1 = v8::FunctionTemplate::New();
cons1->SetClassName(v8_str("Cons1"));
v8::Handle<v8::FunctionTemplate> cons2 = v8::FunctionTemplate::New();
cons2->SetClassName(v8_str("Cons2"));
v8::Handle<v8::FunctionTemplate> cons3 = v8::FunctionTemplate::New();
cons3->SetClassName(v8_str("Cons3"));
v8::Handle<v8::FunctionTemplate> args[3] = { cons1, cons2, cons3 };
v8::Handle<v8::Signature> wsig =
v8::Signature::New(v8::Handle<v8::FunctionTemplate>(), 3, args);
v8::Handle<v8::FunctionTemplate> fun2 =
v8::FunctionTemplate::New(SignatureCallback, v8::Handle<Value>(), wsig);
env->Global()->Set(v8_str("Cons1"), cons1->GetFunction());
env->Global()->Set(v8_str("Cons2"), cons2->GetFunction());
env->Global()->Set(v8_str("Cons3"), cons3->GetFunction());
env->Global()->Set(v8_str("Fun2"), fun2->GetFunction());
v8::Handle<Value> value4 = CompileRun(
"Fun2(new Cons1(), new Cons2(), new Cons3()) =="
"'[object Cons1],[object Cons2],[object Cons3]'");
CHECK(value4->IsTrue());
v8::Handle<Value> value5 = CompileRun(
"Fun2(new Cons1(), new Cons2(), 5) == '[object Cons1],[object Cons2],'");
CHECK(value5->IsTrue());
v8::Handle<Value> value6 = CompileRun(
"Fun2(new Cons3(), new Cons2(), new Cons1()) == ',[object Cons2],'");
CHECK(value6->IsTrue());
v8::Handle<Value> value7 = CompileRun(
"Fun2(new Cons1(), new Cons2(), new Cons3(), 'd') == "
"'[object Cons1],[object Cons2],[object Cons3],d';");
CHECK(value7->IsTrue());
v8::Handle<Value> value8 = CompileRun(
"Fun2(new Cons1(), new Cons2()) == '[object Cons1],[object Cons2]'");
CHECK(value8->IsTrue());
}
THREADED_TEST(HulIgennem) {
v8::HandleScope scope;
LocalContext env;
v8::Handle<v8::Primitive> undef = v8::Undefined();
Local<String> undef_str = undef->ToString();
char* value = i::NewArray<char>(undef_str->Length() + 1);
undef_str->WriteAscii(value);
CHECK_EQ(0, strcmp(value, "undefined"));
i::DeleteArray(value);
}
THREADED_TEST(Access) {
v8::HandleScope scope;
LocalContext env;
Local<v8::Object> obj = v8::Object::New();
Local<Value> foo_before = obj->Get(v8_str("foo"));
CHECK(foo_before->IsUndefined());
Local<String> bar_str = v8_str("bar");
obj->Set(v8_str("foo"), bar_str);
Local<Value> foo_after = obj->Get(v8_str("foo"));
CHECK(!foo_after->IsUndefined());
CHECK(foo_after->IsString());
CHECK_EQ(bar_str, foo_after);
}
THREADED_TEST(Script) {
v8::HandleScope scope;
LocalContext env;
const char* c_source = "1 + 2 + 3";
Local<String> source = String::New(c_source);
Local<Script> script = Script::Compile(source);
CHECK_EQ(6, script->Run()->Int32Value());
}
static uint16_t* AsciiToTwoByteString(const char* source) {
size_t array_length = strlen(source) + 1;
uint16_t* converted = i::NewArray<uint16_t>(array_length);
for (size_t i = 0; i < array_length; i++) converted[i] = source[i];
return converted;
}
class TestResource: public String::ExternalStringResource {
public:
static int dispose_count;
explicit TestResource(uint16_t* data)
: data_(data), length_(0) {
while (data[length_]) ++length_;
}
~TestResource() {
i::DeleteArray(data_);
++dispose_count;
}
const uint16_t* data() const {
return data_;
}
size_t length() const {
return length_;
}
private:
uint16_t* data_;
size_t length_;
};
int TestResource::dispose_count = 0;
class TestAsciiResource: public String::ExternalAsciiStringResource {
public:
static int dispose_count;
explicit TestAsciiResource(const char* data)
: data_(data),
length_(strlen(data)) { }
~TestAsciiResource() {
i::DeleteArray(data_);
++dispose_count;
}
const char* data() const {
return data_;
}
size_t length() const {
return length_;
}
private:
const char* data_;
size_t length_;
};
int TestAsciiResource::dispose_count = 0;
THREADED_TEST(ScriptUsingStringResource) {
TestResource::dispose_count = 0;
const char* c_source = "1 + 2 * 3";
uint16_t* two_byte_source = AsciiToTwoByteString(c_source);
{
v8::HandleScope scope;
LocalContext env;
TestResource* resource = new TestResource(two_byte_source);
Local<String> source = String::NewExternal(resource);
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(7, value->Int32Value());
CHECK(source->IsExternal());
CHECK_EQ(resource,
static_cast<TestResource*>(source->GetExternalStringResource()));
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(0, TestResource::dispose_count);
}
v8::internal::CompilationCache::Clear();
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(1, TestResource::dispose_count);
}
THREADED_TEST(ScriptUsingAsciiStringResource) {
TestAsciiResource::dispose_count = 0;
const char* c_source = "1 + 2 * 3";
{
v8::HandleScope scope;
LocalContext env;
Local<String> source =
String::NewExternal(new TestAsciiResource(i::StrDup(c_source)));
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(7, value->Int32Value());
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(0, TestAsciiResource::dispose_count);
}
v8::internal::CompilationCache::Clear();
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(1, TestAsciiResource::dispose_count);
}
THREADED_TEST(ScriptMakingExternalString) {
TestResource::dispose_count = 0;
uint16_t* two_byte_source = AsciiToTwoByteString("1 + 2 * 3");
{
v8::HandleScope scope;
LocalContext env;
Local<String> source = String::New(two_byte_source);
bool success = source->MakeExternal(new TestResource(two_byte_source));
CHECK(success);
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(7, value->Int32Value());
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(0, TestResource::dispose_count);
}
v8::internal::CompilationCache::Clear();
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(1, TestResource::dispose_count);
}
THREADED_TEST(ScriptMakingExternalAsciiString) {
TestAsciiResource::dispose_count = 0;
const char* c_source = "1 + 2 * 3";
{
v8::HandleScope scope;
LocalContext env;
Local<String> source = v8_str(c_source);
bool success = source->MakeExternal(
new TestAsciiResource(i::StrDup(c_source)));
CHECK(success);
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(7, value->Int32Value());
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(0, TestAsciiResource::dispose_count);
}
v8::internal::CompilationCache::Clear();
v8::internal::Heap::CollectAllGarbage();
CHECK_EQ(1, TestAsciiResource::dispose_count);
}
THREADED_TEST(UsingExternalString) {
v8::HandleScope scope;
uint16_t* two_byte_string = AsciiToTwoByteString("test string");
Local<String> string = String::NewExternal(new TestResource(two_byte_string));
i::Handle<i::String> istring = v8::Utils::OpenHandle(*string);
// Trigger GCs so that the newly allocated string moves to old gen.
i::Heap::CollectGarbage(0, i::NEW_SPACE); // in survivor space now
i::Heap::CollectGarbage(0, i::NEW_SPACE); // in old gen now
i::Handle<i::String> isymbol = i::Factory::SymbolFromString(istring);
CHECK(isymbol->IsSymbol());
}
THREADED_TEST(UsingExternalAsciiString) {
v8::HandleScope scope;
const char* one_byte_string = "test string";
Local<String> string = String::NewExternal(
new TestAsciiResource(i::StrDup(one_byte_string)));
i::Handle<i::String> istring = v8::Utils::OpenHandle(*string);
// Trigger GCs so that the newly allocated string moves to old gen.
i::Heap::CollectGarbage(0, i::NEW_SPACE); // in survivor space now
i::Heap::CollectGarbage(0, i::NEW_SPACE); // in old gen now
i::Handle<i::String> isymbol = i::Factory::SymbolFromString(istring);
CHECK(isymbol->IsSymbol());
}
THREADED_TEST(GlobalProperties) {
v8::HandleScope scope;
LocalContext env;
v8::Handle<v8::Object> global = env->Global();
global->Set(v8_str("pi"), v8_num(3.1415926));
Local<Value> pi = global->Get(v8_str("pi"));
CHECK_EQ(3.1415926, pi->NumberValue());
}
static v8::Handle<Value> handle_call(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
return v8_num(102);
}
static v8::Handle<Value> construct_call(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
args.This()->Set(v8_str("x"), v8_num(1));
args.This()->Set(v8_str("y"), v8_num(2));
return args.This();
}
THREADED_TEST(FunctionTemplate) {
v8::HandleScope scope;
LocalContext env;
{
Local<v8::FunctionTemplate> fun_templ =
v8::FunctionTemplate::New(handle_call);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("obj"), fun);
Local<Script> script = v8_compile("obj()");
CHECK_EQ(102, script->Run()->Int32Value());
}
// Use SetCallHandler to initialize a function template, should work like the
// previous one.
{
Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
fun_templ->SetCallHandler(handle_call);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("obj"), fun);
Local<Script> script = v8_compile("obj()");
CHECK_EQ(102, script->Run()->Int32Value());
}
// Test constructor calls.
{
Local<v8::FunctionTemplate> fun_templ =
v8::FunctionTemplate::New(construct_call);
fun_templ->SetClassName(v8_str("funky"));
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("obj"), fun);
Local<Script> script = v8_compile("var s = new obj(); s.x");
CHECK_EQ(1, script->Run()->Int32Value());
Local<Value> result = v8_compile("(new obj()).toString()")->Run();
CHECK_EQ(v8_str("[object funky]"), result);
}
}
static v8::Handle<Value> handle_property(Local<String> name,
const AccessorInfo&) {
ApiTestFuzzer::Fuzz();
return v8_num(900);
}
THREADED_TEST(PropertyHandler) {
v8::HandleScope scope;
Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
fun_templ->InstanceTemplate()->SetAccessor(v8_str("foo"), handle_property);
LocalContext env;
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("Fun"), fun);
Local<Script> getter = v8_compile("var obj = new Fun(); obj.foo;");
CHECK_EQ(900, getter->Run()->Int32Value());
Local<Script> setter = v8_compile("obj.foo = 901;");
CHECK_EQ(901, setter->Run()->Int32Value());
}
THREADED_TEST(Number) {
v8::HandleScope scope;
LocalContext env;
double PI = 3.1415926;
Local<v8::Number> pi_obj = v8::Number::New(PI);
CHECK_EQ(PI, pi_obj->NumberValue());
}
THREADED_TEST(ToNumber) {
v8::HandleScope scope;
LocalContext env;
Local<String> str = v8_str("3.1415926");
CHECK_EQ(3.1415926, str->NumberValue());
v8::Handle<v8::Boolean> t = v8::True();
CHECK_EQ(1.0, t->NumberValue());
v8::Handle<v8::Boolean> f = v8::False();
CHECK_EQ(0.0, f->NumberValue());
}
THREADED_TEST(Date) {
v8::HandleScope scope;
LocalContext env;
double PI = 3.1415926;
Local<Value> date_obj = v8::Date::New(PI);
CHECK_EQ(3.0, date_obj->NumberValue());
}
THREADED_TEST(Boolean) {
v8::HandleScope scope;
LocalContext env;
v8::Handle<v8::Boolean> t = v8::True();
CHECK(t->Value());
v8::Handle<v8::Boolean> f = v8::False();
CHECK(!f->Value());
v8::Handle<v8::Primitive> u = v8::Undefined();
CHECK(!u->BooleanValue());
v8::Handle<v8::Primitive> n = v8::Null();
CHECK(!n->BooleanValue());
v8::Handle<String> str1 = v8_str("");
CHECK(!str1->BooleanValue());
v8::Handle<String> str2 = v8_str("x");
CHECK(str2->BooleanValue());
CHECK(!v8::Number::New(0)->BooleanValue());
CHECK(v8::Number::New(-1)->BooleanValue());
CHECK(v8::Number::New(1)->BooleanValue());
CHECK(v8::Number::New(42)->BooleanValue());
CHECK(!v8_compile("NaN")->Run()->BooleanValue());
}
static v8::Handle<Value> DummyCallHandler(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
return v8_num(13.4);
}
static v8::Handle<Value> GetM(Local<String> name, const AccessorInfo&) {
ApiTestFuzzer::Fuzz();
return v8_num(876);
}
THREADED_TEST(GlobalPrototype) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> func_templ = v8::FunctionTemplate::New();
func_templ->PrototypeTemplate()->Set(
"dummy",
v8::FunctionTemplate::New(DummyCallHandler));
v8::Handle<ObjectTemplate> templ = func_templ->InstanceTemplate();
templ->Set("x", v8_num(200));
templ->SetAccessor(v8_str("m"), GetM);
LocalContext env(0, templ);
v8::Handle<v8::Object> obj = env->Global();
v8::Handle<Script> script = v8_compile("dummy()");
v8::Handle<Value> result = script->Run();
CHECK_EQ(13.4, result->NumberValue());
CHECK_EQ(200, v8_compile("x")->Run()->Int32Value());
CHECK_EQ(876, v8_compile("m")->Run()->Int32Value());
}
static v8::Handle<Value> GetIntValue(Local<String> property,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
int* value =
static_cast<int*>(v8::Handle<v8::External>::Cast(info.Data())->Value());
return v8_num(*value);
}
static void SetIntValue(Local<String> property,
Local<Value> value,
const AccessorInfo& info) {
int* field =
static_cast<int*>(v8::Handle<v8::External>::Cast(info.Data())->Value());
*field = value->Int32Value();
}
int foo, bar, baz;
THREADED_TEST(GlobalVariableAccess) {
foo = 0;
bar = -4;
baz = 10;
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> templ = v8::FunctionTemplate::New();
templ->InstanceTemplate()->SetAccessor(v8_str("foo"),
GetIntValue,
SetIntValue,
v8::External::New(&foo));
templ->InstanceTemplate()->SetAccessor(v8_str("bar"),
GetIntValue,
SetIntValue,
v8::External::New(&bar));
templ->InstanceTemplate()->SetAccessor(v8_str("baz"),
GetIntValue,
SetIntValue,
v8::External::New(&baz));
LocalContext env(0, templ->InstanceTemplate());
v8_compile("foo = (++bar) + baz")->Run();
CHECK_EQ(bar, -3);
CHECK_EQ(foo, 7);
}
THREADED_TEST(ObjectTemplate) {
v8::HandleScope scope;
Local<ObjectTemplate> templ1 = ObjectTemplate::New();
templ1->Set("x", v8_num(10));
templ1->Set("y", v8_num(13));
LocalContext env;
Local<v8::Object> instance1 = templ1->NewInstance();
env->Global()->Set(v8_str("p"), instance1);
CHECK(v8_compile("(p.x == 10)")->Run()->BooleanValue());
CHECK(v8_compile("(p.y == 13)")->Run()->BooleanValue());
Local<v8::FunctionTemplate> fun = v8::FunctionTemplate::New();
fun->PrototypeTemplate()->Set("nirk", v8_num(123));
Local<ObjectTemplate> templ2 = fun->InstanceTemplate();
templ2->Set("a", v8_num(12));
templ2->Set("b", templ1);
Local<v8::Object> instance2 = templ2->NewInstance();
env->Global()->Set(v8_str("q"), instance2);
CHECK(v8_compile("(q.nirk == 123)")->Run()->BooleanValue());
CHECK(v8_compile("(q.a == 12)")->Run()->BooleanValue());
CHECK(v8_compile("(q.b.x == 10)")->Run()->BooleanValue());
CHECK(v8_compile("(q.b.y == 13)")->Run()->BooleanValue());
}
static v8::Handle<Value> GetFlabby(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
return v8_num(17.2);
}
static v8::Handle<Value> GetKnurd(Local<String> property, const AccessorInfo&) {
ApiTestFuzzer::Fuzz();
return v8_num(15.2);
}
THREADED_TEST(DescriptorInheritance) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> super = v8::FunctionTemplate::New();
super->PrototypeTemplate()->Set("flabby",
v8::FunctionTemplate::New(GetFlabby));
super->PrototypeTemplate()->Set("PI", v8_num(3.14));
super->InstanceTemplate()->SetAccessor(v8_str("knurd"), GetKnurd);
v8::Handle<v8::FunctionTemplate> base1 = v8::FunctionTemplate::New();
base1->Inherit(super);
base1->PrototypeTemplate()->Set("v1", v8_num(20.1));
v8::Handle<v8::FunctionTemplate> base2 = v8::FunctionTemplate::New();
base2->Inherit(super);
base2->PrototypeTemplate()->Set("v2", v8_num(10.1));
LocalContext env;
env->Global()->Set(v8_str("s"), super->GetFunction());
env->Global()->Set(v8_str("base1"), base1->GetFunction());
env->Global()->Set(v8_str("base2"), base2->GetFunction());
// Checks right __proto__ chain.
CHECK(CompileRun("base1.prototype.__proto__ == s.prototype")->BooleanValue());
CHECK(CompileRun("base2.prototype.__proto__ == s.prototype")->BooleanValue());
CHECK(v8_compile("s.prototype.PI == 3.14")->Run()->BooleanValue());
// Instance accessor should not be visible on function object or its prototype
CHECK(CompileRun("s.knurd == undefined")->BooleanValue());
CHECK(CompileRun("s.prototype.knurd == undefined")->BooleanValue());
CHECK(CompileRun("base1.prototype.knurd == undefined")->BooleanValue());
env->Global()->Set(v8_str("obj"),
base1->GetFunction()->NewInstance());
CHECK_EQ(17.2, v8_compile("obj.flabby()")->Run()->NumberValue());
CHECK(v8_compile("'flabby' in obj")->Run()->BooleanValue());
CHECK_EQ(15.2, v8_compile("obj.knurd")->Run()->NumberValue());
CHECK(v8_compile("'knurd' in obj")->Run()->BooleanValue());
CHECK_EQ(20.1, v8_compile("obj.v1")->Run()->NumberValue());
env->Global()->Set(v8_str("obj2"),
base2->GetFunction()->NewInstance());
CHECK_EQ(17.2, v8_compile("obj2.flabby()")->Run()->NumberValue());
CHECK(v8_compile("'flabby' in obj2")->Run()->BooleanValue());
CHECK_EQ(15.2, v8_compile("obj2.knurd")->Run()->NumberValue());
CHECK(v8_compile("'knurd' in obj2")->Run()->BooleanValue());
CHECK_EQ(10.1, v8_compile("obj2.v2")->Run()->NumberValue());
// base1 and base2 cannot cross reference to each's prototype
CHECK(v8_compile("obj.v2")->Run()->IsUndefined());
CHECK(v8_compile("obj2.v1")->Run()->IsUndefined());
}
int echo_named_call_count;
static v8::Handle<Value> EchoNamedProperty(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK_EQ(v8_str("data"), info.Data());
echo_named_call_count++;
return name;
}
THREADED_TEST(NamedPropertyHandlerGetter) {
echo_named_call_count = 0;
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> templ = v8::FunctionTemplate::New();
templ->InstanceTemplate()->SetNamedPropertyHandler(EchoNamedProperty,
0, 0, 0, 0,
v8_str("data"));
LocalContext env;
env->Global()->Set(v8_str("obj"),
templ->GetFunction()->NewInstance());
CHECK_EQ(echo_named_call_count, 0);
v8_compile("obj.x")->Run();
CHECK_EQ(echo_named_call_count, 1);
const char* code = "var str = 'oddle'; obj[str] + obj.poddle;";
v8::Handle<Value> str = CompileRun(code);
String::AsciiValue value(str);
CHECK_EQ(*value, "oddlepoddle");
// Check default behavior
CHECK_EQ(v8_compile("obj.flob = 10;")->Run()->Int32Value(), 10);
CHECK(v8_compile("'myProperty' in obj")->Run()->BooleanValue());
CHECK(v8_compile("delete obj.myProperty")->Run()->BooleanValue());
}
int echo_indexed_call_count = 0;
static v8::Handle<Value> EchoIndexedProperty(uint32_t index,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK_EQ(v8_num(637), info.Data());
echo_indexed_call_count++;
return v8_num(index);
}
THREADED_TEST(IndexedPropertyHandlerGetter) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> templ = v8::FunctionTemplate::New();
templ->InstanceTemplate()->SetIndexedPropertyHandler(EchoIndexedProperty,
0, 0, 0, 0,
v8_num(637));
LocalContext env;
env->Global()->Set(v8_str("obj"),
templ->GetFunction()->NewInstance());
Local<Script> script = v8_compile("obj[900]");
CHECK_EQ(script->Run()->Int32Value(), 900);
}
v8::Handle<v8::Object> bottom;
static v8::Handle<Value> CheckThisIndexedPropertyHandler(
uint32_t index,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<Value>();
}
static v8::Handle<Value> CheckThisNamedPropertyHandler(
Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<Value>();
}
v8::Handle<Value> CheckThisIndexedPropertySetter(uint32_t index,
Local<Value> value,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<Value>();
}
v8::Handle<Value> CheckThisNamedPropertySetter(Local<String> property,
Local<Value> value,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<Value>();
}
v8::Handle<v8::Boolean> CheckThisIndexedPropertyQuery(
uint32_t index,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<v8::Boolean>();
}
v8::Handle<v8::Boolean> CheckThisNamedPropertyQuery(Local<String> property,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<v8::Boolean>();
}
v8::Handle<v8::Boolean> CheckThisIndexedPropertyDeleter(
uint32_t index,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<v8::Boolean>();
}
v8::Handle<v8::Boolean> CheckThisNamedPropertyDeleter(
Local<String> property,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<v8::Boolean>();
}
v8::Handle<v8::Array> CheckThisIndexedPropertyEnumerator(
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<v8::Array>();
}
v8::Handle<v8::Array> CheckThisNamedPropertyEnumerator(
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<v8::Array>();
}
THREADED_TEST(PropertyHandlerInPrototype) {
v8::HandleScope scope;
LocalContext env;
// Set up a prototype chain with three interceptors.
v8::Handle<v8::FunctionTemplate> templ = v8::FunctionTemplate::New();
templ->InstanceTemplate()->SetIndexedPropertyHandler(
CheckThisIndexedPropertyHandler,
CheckThisIndexedPropertySetter,
CheckThisIndexedPropertyQuery,
CheckThisIndexedPropertyDeleter,
CheckThisIndexedPropertyEnumerator);
templ->InstanceTemplate()->SetNamedPropertyHandler(
CheckThisNamedPropertyHandler,
CheckThisNamedPropertySetter,
CheckThisNamedPropertyQuery,
CheckThisNamedPropertyDeleter,
CheckThisNamedPropertyEnumerator);
bottom = templ->GetFunction()->NewInstance();
Local<v8::Object> top = templ->GetFunction()->NewInstance();
Local<v8::Object> middle = templ->GetFunction()->NewInstance();
bottom->Set(v8_str("__proto__"), middle);
middle->Set(v8_str("__proto__"), top);
env->Global()->Set(v8_str("obj"), bottom);
// Indexed and named get.
Script::Compile(v8_str("obj[0]"))->Run();
Script::Compile(v8_str("obj.x"))->Run();
// Indexed and named set.
Script::Compile(v8_str("obj[1] = 42"))->Run();
Script::Compile(v8_str("obj.y = 42"))->Run();
// Indexed and named query.
Script::Compile(v8_str("0 in obj"))->Run();
Script::Compile(v8_str("'x' in obj"))->Run();
// Indexed and named deleter.
Script::Compile(v8_str("delete obj[0]"))->Run();
Script::Compile(v8_str("delete obj.x"))->Run();
// Enumerators.
Script::Compile(v8_str("for (var p in obj) ;"))->Run();
}
static v8::Handle<Value> PrePropertyHandlerGet(Local<String> key,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
if (v8_str("pre")->Equals(key)) {
return v8_str("PrePropertyHandler: pre");
}
return v8::Handle<String>();
}
static v8::Handle<v8::Boolean> PrePropertyHandlerHas(Local<String> key,
const AccessorInfo&) {
if (v8_str("pre")->Equals(key)) {
return v8::True();
}
return v8::Handle<v8::Boolean>(); // do not intercept the call
}
THREADED_TEST(PrePropertyHandler) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> desc = v8::FunctionTemplate::New();
desc->InstanceTemplate()->SetNamedPropertyHandler(PrePropertyHandlerGet,
0,
PrePropertyHandlerHas);
LocalContext env(NULL, desc->InstanceTemplate());
Script::Compile(v8_str(
"var pre = 'Object: pre'; var on = 'Object: on';"))->Run();
v8::Handle<Value> result_pre = Script::Compile(v8_str("pre"))->Run();
CHECK_EQ(v8_str("PrePropertyHandler: pre"), result_pre);
v8::Handle<Value> result_on = Script::Compile(v8_str("on"))->Run();
CHECK_EQ(v8_str("Object: on"), result_on);
v8::Handle<Value> result_post = Script::Compile(v8_str("post"))->Run();
CHECK(result_post.IsEmpty());
}
THREADED_TEST(UndefinedIsNotEnumerable) {
v8::HandleScope scope;
LocalContext env;
v8::Handle<Value> result = Script::Compile(v8_str(
"this.propertyIsEnumerable(undefined)"))->Run();
CHECK(result->IsFalse());
}
v8::Handle<Script> call_recursively_script;
static const int kTargetRecursionDepth = 300; // near maximum
static v8::Handle<Value> CallScriptRecursivelyCall(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
int depth = args.This()->Get(v8_str("depth"))->Int32Value();
if (depth == kTargetRecursionDepth) return v8::Undefined();
args.This()->Set(v8_str("depth"), v8::Integer::New(depth + 1));
return call_recursively_script->Run();
}
static v8::Handle<Value> CallFunctionRecursivelyCall(
const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
int depth = args.This()->Get(v8_str("depth"))->Int32Value();
if (depth == kTargetRecursionDepth) {
printf("[depth = %d]\n", depth);
return v8::Undefined();
}
args.This()->Set(v8_str("depth"), v8::Integer::New(depth + 1));
v8::Handle<Value> function =
args.This()->Get(v8_str("callFunctionRecursively"));
return v8::Handle<Function>::Cast(function)->Call(args.This(), 0, NULL);
}
THREADED_TEST(DeepCrossLanguageRecursion) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> global = ObjectTemplate::New();
global->Set(v8_str("callScriptRecursively"),
v8::FunctionTemplate::New(CallScriptRecursivelyCall));
global->Set(v8_str("callFunctionRecursively"),
v8::FunctionTemplate::New(CallFunctionRecursivelyCall));
LocalContext env(NULL, global);
env->Global()->Set(v8_str("depth"), v8::Integer::New(0));
call_recursively_script = v8_compile("callScriptRecursively()");
v8::Handle<Value> result = call_recursively_script->Run();
call_recursively_script = v8::Handle<Script>();
env->Global()->Set(v8_str("depth"), v8::Integer::New(0));
Script::Compile(v8_str("callFunctionRecursively()"))->Run();
}
static v8::Handle<Value>
ThrowingPropertyHandlerGet(Local<String> key, const AccessorInfo&) {
ApiTestFuzzer::Fuzz();
return v8::ThrowException(key);
}
static v8::Handle<Value> ThrowingPropertyHandlerSet(Local<String> key,
Local<Value>,
const AccessorInfo&) {
v8::ThrowException(key);
return v8::Undefined(); // not the same as v8::Handle<v8::Value>()
}
THREADED_TEST(CallbackExceptionRegression) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New();
obj->SetNamedPropertyHandler(ThrowingPropertyHandlerGet,
ThrowingPropertyHandlerSet);
LocalContext env;
env->Global()->Set(v8_str("obj"), obj->NewInstance());
v8::Handle<Value> otto = Script::Compile(v8_str(
"try { with (obj) { otto; } } catch (e) { e; }"))->Run();
CHECK_EQ(v8_str("otto"), otto);
v8::Handle<Value> netto = Script::Compile(v8_str(
"try { with (obj) { netto = 4; } } catch (e) { e; }"))->Run();
CHECK_EQ(v8_str("netto"), netto);
}
static v8::Handle<Value> ThrowingGetAccessor(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
return v8::ThrowException(v8_str("g"));
}
static void ThrowingSetAccessor(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
v8::ThrowException(value);
}
THREADED_TEST(Regress1054726) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New();
obj->SetAccessor(v8_str("x"),
ThrowingGetAccessor,
ThrowingSetAccessor,
Local<Value>());
LocalContext env;
env->Global()->Set(v8_str("obj"), obj->NewInstance());
// Use the throwing property setter/getter in a loop to force
// the accessor ICs to be initialized.
v8::Handle<Value> result;
result = Script::Compile(v8_str(
"var result = '';"
"for (var i = 0; i < 5; i++) {"
" try { obj.x; } catch (e) { result += e; }"
"}; result"))->Run();
CHECK_EQ(v8_str("ggggg"), result);
result = Script::Compile(String::New(
"var result = '';"
"for (var i = 0; i < 5; i++) {"
" try { obj.x = i; } catch (e) { result += e; }"
"}; result"))->Run();
CHECK_EQ(v8_str("01234"), result);
}
THREADED_TEST(FunctionPrototype) {
v8::HandleScope scope;
Local<v8::FunctionTemplate> Foo = v8::FunctionTemplate::New();
Foo->PrototypeTemplate()->Set(v8_str("plak"), v8_num(321));
LocalContext env;
env->Global()->Set(v8_str("Foo"), Foo->GetFunction());
Local<Script> script = Script::Compile(v8_str("Foo.prototype.plak"));
CHECK_EQ(script->Run()->Int32Value(), 321);
}
THREADED_TEST(InternalFields) {
v8::HandleScope scope;
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
LocalContext env;
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New();
Local<v8::ObjectTemplate> instance_templ = templ->InstanceTemplate();
instance_templ->SetInternalFieldCount(1);
Local<v8::Object> obj = templ->GetFunction()->NewInstance();
CHECK_EQ(1, obj->InternalFieldCount());
CHECK(obj->GetInternalField(0)->IsUndefined());
obj->SetInternalField(0, v8_num(17));
CHECK_EQ(17, obj->GetInternalField(0)->Int32Value());
}
THREADED_TEST(IdentityHash) {
v8::HandleScope scope;
LocalContext env;
// Ensure that the test starts with an fresh heap to test whether the hash
// code is based on the address.
i::Heap::CollectAllGarbage();
Local<v8::Object> obj = v8::Object::New();
int hash = obj->GetIdentityHash();
int hash1 = obj->GetIdentityHash();
CHECK_EQ(hash, hash1);
int hash2 = v8::Object::New()->GetIdentityHash();
// Since the identity hash is essentially a random number two consecutive
// objects should not be assigned the same hash code. If the test below fails
// the random number generator should be evaluated.
CHECK_NE(hash, hash2);
i::Heap::CollectAllGarbage();
int hash3 = v8::Object::New()->GetIdentityHash();
// Make sure that the identity hash is not based on the initial address of
// the object alone. If the test below fails the random number generator
// should be evaluated.
CHECK_NE(hash, hash3);
int hash4 = obj->GetIdentityHash();
CHECK_EQ(hash, hash4);
}
THREADED_TEST(HiddenProperties) {
v8::HandleScope scope;
LocalContext env;
v8::Local<v8::Object> obj = v8::Object::New();
v8::Local<v8::String> key = v8_str("api-test::hidden-key");
v8::Local<v8::String> empty = v8_str("");
v8::Local<v8::String> prop_name = v8_str("prop_name");
i::Heap::CollectAllGarbage();
// Make sure delete of a non-existent hidden value works
CHECK(obj->DeleteHiddenValue(key));
CHECK(obj->SetHiddenValue(key, v8::Integer::New(1503)));
CHECK_EQ(1503, obj->GetHiddenValue(key)->Int32Value());
CHECK(obj->SetHiddenValue(key, v8::Integer::New(2002)));
CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
i::Heap::CollectAllGarbage();
// Make sure we do not find the hidden property.
CHECK(!obj->Has(empty));
CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
CHECK(obj->Get(empty)->IsUndefined());
CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
CHECK(obj->Set(empty, v8::Integer::New(2003)));
CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
CHECK_EQ(2003, obj->Get(empty)->Int32Value());
i::Heap::CollectAllGarbage();
// Add another property and delete it afterwards to force the object in
// slow case.
CHECK(obj->Set(prop_name, v8::Integer::New(2008)));
CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
CHECK_EQ(2008, obj->Get(prop_name)->Int32Value());
CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
CHECK(obj->Delete(prop_name));
CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
i::Heap::CollectAllGarbage();
CHECK(obj->DeleteHiddenValue(key));
CHECK(obj->GetHiddenValue(key).IsEmpty());
}
static v8::Handle<Value> InterceptorForHiddenProperties(
Local<String> name, const AccessorInfo& info) {
// Make sure objects move.
bool saved_always_compact = i::FLAG_always_compact;
if (!i::FLAG_never_compact) {
i::FLAG_always_compact = true;
}
// The whole goal of this interceptor is to cause a GC during local property
// lookup.
i::Heap::CollectAllGarbage();
i::FLAG_always_compact = saved_always_compact;
return v8::Handle<Value>();
}
THREADED_TEST(HiddenPropertiesWithInterceptors) {
v8::HandleScope scope;
LocalContext context;
v8::Local<v8::String> key = v8_str("api-test::hidden-key");
// Associate an interceptor with an object and start setting hidden values.
Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
Local<v8::ObjectTemplate> instance_templ = fun_templ->InstanceTemplate();
instance_templ->SetNamedPropertyHandler(InterceptorForHiddenProperties);
Local<v8::Function> function = fun_templ->GetFunction();
Local<v8::Object> obj = function->NewInstance();
CHECK(obj->SetHiddenValue(key, v8::Integer::New(2302)));
CHECK_EQ(2302, obj->GetHiddenValue(key)->Int32Value());
}
THREADED_TEST(External) {
v8::HandleScope scope;
int x = 3;
Local<v8::External> ext = v8::External::New(&x);
LocalContext env;
env->Global()->Set(v8_str("ext"), ext);
Local<Value> reext_obj = Script::Compile(v8_str("this.ext"))->Run();
v8::Handle<v8::External> reext = v8::Handle<v8::External>::Cast(reext_obj);
int* ptr = static_cast<int*>(reext->Value());
CHECK_EQ(x, 3);
*ptr = 10;
CHECK_EQ(x, 10);
// Make sure unaligned pointers are wrapped properly.
char* data = i::StrDup("0123456789");
Local<v8::Value> zero = v8::External::Wrap(&data[0]);
Local<v8::Value> one = v8::External::Wrap(&data[1]);
Local<v8::Value> two = v8::External::Wrap(&data[2]);
Local<v8::Value> three = v8::External::Wrap(&data[3]);
char* char_ptr = reinterpret_cast<char*>(v8::External::Unwrap(zero));
CHECK_EQ('0', *char_ptr);
char_ptr = reinterpret_cast<char*>(v8::External::Unwrap(one));
CHECK_EQ('1', *char_ptr);
char_ptr = reinterpret_cast<char*>(v8::External::Unwrap(two));
CHECK_EQ('2', *char_ptr);
char_ptr = reinterpret_cast<char*>(v8::External::Unwrap(three));
CHECK_EQ('3', *char_ptr);
i::DeleteArray(data);
}
THREADED_TEST(GlobalHandle) {
v8::Persistent<String> global;
{
v8::HandleScope scope;
Local<String> str = v8_str("str");
global = v8::Persistent<String>::New(str);
}
CHECK_EQ(global->Length(), 3);
global.Dispose();
}
THREADED_TEST(ScriptException) {
v8::HandleScope scope;
LocalContext env;
Local<Script> script = Script::Compile(v8_str("throw 'panama!';"));
v8::TryCatch try_catch;
Local<Value> result = script->Run();
CHECK(result.IsEmpty());
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value(try_catch.Exception());
CHECK_EQ(*exception_value, "panama!");
}
bool message_received;
static void check_message(v8::Handle<v8::Message> message,
v8::Handle<Value> data) {
CHECK_EQ(5.76, data->NumberValue());
CHECK_EQ(6.75, message->GetScriptResourceName()->NumberValue());
CHECK_EQ(7.56, message->GetScriptData()->NumberValue());
message_received = true;
}
THREADED_TEST(MessageHandlerData) {
message_received = false;
v8::HandleScope scope;
CHECK(!message_received);
v8::V8::AddMessageListener(check_message, v8_num(5.76));
LocalContext context;
v8::ScriptOrigin origin =
v8::ScriptOrigin(v8_str("6.75"));
v8::Handle<v8::Script> script = Script::Compile(v8_str("throw 'error'"),
&origin);
script->SetData(v8_str("7.56"));
script->Run();
CHECK(message_received);
// clear out the message listener
v8::V8::RemoveMessageListeners(check_message);
}
THREADED_TEST(GetSetProperty) {
v8::HandleScope scope;
LocalContext context;
context->Global()->Set(v8_str("foo"), v8_num(14));
context->Global()->Set(v8_str("12"), v8_num(92));
context->Global()->Set(v8::Integer::New(16), v8_num(32));
context->Global()->Set(v8_num(13), v8_num(56));
Local<Value> foo = Script::Compile(v8_str("this.foo"))->Run();
CHECK_EQ(14, foo->Int32Value());
Local<Value> twelve = Script::Compile(v8_str("this[12]"))->Run();
CHECK_EQ(92, twelve->Int32Value());
Local<Value> sixteen = Script::Compile(v8_str("this[16]"))->Run();
CHECK_EQ(32, sixteen->Int32Value());
Local<Value> thirteen = Script::Compile(v8_str("this[13]"))->Run();
CHECK_EQ(56, thirteen->Int32Value());
CHECK_EQ(92, context->Global()->Get(v8::Integer::New(12))->Int32Value());
CHECK_EQ(92, context->Global()->Get(v8_str("12"))->Int32Value());
CHECK_EQ(92, context->Global()->Get(v8_num(12))->Int32Value());
CHECK_EQ(32, context->Global()->Get(v8::Integer::New(16))->Int32Value());
CHECK_EQ(32, context->Global()->Get(v8_str("16"))->Int32Value());
CHECK_EQ(32, context->Global()->Get(v8_num(16))->Int32Value());
CHECK_EQ(56, context->Global()->Get(v8::Integer::New(13))->Int32Value());
CHECK_EQ(56, context->Global()->Get(v8_str("13"))->Int32Value());
CHECK_EQ(56, context->Global()->Get(v8_num(13))->Int32Value());
}
THREADED_TEST(PropertyAttributes) {
v8::HandleScope scope;
LocalContext context;
// read-only
Local<String> prop = v8_str("read_only");
context->Global()->Set(prop, v8_num(7), v8::ReadOnly);
CHECK_EQ(7, context->Global()->Get(prop)->Int32Value());
Script::Compile(v8_str("read_only = 9"))->Run();
CHECK_EQ(7, context->Global()->Get(prop)->Int32Value());
context->Global()->Set(prop, v8_num(10));
CHECK_EQ(7, context->Global()->Get(prop)->Int32Value());
// dont-delete
prop = v8_str("dont_delete");
context->Global()->Set(prop, v8_num(13), v8::DontDelete);
CHECK_EQ(13, context->Global()->Get(prop)->Int32Value());
Script::Compile(v8_str("delete dont_delete"))->Run();
CHECK_EQ(13, context->Global()->Get(prop)->Int32Value());
}
THREADED_TEST(Array) {
v8::HandleScope scope;
LocalContext context;
Local<v8::Array> array = v8::Array::New();
CHECK_EQ(0, array->Length());
CHECK(array->Get(v8::Integer::New(0))->IsUndefined());
CHECK(!array->Has(0));
CHECK(array->Get(v8::Integer::New(100))->IsUndefined());
CHECK(!array->Has(100));
array->Set(v8::Integer::New(2), v8_num(7));
CHECK_EQ(3, array->Length());
CHECK(!array->Has(0));
CHECK(!array->Has(1));
CHECK(array->Has(2));
CHECK_EQ(7, array->Get(v8::Integer::New(2))->Int32Value());
Local<Value> obj = Script::Compile(v8_str("[1, 2, 3]"))->Run();
Local<v8::Array> arr = Local<v8::Array>::Cast(obj);
CHECK_EQ(3, arr->Length());
CHECK_EQ(1, arr->Get(v8::Integer::New(0))->Int32Value());
CHECK_EQ(2, arr->Get(v8::Integer::New(1))->Int32Value());
CHECK_EQ(3, arr->Get(v8::Integer::New(2))->Int32Value());
}
v8::Handle<Value> HandleF(const v8::Arguments& args) {
v8::HandleScope scope;
ApiTestFuzzer::Fuzz();
Local<v8::Array> result = v8::Array::New(args.Length());
for (int i = 0; i < args.Length(); i++)
result->Set(v8::Integer::New(i), args[i]);
return scope.Close(result);
}
THREADED_TEST(Vector) {
v8::HandleScope scope;
Local<ObjectTemplate> global = ObjectTemplate::New();
global->Set(v8_str("f"), v8::FunctionTemplate::New(HandleF));
LocalContext context(0, global);
const char* fun = "f()";
Local<v8::Array> a0 =
Local<v8::Array>::Cast(Script::Compile(String::New(fun))->Run());
CHECK_EQ(0, a0->Length());
const char* fun2 = "f(11)";
Local<v8::Array> a1 =
Local<v8::Array>::Cast(Script::Compile(String::New(fun2))->Run());
CHECK_EQ(1, a1->Length());
CHECK_EQ(11, a1->Get(v8::Integer::New(0))->Int32Value());
const char* fun3 = "f(12, 13)";
Local<v8::Array> a2 =
Local<v8::Array>::Cast(Script::Compile(String::New(fun3))->Run());
CHECK_EQ(2, a2->Length());
CHECK_EQ(12, a2->Get(v8::Integer::New(0))->Int32Value());
CHECK_EQ(13, a2->Get(v8::Integer::New(1))->Int32Value());
const char* fun4 = "f(14, 15, 16)";
Local<v8::Array> a3 =
Local<v8::Array>::Cast(Script::Compile(String::New(fun4))->Run());
CHECK_EQ(3, a3->Length());
CHECK_EQ(14, a3->Get(v8::Integer::New(0))->Int32Value());
CHECK_EQ(15, a3->Get(v8::Integer::New(1))->Int32Value());
CHECK_EQ(16, a3->Get(v8::Integer::New(2))->Int32Value());
const char* fun5 = "f(17, 18, 19, 20)";
Local<v8::Array> a4 =
Local<v8::Array>::Cast(Script::Compile(String::New(fun5))->Run());
CHECK_EQ(4, a4->Length());
CHECK_EQ(17, a4->Get(v8::Integer::New(0))->Int32Value());
CHECK_EQ(18, a4->Get(v8::Integer::New(1))->Int32Value());
CHECK_EQ(19, a4->Get(v8::Integer::New(2))->Int32Value());
CHECK_EQ(20, a4->Get(v8::Integer::New(3))->Int32Value());
}
THREADED_TEST(FunctionCall) {
v8::HandleScope scope;
LocalContext context;
CompileRun(
"function Foo() {"
" var result = [];"
" for (var i = 0; i < arguments.length; i++) {"
" result.push(arguments[i]);"
" }"
" return result;"
"}");
Local<Function> Foo =
Local<Function>::Cast(context->Global()->Get(v8_str("Foo")));
v8::Handle<Value>* args0 = NULL;
Local<v8::Array> a0 = Local<v8::Array>::Cast(Foo->Call(Foo, 0, args0));
CHECK_EQ(0, a0->Length());
v8::Handle<Value> args1[] = { v8_num(1.1) };
Local<v8::Array> a1 = Local<v8::Array>::Cast(Foo->Call(Foo, 1, args1));
CHECK_EQ(1, a1->Length());
CHECK_EQ(1.1, a1->Get(v8::Integer::New(0))->NumberValue());
v8::Handle<Value> args2[] = { v8_num(2.2),
v8_num(3.3) };
Local<v8::Array> a2 = Local<v8::Array>::Cast(Foo->Call(Foo, 2, args2));
CHECK_EQ(2, a2->Length());
CHECK_EQ(2.2, a2->Get(v8::Integer::New(0))->NumberValue());
CHECK_EQ(3.3, a2->Get(v8::Integer::New(1))->NumberValue());
v8::Handle<Value> args3[] = { v8_num(4.4),
v8_num(5.5),
v8_num(6.6) };
Local<v8::Array> a3 = Local<v8::Array>::Cast(Foo->Call(Foo, 3, args3));
CHECK_EQ(3, a3->Length());
CHECK_EQ(4.4, a3->Get(v8::Integer::New(0))->NumberValue());
CHECK_EQ(5.5, a3->Get(v8::Integer::New(1))->NumberValue());
CHECK_EQ(6.6, a3->Get(v8::Integer::New(2))->NumberValue());
v8::Handle<Value> args4[] = { v8_num(7.7),
v8_num(8.8),
v8_num(9.9),
v8_num(10.11) };
Local<v8::Array> a4 = Local<v8::Array>::Cast(Foo->Call(Foo, 4, args4));
CHECK_EQ(4, a4->Length());
CHECK_EQ(7.7, a4->Get(v8::Integer::New(0))->NumberValue());
CHECK_EQ(8.8, a4->Get(v8::Integer::New(1))->NumberValue());
CHECK_EQ(9.9, a4->Get(v8::Integer::New(2))->NumberValue());
CHECK_EQ(10.11, a4->Get(v8::Integer::New(3))->NumberValue());
}
static const char* js_code_causing_out_of_memory =
"var a = new Array(); while(true) a.push(a);";
// These tests run for a long time and prevent us from running tests
// that come after them so they cannot run in parallel.
TEST(OutOfMemory) {
// It's not possible to read a snapshot into a heap with different dimensions.
if (v8::internal::Snapshot::IsEnabled()) return;
// Set heap limits.
static const int K = 1024;
v8::ResourceConstraints constraints;
constraints.set_max_young_space_size(256 * K);
constraints.set_max_old_space_size(4 * K * K);
v8::SetResourceConstraints(&constraints);
// Execute a script that causes out of memory.
v8::HandleScope scope;
LocalContext context;
v8::V8::IgnoreOutOfMemoryException();
Local<Script> script =
Script::Compile(String::New(js_code_causing_out_of_memory));
Local<Value> result = script->Run();
// Check for out of memory state.
CHECK(result.IsEmpty());
CHECK(context->HasOutOfMemoryException());
}
v8::Handle<Value> ProvokeOutOfMemory(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
v8::HandleScope scope;
LocalContext context;
Local<Script> script =
Script::Compile(String::New(js_code_causing_out_of_memory));
Local<Value> result = script->Run();
// Check for out of memory state.
CHECK(result.IsEmpty());
CHECK(context->HasOutOfMemoryException());
return result;
}
TEST(OutOfMemoryNested) {
// It's not possible to read a snapshot into a heap with different dimensions.
if (v8::internal::Snapshot::IsEnabled()) return;
// Set heap limits.
static const int K = 1024;
v8::ResourceConstraints constraints;
constraints.set_max_young_space_size(256 * K);
constraints.set_max_old_space_size(4 * K * K);
v8::SetResourceConstraints(&constraints);
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("ProvokeOutOfMemory"),
v8::FunctionTemplate::New(ProvokeOutOfMemory));
LocalContext context(0, templ);
v8::V8::IgnoreOutOfMemoryException();
Local<Value> result = CompileRun(
"var thrown = false;"
"try {"
" ProvokeOutOfMemory();"
"} catch (e) {"
" thrown = true;"
"}");
// Check for out of memory state.
CHECK(result.IsEmpty());
CHECK(context->HasOutOfMemoryException());
}
TEST(HugeConsStringOutOfMemory) {
// It's not possible to read a snapshot into a heap with different dimensions.
if (v8::internal::Snapshot::IsEnabled()) return;
v8::HandleScope scope;
LocalContext context;
// Set heap limits.
static const int K = 1024;
v8::ResourceConstraints constraints;
constraints.set_max_young_space_size(256 * K);
constraints.set_max_old_space_size(2 * K * K);
v8::SetResourceConstraints(&constraints);
// Execute a script that causes out of memory.
v8::V8::IgnoreOutOfMemoryException();
// Build huge string. This should fail with out of memory exception.
Local<Value> result = CompileRun(
"var str = Array.prototype.join.call({length: 513}, \"A\").toUpperCase();"
"for (var i = 0; i < 21; i++) { str = str + str; }");
// Check for out of memory state.
CHECK(result.IsEmpty());
CHECK(context->HasOutOfMemoryException());
}
THREADED_TEST(ConstructCall) {
v8::HandleScope scope;
LocalContext context;
CompileRun(
"function Foo() {"
" var result = [];"
" for (var i = 0; i < arguments.length; i++) {"
" result.push(arguments[i]);"
" }"
" return result;"
"}");
Local<Function> Foo =
Local<Function>::Cast(context->Global()->Get(v8_str("Foo")));
v8::Handle<Value>* args0 = NULL;
Local<v8::Array> a0 = Local<v8::Array>::Cast(Foo->NewInstance(0, args0));
CHECK_EQ(0, a0->Length());
v8::Handle<Value> args1[] = { v8_num(1.1) };
Local<v8::Array> a1 = Local<v8::Array>::Cast(Foo->NewInstance(1, args1));
CHECK_EQ(1, a1->Length());
CHECK_EQ(1.1, a1->Get(v8::Integer::New(0))->NumberValue());
v8::Handle<Value> args2[] = { v8_num(2.2),
v8_num(3.3) };
Local<v8::Array> a2 = Local<v8::Array>::Cast(Foo->NewInstance(2, args2));
CHECK_EQ(2, a2->Length());
CHECK_EQ(2.2, a2->Get(v8::Integer::New(0))->NumberValue());
CHECK_EQ(3.3, a2->Get(v8::Integer::New(1))->NumberValue());
v8::Handle<Value> args3[] = { v8_num(4.4),
v8_num(5.5),
v8_num(6.6) };
Local<v8::Array> a3 = Local<v8::Array>::Cast(Foo->NewInstance(3, args3));
CHECK_EQ(3, a3->Length());
CHECK_EQ(4.4, a3->Get(v8::Integer::New(0))->NumberValue());
CHECK_EQ(5.5, a3->Get(v8::Integer::New(1))->NumberValue());
CHECK_EQ(6.6, a3->Get(v8::Integer::New(2))->NumberValue());
v8::Handle<Value> args4[] = { v8_num(7.7),
v8_num(8.8),
v8_num(9.9),
v8_num(10.11) };
Local<v8::Array> a4 = Local<v8::Array>::Cast(Foo->NewInstance(4, args4));
CHECK_EQ(4, a4->Length());
CHECK_EQ(7.7, a4->Get(v8::Integer::New(0))->NumberValue());
CHECK_EQ(8.8, a4->Get(v8::Integer::New(1))->NumberValue());
CHECK_EQ(9.9, a4->Get(v8::Integer::New(2))->NumberValue());
CHECK_EQ(10.11, a4->Get(v8::Integer::New(3))->NumberValue());
}
static void CheckUncle(v8::TryCatch* try_catch) {
CHECK(try_catch->HasCaught());
String::AsciiValue str_value(try_catch->Exception());
CHECK_EQ(*str_value, "uncle?");
try_catch->Reset();
}
THREADED_TEST(ConversionException) {
v8::HandleScope scope;
LocalContext env;
CompileRun(
"function TestClass() { };"
"TestClass.prototype.toString = function () { throw 'uncle?'; };"
"var obj = new TestClass();");
Local<Value> obj = env->Global()->Get(v8_str("obj"));
v8::TryCatch try_catch;
Local<Value> to_string_result = obj->ToString();
CHECK(to_string_result.IsEmpty());
CheckUncle(&try_catch);
Local<Value> to_number_result = obj->ToNumber();
CHECK(to_number_result.IsEmpty());
CheckUncle(&try_catch);
Local<Value> to_integer_result = obj->ToInteger();
CHECK(to_integer_result.IsEmpty());
CheckUncle(&try_catch);
Local<Value> to_uint32_result = obj->ToUint32();
CHECK(to_uint32_result.IsEmpty());
CheckUncle(&try_catch);
Local<Value> to_int32_result = obj->ToInt32();
CHECK(to_int32_result.IsEmpty());
CheckUncle(&try_catch);
Local<Value> to_object_result = v8::Undefined()->ToObject();
CHECK(to_object_result.IsEmpty());
CHECK(try_catch.HasCaught());
try_catch.Reset();
int32_t int32_value = obj->Int32Value();
CHECK_EQ(0, int32_value);
CheckUncle(&try_catch);
uint32_t uint32_value = obj->Uint32Value();
CHECK_EQ(0, uint32_value);
CheckUncle(&try_catch);
double number_value = obj->NumberValue();
CHECK_NE(0, IsNaN(number_value));
CheckUncle(&try_catch);
int64_t integer_value = obj->IntegerValue();
CHECK_EQ(0.0, static_cast<double>(integer_value));
CheckUncle(&try_catch);
}
v8::Handle<Value> ThrowFromC(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
return v8::ThrowException(v8_str("konto"));
}
v8::Handle<Value> CCatcher(const v8::Arguments& args) {
if (args.Length() < 1) return v8::Boolean::New(false);
v8::HandleScope scope;
v8::TryCatch try_catch;
Local<Value> result = v8::Script::Compile(args[0]->ToString())->Run();
CHECK(!try_catch.HasCaught() || result.IsEmpty());
return v8::Boolean::New(try_catch.HasCaught());
}
THREADED_TEST(APICatch) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("ThrowFromC"),
v8::FunctionTemplate::New(ThrowFromC));
LocalContext context(0, templ);
CompileRun(
"var thrown = false;"
"try {"
" ThrowFromC();"
"} catch (e) {"
" thrown = true;"
"}");
Local<Value> thrown = context->Global()->Get(v8_str("thrown"));
CHECK(thrown->BooleanValue());
}
THREADED_TEST(APIThrowTryCatch) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("ThrowFromC"),
v8::FunctionTemplate::New(ThrowFromC));
LocalContext context(0, templ);
v8::TryCatch try_catch;
CompileRun("ThrowFromC();");
CHECK(try_catch.HasCaught());
}
// Test that a try-finally block doesn't shadow a try-catch block
// when setting up an external handler.
//
// BUG(271): Some of the exception propagation does not work on the
// ARM simulator because the simulator separates the C++ stack and the
// JS stack. This test therefore fails on the simulator. The test is
// not threaded to allow the threading tests to run on the simulator.
TEST(TryCatchInTryFinally) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("CCatcher"),
v8::FunctionTemplate::New(CCatcher));
LocalContext context(0, templ);
Local<Value> result = CompileRun("try {"
" try {"
" CCatcher('throw 7;');"
" } finally {"
" }"
"} catch (e) {"
"}");
CHECK(result->IsTrue());
}
static void receive_message(v8::Handle<v8::Message> message,
v8::Handle<v8::Value> data) {
message->Get();
message_received = true;
}
TEST(APIThrowMessage) {
message_received = false;
v8::HandleScope scope;
v8::V8::AddMessageListener(receive_message);
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("ThrowFromC"),
v8::FunctionTemplate::New(ThrowFromC));
LocalContext context(0, templ);
CompileRun("ThrowFromC();");
CHECK(message_received);
v8::V8::RemoveMessageListeners(check_message);
}
TEST(APIThrowMessageAndVerboseTryCatch) {
message_received = false;
v8::HandleScope scope;
v8::V8::AddMessageListener(receive_message);
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("ThrowFromC"),
v8::FunctionTemplate::New(ThrowFromC));
LocalContext context(0, templ);
v8::TryCatch try_catch;
try_catch.SetVerbose(true);
Local<Value> result = CompileRun("ThrowFromC();");
CHECK(try_catch.HasCaught());
CHECK(result.IsEmpty());
CHECK(message_received);
v8::V8::RemoveMessageListeners(check_message);
}
THREADED_TEST(ExternalScriptException) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("ThrowFromC"),
v8::FunctionTemplate::New(ThrowFromC));
LocalContext context(0, templ);
v8::TryCatch try_catch;
Local<Script> script
= Script::Compile(v8_str("ThrowFromC(); throw 'panama';"));
Local<Value> result = script->Run();
CHECK(result.IsEmpty());
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value(try_catch.Exception());
CHECK_EQ("konto", *exception_value);
}
v8::Handle<Value> CThrowCountDown(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
CHECK_EQ(4, args.Length());
int count = args[0]->Int32Value();
int cInterval = args[2]->Int32Value();
if (count == 0) {
return v8::ThrowException(v8_str("FromC"));
} else {
Local<v8::Object> global = Context::GetCurrent()->Global();
Local<Value> fun = global->Get(v8_str("JSThrowCountDown"));
v8::Handle<Value> argv[] = { v8_num(count - 1),
args[1],
args[2],
args[3] };
if (count % cInterval == 0) {
v8::TryCatch try_catch;
Local<Value> result =
v8::Handle<Function>::Cast(fun)->Call(global, 4, argv);
int expected = args[3]->Int32Value();
if (try_catch.HasCaught()) {
CHECK_EQ(expected, count);
CHECK(result.IsEmpty());
CHECK(!i::Top::has_scheduled_exception());
} else {
CHECK_NE(expected, count);
}
return result;
} else {
return v8::Handle<Function>::Cast(fun)->Call(global, 4, argv);
}
}
}
v8::Handle<Value> JSCheck(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
CHECK_EQ(3, args.Length());
bool equality = args[0]->BooleanValue();
int count = args[1]->Int32Value();
int expected = args[2]->Int32Value();
if (equality) {
CHECK_EQ(count, expected);
} else {
CHECK_NE(count, expected);
}
return v8::Undefined();
}
THREADED_TEST(EvalInTryFinally) {
v8::HandleScope scope;
LocalContext context;
v8::TryCatch try_catch;
CompileRun("(function() {"
" try {"
" eval('asldkf (*&^&*^');"
" } finally {"
" return;"
" }"
"})()");
CHECK(!try_catch.HasCaught());
}
// This test works by making a stack of alternating JavaScript and C
// activations. These activations set up exception handlers with regular
// intervals, one interval for C activations and another for JavaScript
// activations. When enough activations have been created an exception is
// thrown and we check that the right activation catches the exception and that
// no other activations do. The right activation is always the topmost one with
// a handler, regardless of whether it is in JavaScript or C.
//
// The notation used to describe a test case looks like this:
//
// *JS[4] *C[3] @JS[2] C[1] JS[0]
//
// Each entry is an activation, either JS or C. The index is the count at that
// level. Stars identify activations with exception handlers, the @ identifies
// the exception handler that should catch the exception.
//
// BUG(271): Some of the exception propagation does not work on the
// ARM simulator because the simulator separates the C++ stack and the
// JS stack. This test therefore fails on the simulator. The test is
// not threaded to allow the threading tests to run on the simulator.
TEST(ExceptionOrder) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("check"), v8::FunctionTemplate::New(JSCheck));
templ->Set(v8_str("CThrowCountDown"),
v8::FunctionTemplate::New(CThrowCountDown));
LocalContext context(0, templ);
CompileRun(
"function JSThrowCountDown(count, jsInterval, cInterval, expected) {"
" if (count == 0) throw 'FromJS';"
" if (count % jsInterval == 0) {"
" try {"
" var value = CThrowCountDown(count - 1,"
" jsInterval,"
" cInterval,"
" expected);"
" check(false, count, expected);"
" return value;"
" } catch (e) {"
" check(true, count, expected);"
" }"
" } else {"
" return CThrowCountDown(count - 1, jsInterval, cInterval, expected);"
" }"
"}");
Local<Function> fun =
Local<Function>::Cast(context->Global()->Get(v8_str("JSThrowCountDown")));
const int argc = 4;
// count jsInterval cInterval expected
// *JS[4] *C[3] @JS[2] C[1] JS[0]
v8::Handle<Value> a0[argc] = { v8_num(4), v8_num(2), v8_num(3), v8_num(2) };
fun->Call(fun, argc, a0);
// JS[5] *C[4] JS[3] @C[2] JS[1] C[0]
v8::Handle<Value> a1[argc] = { v8_num(5), v8_num(6), v8_num(1), v8_num(2) };
fun->Call(fun, argc, a1);
// JS[6] @C[5] JS[4] C[3] JS[2] C[1] JS[0]
v8::Handle<Value> a2[argc] = { v8_num(6), v8_num(7), v8_num(5), v8_num(5) };
fun->Call(fun, argc, a2);
// @JS[6] C[5] JS[4] C[3] JS[2] C[1] JS[0]
v8::Handle<Value> a3[argc] = { v8_num(6), v8_num(6), v8_num(7), v8_num(6) };
fun->Call(fun, argc, a3);
// JS[6] *C[5] @JS[4] C[3] JS[2] C[1] JS[0]
v8::Handle<Value> a4[argc] = { v8_num(6), v8_num(4), v8_num(5), v8_num(4) };
fun->Call(fun, argc, a4);
// JS[6] C[5] *JS[4] @C[3] JS[2] C[1] JS[0]
v8::Handle<Value> a5[argc] = { v8_num(6), v8_num(4), v8_num(3), v8_num(3) };
fun->Call(fun, argc, a5);
}
v8::Handle<Value> ThrowValue(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
CHECK_EQ(1, args.Length());
return v8::ThrowException(args[0]);
}
THREADED_TEST(ThrowValues) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("Throw"), v8::FunctionTemplate::New(ThrowValue));
LocalContext context(0, templ);
v8::Handle<v8::Array> result = v8::Handle<v8::Array>::Cast(CompileRun(
"function Run(obj) {"
" try {"
" Throw(obj);"
" } catch (e) {"
" return e;"
" }"
" return 'no exception';"
"}"
"[Run('str'), Run(1), Run(0), Run(null), Run(void 0)];"));
CHECK_EQ(5, result->Length());
CHECK(result->Get(v8::Integer::New(0))->IsString());
CHECK(result->Get(v8::Integer::New(1))->IsNumber());
CHECK_EQ(1, result->Get(v8::Integer::New(1))->Int32Value());
CHECK(result->Get(v8::Integer::New(2))->IsNumber());
CHECK_EQ(0, result->Get(v8::Integer::New(2))->Int32Value());
CHECK(result->Get(v8::Integer::New(3))->IsNull());
CHECK(result->Get(v8::Integer::New(4))->IsUndefined());
}
THREADED_TEST(CatchZero) {
v8::HandleScope scope;
LocalContext context;
v8::TryCatch try_catch;
CHECK(!try_catch.HasCaught());
Script::Compile(v8_str("throw 10"))->Run();
CHECK(try_catch.HasCaught());
CHECK_EQ(10, try_catch.Exception()->Int32Value());
try_catch.Reset();
CHECK(!try_catch.HasCaught());
Script::Compile(v8_str("throw 0"))->Run();
CHECK(try_catch.HasCaught());
CHECK_EQ(0, try_catch.Exception()->Int32Value());
}
THREADED_TEST(CatchExceptionFromWith) {
v8::HandleScope scope;
LocalContext context;
v8::TryCatch try_catch;
CHECK(!try_catch.HasCaught());
Script::Compile(v8_str("var o = {}; with (o) { throw 42; }"))->Run();
CHECK(try_catch.HasCaught());
}
THREADED_TEST(Equality) {
v8::HandleScope scope;
LocalContext context;
// Check that equality works at all before relying on CHECK_EQ
CHECK(v8_str("a")->Equals(v8_str("a")));
CHECK(!v8_str("a")->Equals(v8_str("b")));
CHECK_EQ(v8_str("a"), v8_str("a"));
CHECK_NE(v8_str("a"), v8_str("b"));
CHECK_EQ(v8_num(1), v8_num(1));
CHECK_EQ(v8_num(1.00), v8_num(1));
CHECK_NE(v8_num(1), v8_num(2));
// Assume String is not symbol.
CHECK(v8_str("a")->StrictEquals(v8_str("a")));
CHECK(!v8_str("a")->StrictEquals(v8_str("b")));
CHECK(!v8_str("5")->StrictEquals(v8_num(5)));
CHECK(v8_num(1)->StrictEquals(v8_num(1)));
CHECK(!v8_num(1)->StrictEquals(v8_num(2)));
CHECK(v8_num(0)->StrictEquals(v8_num(-0)));
Local<Value> not_a_number = v8_num(i::OS::nan_value());
CHECK(!not_a_number->StrictEquals(not_a_number));
CHECK(v8::False()->StrictEquals(v8::False()));
CHECK(!v8::False()->StrictEquals(v8::Undefined()));
v8::Handle<v8::Object> obj = v8::Object::New();
v8::Persistent<v8::Object> alias = v8::Persistent<v8::Object>::New(obj);
CHECK(alias->StrictEquals(obj));
alias.Dispose();
}
THREADED_TEST(MultiRun) {
v8::HandleScope scope;
LocalContext context;
Local<Script> script = Script::Compile(v8_str("x"));
for (int i = 0; i < 10; i++)
script->Run();
}
static v8::Handle<Value> GetXValue(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK_EQ(info.Data(), v8_str("donut"));
CHECK_EQ(name, v8_str("x"));
return name;
}
THREADED_TEST(SimplePropertyRead) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut"));
LocalContext context;
context->Global()->Set(v8_str("obj"), templ->NewInstance());
Local<Script> script = Script::Compile(v8_str("obj.x"));
for (int i = 0; i < 10; i++) {
Local<Value> result = script->Run();
CHECK_EQ(result, v8_str("x"));
}
}
v8::Persistent<Value> xValue;
static void SetXValue(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
CHECK_EQ(value, v8_num(4));
CHECK_EQ(info.Data(), v8_str("donut"));
CHECK_EQ(name, v8_str("x"));
CHECK(xValue.IsEmpty());
xValue = v8::Persistent<Value>::New(value);
}
THREADED_TEST(SimplePropertyWrite) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessor(v8_str("x"), GetXValue, SetXValue, v8_str("donut"));
LocalContext context;
context->Global()->Set(v8_str("obj"), templ->NewInstance());
Local<Script> script = Script::Compile(v8_str("obj.x = 4"));
for (int i = 0; i < 10; i++) {
CHECK(xValue.IsEmpty());
script->Run();
CHECK_EQ(v8_num(4), xValue);
xValue.Dispose();
xValue = v8::Persistent<Value>();
}
}
static v8::Handle<Value> XPropertyGetter(Local<String> property,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.Data()->IsUndefined());
return property;
}
THREADED_TEST(NamedInterceporPropertyRead) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(XPropertyGetter);
LocalContext context;
context->Global()->Set(v8_str("obj"), templ->NewInstance());
Local<Script> script = Script::Compile(v8_str("obj.x"));
for (int i = 0; i < 10; i++) {
Local<Value> result = script->Run();
CHECK_EQ(result, v8_str("x"));
}
}
THREADED_TEST(MultiContexts) {
v8::HandleScope scope;
v8::Handle<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("dummy"), v8::FunctionTemplate::New(DummyCallHandler));
Local<String> password = v8_str("Password");
// Create an environment
LocalContext context0(0, templ);
context0->SetSecurityToken(password);
v8::Handle<v8::Object> global0 = context0->Global();
global0->Set(v8_str("custom"), v8_num(1234));
CHECK_EQ(1234, global0->Get(v8_str("custom"))->Int32Value());
// Create an independent environment
LocalContext context1(0, templ);
context1->SetSecurityToken(password);
v8::Handle<v8::Object> global1 = context1->Global();
global1->Set(v8_str("custom"), v8_num(1234));
CHECK_NE(global0, global1);
CHECK_EQ(1234, global0->Get(v8_str("custom"))->Int32Value());
CHECK_EQ(1234, global1->Get(v8_str("custom"))->Int32Value());
// Now create a new context with the old global
LocalContext context2(0, templ, global1);
context2->SetSecurityToken(password);
v8::Handle<v8::Object> global2 = context2->Global();
CHECK_EQ(global1, global2);
CHECK_EQ(0, global1->Get(v8_str("custom"))->Int32Value());
CHECK_EQ(0, global2->Get(v8_str("custom"))->Int32Value());
}
THREADED_TEST(FunctionPrototypeAcrossContexts) {
// Make sure that functions created by cloning boilerplates cannot
// communicate through their __proto__ field.
v8::HandleScope scope;
LocalContext env0;
v8::Handle<v8::Object> global0 =
env0->Global();
v8::Handle<v8::Object> object0 =
v8::Handle<v8::Object>::Cast(global0->Get(v8_str("Object")));
v8::Handle<v8::Object> tostring0 =
v8::Handle<v8::Object>::Cast(object0->Get(v8_str("toString")));
v8::Handle<v8::Object> proto0 =
v8::Handle<v8::Object>::Cast(tostring0->Get(v8_str("__proto__")));
proto0->Set(v8_str("custom"), v8_num(1234));
LocalContext env1;
v8::Handle<v8::Object> global1 =
env1->Global();
v8::Handle<v8::Object> object1 =
v8::Handle<v8::Object>::Cast(global1->Get(v8_str("Object")));
v8::Handle<v8::Object> tostring1 =
v8::Handle<v8::Object>::Cast(object1->Get(v8_str("toString")));
v8::Handle<v8::Object> proto1 =
v8::Handle<v8::Object>::Cast(tostring1->Get(v8_str("__proto__")));
CHECK(!proto1->Has(v8_str("custom")));
}
THREADED_TEST(Regress892105) {
// Make sure that object and array literals created by cloning
// boilerplates cannot communicate through their __proto__
// field. This is rather difficult to check, but we try to add stuff
// to Object.prototype and Array.prototype and create a new
// environment. This should succeed.
v8::HandleScope scope;
Local<String> source = v8_str("Object.prototype.obj = 1234;"
"Array.prototype.arr = 4567;"
"8901");
LocalContext env0;
Local<Script> script0 = Script::Compile(source);
CHECK_EQ(8901.0, script0->Run()->NumberValue());
LocalContext env1;
Local<Script> script1 = Script::Compile(source);
CHECK_EQ(8901.0, script1->Run()->NumberValue());
}
static void ExpectString(const char* code, const char* expected) {
Local<Value> result = CompileRun(code);
CHECK(result->IsString());
String::AsciiValue ascii(result);
CHECK_EQ(0, strcmp(*ascii, expected));
}
static void ExpectBoolean(const char* code, bool expected) {
Local<Value> result = CompileRun(code);
CHECK(result->IsBoolean());
CHECK_EQ(expected, result->BooleanValue());
}
static void ExpectObject(const char* code, Local<Value> expected) {
Local<Value> result = CompileRun(code);
CHECK(result->Equals(expected));
}
THREADED_TEST(UndetectableObject) {
v8::HandleScope scope;
LocalContext env;
Local<v8::FunctionTemplate> desc =
v8::FunctionTemplate::New(0, v8::Handle<Value>());
desc->InstanceTemplate()->MarkAsUndetectable(); // undetectable
Local<v8::Object> obj = desc->GetFunction()->NewInstance();
env->Global()->Set(v8_str("undetectable"), obj);
ExpectString("undetectable.toString()", "[object Object]");
ExpectString("typeof undetectable", "undefined");
ExpectString("typeof(undetectable)", "undefined");
ExpectBoolean("typeof undetectable == 'undefined'", true);
ExpectBoolean("typeof undetectable == 'object'", false);
ExpectBoolean("if (undetectable) { true; } else { false; }", false);
ExpectBoolean("!undetectable", true);
ExpectObject("true&&undetectable", obj);
ExpectBoolean("false&&undetectable", false);
ExpectBoolean("true||undetectable", true);
ExpectObject("false||undetectable", obj);
ExpectObject("undetectable&&true", obj);
ExpectObject("undetectable&&false", obj);
ExpectBoolean("undetectable||true", true);
ExpectBoolean("undetectable||false", false);
ExpectBoolean("undetectable==null", true);
ExpectBoolean("null==undetectable", true);
ExpectBoolean("undetectable==undefined", true);
ExpectBoolean("undefined==undetectable", true);
ExpectBoolean("undetectable==undetectable", true);
ExpectBoolean("undetectable===null", false);
ExpectBoolean("null===undetectable", false);
ExpectBoolean("undetectable===undefined", false);
ExpectBoolean("undefined===undetectable", false);
ExpectBoolean("undetectable===undetectable", true);
}
THREADED_TEST(UndetectableString) {
v8::HandleScope scope;
LocalContext env;
Local<String> obj = String::NewUndetectable("foo");
env->Global()->Set(v8_str("undetectable"), obj);
ExpectString("undetectable", "foo");
ExpectString("typeof undetectable", "undefined");
ExpectString("typeof(undetectable)", "undefined");
ExpectBoolean("typeof undetectable == 'undefined'", true);
ExpectBoolean("typeof undetectable == 'string'", false);
ExpectBoolean("if (undetectable) { true; } else { false; }", false);
ExpectBoolean("!undetectable", true);
ExpectObject("true&&undetectable", obj);
ExpectBoolean("false&&undetectable", false);
ExpectBoolean("true||undetectable", true);
ExpectObject("false||undetectable", obj);
ExpectObject("undetectable&&true", obj);
ExpectObject("undetectable&&false", obj);
ExpectBoolean("undetectable||true", true);
ExpectBoolean("undetectable||false", false);
ExpectBoolean("undetectable==null", true);
ExpectBoolean("null==undetectable", true);
ExpectBoolean("undetectable==undefined", true);
ExpectBoolean("undefined==undetectable", true);
ExpectBoolean("undetectable==undetectable", true);
ExpectBoolean("undetectable===null", false);
ExpectBoolean("null===undetectable", false);
ExpectBoolean("undetectable===undefined", false);
ExpectBoolean("undefined===undetectable", false);
ExpectBoolean("undetectable===undetectable", true);
}
template <typename T> static void USE(T) { }
// This test is not intended to be run, just type checked.
static void PersistentHandles() {
USE(PersistentHandles);
Local<String> str = v8_str("foo");
v8::Persistent<String> p_str = v8::Persistent<String>::New(str);
USE(p_str);
Local<Script> scr = Script::Compile(v8_str(""));
v8::Persistent<Script> p_scr = v8::Persistent<Script>::New(scr);
USE(p_scr);
Local<ObjectTemplate> templ = ObjectTemplate::New();
v8::Persistent<ObjectTemplate> p_templ =
v8::Persistent<ObjectTemplate>::New(templ);
USE(p_templ);
}
static v8::Handle<Value> HandleLogDelegator(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
return v8::Undefined();
}
THREADED_TEST(GlobalObjectTemplate) {
v8::HandleScope handle_scope;
Local<ObjectTemplate> global_template = ObjectTemplate::New();
global_template->Set(v8_str("JSNI_Log"),
v8::FunctionTemplate::New(HandleLogDelegator));
v8::Persistent<Context> context = Context::New(0, global_template);
Context::Scope context_scope(context);
Script::Compile(v8_str("JSNI_Log('LOG')"))->Run();
context.Dispose();
}
static const char* kSimpleExtensionSource =
"function Foo() {"
" return 4;"
"}";
THREADED_TEST(SimpleExtensions) {
v8::HandleScope handle_scope;
v8::RegisterExtension(new Extension("simpletest", kSimpleExtensionSource));
const char* extension_names[] = { "simpletest" };
v8::ExtensionConfiguration extensions(1, extension_names);
v8::Handle<Context> context = Context::New(&extensions);
Context::Scope lock(context);
v8::Handle<Value> result = Script::Compile(v8_str("Foo()"))->Run();
CHECK_EQ(result, v8::Integer::New(4));
}
static const char* kEvalExtensionSource =
"function UseEval() {"
" var x = 42;"
" return eval('x');"
"}";
THREADED_TEST(UseEvalFromExtension) {
v8::HandleScope handle_scope;
v8::RegisterExtension(new Extension("evaltest", kEvalExtensionSource));
const char* extension_names[] = { "evaltest" };
v8::ExtensionConfiguration extensions(1, extension_names);
v8::Handle<Context> context = Context::New(&extensions);
Context::Scope lock(context);
v8::Handle<Value> result = Script::Compile(v8_str("UseEval()"))->Run();
CHECK_EQ(result, v8::Integer::New(42));
}
static const char* kWithExtensionSource =
"function UseWith() {"
" var x = 42;"
" with({x:87}) { return x; }"
"}";
THREADED_TEST(UseWithFromExtension) {
v8::HandleScope handle_scope;
v8::RegisterExtension(new Extension("withtest", kWithExtensionSource));
const char* extension_names[] = { "withtest" };
v8::ExtensionConfiguration extensions(1, extension_names);
v8::Handle<Context> context = Context::New(&extensions);
Context::Scope lock(context);
v8::Handle<Value> result = Script::Compile(v8_str("UseWith()"))->Run();
CHECK_EQ(result, v8::Integer::New(87));
}
THREADED_TEST(AutoExtensions) {
v8::HandleScope handle_scope;
Extension* extension = new Extension("autotest", kSimpleExtensionSource);
extension->set_auto_enable(true);
v8::RegisterExtension(extension);
v8::Handle<Context> context = Context::New();
Context::Scope lock(context);
v8::Handle<Value> result = Script::Compile(v8_str("Foo()"))->Run();
CHECK_EQ(result, v8::Integer::New(4));
}
static void CheckDependencies(const char* name, const char* expected) {
v8::HandleScope handle_scope;
v8::ExtensionConfiguration config(1, &name);
LocalContext context(&config);
CHECK_EQ(String::New(expected), context->Global()->Get(v8_str("loaded")));
}
/*
* Configuration:
*
* /-- B <--\
* A <- -- D <-- E
* \-- C <--/
*/
THREADED_TEST(ExtensionDependency) {
static const char* kEDeps[] = { "D" };
v8::RegisterExtension(new Extension("E", "this.loaded += 'E';", 1, kEDeps));
static const char* kDDeps[] = { "B", "C" };
v8::RegisterExtension(new Extension("D", "this.loaded += 'D';", 2, kDDeps));
static const char* kBCDeps[] = { "A" };
v8::RegisterExtension(new Extension("B", "this.loaded += 'B';", 1, kBCDeps));
v8::RegisterExtension(new Extension("C", "this.loaded += 'C';", 1, kBCDeps));
v8::RegisterExtension(new Extension("A", "this.loaded += 'A';"));
CheckDependencies("A", "undefinedA");
CheckDependencies("B", "undefinedAB");
CheckDependencies("C", "undefinedAC");
CheckDependencies("D", "undefinedABCD");
CheckDependencies("E", "undefinedABCDE");
v8::HandleScope handle_scope;
static const char* exts[2] = { "C", "E" };
v8::ExtensionConfiguration config(2, exts);
LocalContext context(&config);
CHECK_EQ(v8_str("undefinedACBDE"), context->Global()->Get(v8_str("loaded")));
}
static const char* kExtensionTestScript =
"native function A();"
"native function B();"
"native function C();"
"function Foo(i) {"
" if (i == 0) return A();"
" if (i == 1) return B();"
" if (i == 2) return C();"
"}";
static v8::Handle<Value> CallFun(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
return args.Data();
}
class FunctionExtension : public Extension {
public:
FunctionExtension() : Extension("functiontest", kExtensionTestScript) { }
virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
v8::Handle<String> name);
};
static int lookup_count = 0;
v8::Handle<v8::FunctionTemplate> FunctionExtension::GetNativeFunction(
v8::Handle<String> name) {
lookup_count++;
if (name->Equals(v8_str("A"))) {
return v8::FunctionTemplate::New(CallFun, v8::Integer::New(8));
} else if (name->Equals(v8_str("B"))) {
return v8::FunctionTemplate::New(CallFun, v8::Integer::New(7));
} else if (name->Equals(v8_str("C"))) {
return v8::FunctionTemplate::New(CallFun, v8::Integer::New(6));
} else {
return v8::Handle<v8::FunctionTemplate>();
}
}
THREADED_TEST(FunctionLookup) {
v8::RegisterExtension(new FunctionExtension());
v8::HandleScope handle_scope;
static const char* exts[1] = { "functiontest" };
v8::ExtensionConfiguration config(1, exts);
LocalContext context(&config);
CHECK_EQ(3, lookup_count);
CHECK_EQ(v8::Integer::New(8), Script::Compile(v8_str("Foo(0)"))->Run());
CHECK_EQ(v8::Integer::New(7), Script::Compile(v8_str("Foo(1)"))->Run());
CHECK_EQ(v8::Integer::New(6), Script::Compile(v8_str("Foo(2)"))->Run());
}
static const char* last_location;
static const char* last_message;
void StoringErrorCallback(const char* location, const char* message) {
if (last_location == NULL) {
last_location = location;
last_message = message;
}
}
// ErrorReporting creates a circular extensions configuration and
// tests that the fatal error handler gets called. This renders V8
// unusable and therefore this test cannot be run in parallel.
TEST(ErrorReporting) {
v8::V8::SetFatalErrorHandler(StoringErrorCallback);
static const char* aDeps[] = { "B" };
v8::RegisterExtension(new Extension("A", "", 1, aDeps));
static const char* bDeps[] = { "A" };
v8::RegisterExtension(new Extension("B", "", 1, bDeps));
last_location = NULL;
v8::ExtensionConfiguration config(1, bDeps);
v8::Handle<Context> context = Context::New(&config);
CHECK(context.IsEmpty());
CHECK_NE(last_location, NULL);
}
static const char* js_code_causing_huge_string_flattening =
"var str = 'X';"
"for (var i = 0; i < 29; i++) {"
" str = str + str;"
"}"
"str.match(/X/);";
void OOMCallback(const char* location, const char* message) {
exit(0);
}
TEST(RegexpOutOfMemory) {
// Execute a script that causes out of memory when flattening a string.
v8::HandleScope scope;
v8::V8::SetFatalErrorHandler(OOMCallback);
LocalContext context;
Local<Script> script =
Script::Compile(String::New(js_code_causing_huge_string_flattening));
last_location = NULL;
Local<Value> result = script->Run();
CHECK(false); // Should not return.
}
static void MissingScriptInfoMessageListener(v8::Handle<v8::Message> message,
v8::Handle<Value> data) {
CHECK_EQ(v8::Undefined(), data);
CHECK(message->GetScriptResourceName()->IsUndefined());
CHECK_EQ(v8::Undefined(), message->GetScriptResourceName());
message->GetLineNumber();
message->GetSourceLine();
}
THREADED_TEST(ErrorWithMissingScriptInfo) {
v8::HandleScope scope;
LocalContext context;
v8::V8::AddMessageListener(MissingScriptInfoMessageListener);
Script::Compile(v8_str("throw Error()"))->Run();
v8::V8::RemoveMessageListeners(MissingScriptInfoMessageListener);
}
int global_index = 0;
class Snorkel {
public:
Snorkel() { index_ = global_index++; }
int index_;
};
class Whammy {
public:
Whammy() {
cursor_ = 0;
}
~Whammy() {
script_.Dispose();
}
v8::Handle<Script> getScript() {
if (script_.IsEmpty())
script_ = v8::Persistent<Script>::New(v8_compile("({}).blammo"));
return Local<Script>(*script_);
}
public:
static const int kObjectCount = 256;
int cursor_;
v8::Persistent<v8::Object> objects_[kObjectCount];
v8::Persistent<Script> script_;
};
static void HandleWeakReference(v8::Persistent<v8::Value> obj, void* data) {
Snorkel* snorkel = reinterpret_cast<Snorkel*>(data);
delete snorkel;
obj.ClearWeak();
}
v8::Handle<Value> WhammyPropertyGetter(Local<String> name,
const AccessorInfo& info) {
Whammy* whammy =
static_cast<Whammy*>(v8::Handle<v8::External>::Cast(info.Data())->Value());
v8::Persistent<v8::Object> prev = whammy->objects_[whammy->cursor_];
v8::Handle<v8::Object> obj = v8::Object::New();
v8::Persistent<v8::Object> global = v8::Persistent<v8::Object>::New(obj);
if (!prev.IsEmpty()) {
prev->Set(v8_str("next"), obj);
prev.MakeWeak(new Snorkel(), &HandleWeakReference);
whammy->objects_[whammy->cursor_].Clear();
}
whammy->objects_[whammy->cursor_] = global;
whammy->cursor_ = (whammy->cursor_ + 1) % Whammy::kObjectCount;
return whammy->getScript()->Run();
}
THREADED_TEST(WeakReference) {
v8::HandleScope handle_scope;
v8::Handle<v8::ObjectTemplate> templ= v8::ObjectTemplate::New();
templ->SetNamedPropertyHandler(WhammyPropertyGetter,
0, 0, 0, 0,
v8::External::New(new Whammy()));
const char* extension_list[] = { "v8/gc" };
v8::ExtensionConfiguration extensions(1, extension_list);
v8::Persistent<Context> context = Context::New(&extensions);
Context::Scope context_scope(context);
v8::Handle<v8::Object> interceptor = templ->NewInstance();
context->Global()->Set(v8_str("whammy"), interceptor);
const char* code =
"var last;"
"for (var i = 0; i < 10000; i++) {"
" var obj = whammy.length;"
" if (last) last.next = obj;"
" last = obj;"
"}"
"gc();"
"4";
v8::Handle<Value> result = CompileRun(code);
CHECK_EQ(4.0, result->NumberValue());
context.Dispose();
}
v8::Handle<Function> args_fun;
static v8::Handle<Value> ArgumentsTestCallback(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
CHECK_EQ(args_fun, args.Callee());
CHECK_EQ(3, args.Length());
CHECK_EQ(v8::Integer::New(1), args[0]);
CHECK_EQ(v8::Integer::New(2), args[1]);
CHECK_EQ(v8::Integer::New(3), args[2]);
CHECK_EQ(v8::Undefined(), args[3]);
v8::HandleScope scope;
i::Heap::CollectAllGarbage();
return v8::Undefined();
}
THREADED_TEST(Arguments) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> global = ObjectTemplate::New();
global->Set(v8_str("f"), v8::FunctionTemplate::New(ArgumentsTestCallback));
LocalContext context(NULL, global);
args_fun = v8::Handle<Function>::Cast(context->Global()->Get(v8_str("f")));
v8_compile("f(1, 2, 3)")->Run();
}
static int x_register = 0;
static v8::Handle<v8::Object> x_receiver;
static v8::Handle<v8::Object> x_holder;
static v8::Handle<Value> XGetter(Local<String> name, const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK_EQ(x_receiver, info.This());
CHECK_EQ(x_holder, info.Holder());
return v8_num(x_register);
}
static void XSetter(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
CHECK_EQ(x_holder, info.This());
CHECK_EQ(x_holder, info.Holder());
x_register = value->Int32Value();
}
THREADED_TEST(AccessorIC) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New();
obj->SetAccessor(v8_str("x"), XGetter, XSetter);
LocalContext context;
x_holder = obj->NewInstance();
context->Global()->Set(v8_str("holder"), x_holder);
x_receiver = v8::Object::New();
context->Global()->Set(v8_str("obj"), x_receiver);
v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(CompileRun(
"obj.__proto__ = holder;"
"var result = [];"
"for (var i = 0; i < 10; i++) {"
" holder.x = i;"
" result.push(obj.x);"
"}"
"result"));
CHECK_EQ(10, array->Length());
for (int i = 0; i < 10; i++) {
v8::Handle<Value> entry = array->Get(v8::Integer::New(i));
CHECK_EQ(v8::Integer::New(i), entry);
}
}
static v8::Handle<Value> NoBlockGetterX(Local<String> name,
const AccessorInfo&) {
return v8::Handle<Value>();
}
static v8::Handle<Value> NoBlockGetterI(uint32_t index,
const AccessorInfo&) {
return v8::Handle<Value>();
}
static v8::Handle<v8::Boolean> PDeleter(Local<String> name,
const AccessorInfo&) {
if (!name->Equals(v8_str("foo"))) {
return v8::Handle<v8::Boolean>(); // not intercepted
}
return v8::False(); // intercepted, and don't delete the property
}
static v8::Handle<v8::Boolean> IDeleter(uint32_t index, const AccessorInfo&) {
if (index != 2) {
return v8::Handle<v8::Boolean>(); // not intercepted
}
return v8::False(); // intercepted, and don't delete the property
}
THREADED_TEST(Deleter) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New();
obj->SetNamedPropertyHandler(NoBlockGetterX, NULL, NULL, PDeleter, NULL);
obj->SetIndexedPropertyHandler(NoBlockGetterI, NULL, NULL, IDeleter, NULL);
LocalContext context;
context->Global()->Set(v8_str("k"), obj->NewInstance());
CompileRun(
"k.foo = 'foo';"
"k.bar = 'bar';"
"k[2] = 2;"
"k[4] = 4;");
CHECK(v8_compile("delete k.foo")->Run()->IsFalse());
CHECK(v8_compile("delete k.bar")->Run()->IsTrue());
CHECK_EQ(v8_compile("k.foo")->Run(), v8_str("foo"));
CHECK(v8_compile("k.bar")->Run()->IsUndefined());
CHECK(v8_compile("delete k[2]")->Run()->IsFalse());
CHECK(v8_compile("delete k[4]")->Run()->IsTrue());
CHECK_EQ(v8_compile("k[2]")->Run(), v8_num(2));
CHECK(v8_compile("k[4]")->Run()->IsUndefined());
}
static v8::Handle<Value> GetK(Local<String> name, const AccessorInfo&) {
ApiTestFuzzer::Fuzz();
if (name->Equals(v8_str("foo")) ||
name->Equals(v8_str("bar")) ||
name->Equals(v8_str("baz"))) {
return v8::Undefined();
}
return v8::Handle<Value>();
}
static v8::Handle<Value> IndexedGetK(uint32_t index, const AccessorInfo&) {
ApiTestFuzzer::Fuzz();
if (index == 0 || index == 1) return v8::Undefined();
return v8::Handle<Value>();
}
static v8::Handle<v8::Array> NamedEnum(const AccessorInfo&) {
ApiTestFuzzer::Fuzz();
v8::Handle<v8::Array> result = v8::Array::New(3);
result->Set(v8::Integer::New(0), v8_str("foo"));
result->Set(v8::Integer::New(1), v8_str("bar"));
result->Set(v8::Integer::New(2), v8_str("baz"));
return result;
}
static v8::Handle<v8::Array> IndexedEnum(const AccessorInfo&) {
ApiTestFuzzer::Fuzz();
v8::Handle<v8::Array> result = v8::Array::New(2);
result->Set(v8::Integer::New(0), v8_str("0"));
result->Set(v8::Integer::New(1), v8_str("1"));
return result;
}
THREADED_TEST(Enumerators) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New();
obj->SetNamedPropertyHandler(GetK, NULL, NULL, NULL, NamedEnum);
obj->SetIndexedPropertyHandler(IndexedGetK, NULL, NULL, NULL, IndexedEnum);
LocalContext context;
context->Global()->Set(v8_str("k"), obj->NewInstance());
v8::Handle<v8::Array> result = v8::Handle<v8::Array>::Cast(CompileRun(
"k[10] = 0;"
"k.a = 0;"
"k[5] = 0;"
"k.b = 0;"
"k[4294967295] = 0;"
"k.c = 0;"
"k[4294967296] = 0;"
"k.d = 0;"
"k[140000] = 0;"
"k.e = 0;"
"k[30000000000] = 0;"
"k.f = 0;"
"var result = [];"
"for (var prop in k) {"
" result.push(prop);"
"}"
"result"));
// Check that we get all the property names returned including the
// ones from the enumerators in the right order: indexed properties
// in numerical order, indexed interceptor properties, named
// properties in insertion order, named interceptor properties.
// This order is not mandated by the spec, so this test is just
// documenting our behavior.
CHECK_EQ(17, result->Length());
// Indexed properties in numerical order.
CHECK_EQ(v8_str("5"), result->Get(v8::Integer::New(0)));
CHECK_EQ(v8_str("10"), result->Get(v8::Integer::New(1)));
CHECK_EQ(v8_str("140000"), result->Get(v8::Integer::New(2)));
CHECK_EQ(v8_str("4294967295"), result->Get(v8::Integer::New(3)));
// Indexed interceptor properties in the order they are returned
// from the enumerator interceptor.
CHECK_EQ(v8_str("0"), result->Get(v8::Integer::New(4)));
CHECK_EQ(v8_str("1"), result->Get(v8::Integer::New(5)));
// Named properties in insertion order.
CHECK_EQ(v8_str("a"), result->Get(v8::Integer::New(6)));
CHECK_EQ(v8_str("b"), result->Get(v8::Integer::New(7)));
CHECK_EQ(v8_str("c"), result->Get(v8::Integer::New(8)));
CHECK_EQ(v8_str("4294967296"), result->Get(v8::Integer::New(9)));
CHECK_EQ(v8_str("d"), result->Get(v8::Integer::New(10)));
CHECK_EQ(v8_str("e"), result->Get(v8::Integer::New(11)));
CHECK_EQ(v8_str("30000000000"), result->Get(v8::Integer::New(12)));
CHECK_EQ(v8_str("f"), result->Get(v8::Integer::New(13)));
// Named interceptor properties.
CHECK_EQ(v8_str("foo"), result->Get(v8::Integer::New(14)));
CHECK_EQ(v8_str("bar"), result->Get(v8::Integer::New(15)));
CHECK_EQ(v8_str("baz"), result->Get(v8::Integer::New(16)));
}
int p_getter_count;
int p_getter_count2;
static v8::Handle<Value> PGetter(Local<String> name, const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
p_getter_count++;
v8::Handle<v8::Object> global = Context::GetCurrent()->Global();
CHECK_EQ(info.Holder(), global->Get(v8_str("o1")));
if (name->Equals(v8_str("p1"))) {
CHECK_EQ(info.This(), global->Get(v8_str("o1")));
} else if (name->Equals(v8_str("p2"))) {
CHECK_EQ(info.This(), global->Get(v8_str("o2")));
} else if (name->Equals(v8_str("p3"))) {
CHECK_EQ(info.This(), global->Get(v8_str("o3")));
} else if (name->Equals(v8_str("p4"))) {
CHECK_EQ(info.This(), global->Get(v8_str("o4")));
}
return v8::Undefined();
}
static void RunHolderTest(v8::Handle<v8::ObjectTemplate> obj) {
ApiTestFuzzer::Fuzz();
LocalContext context;
context->Global()->Set(v8_str("o1"), obj->NewInstance());
CompileRun(
"o1.__proto__ = { };"
"var o2 = { __proto__: o1 };"
"var o3 = { __proto__: o2 };"
"var o4 = { __proto__: o3 };"
"for (var i = 0; i < 10; i++) o4.p4;"
"for (var i = 0; i < 10; i++) o3.p3;"
"for (var i = 0; i < 10; i++) o2.p2;"
"for (var i = 0; i < 10; i++) o1.p1;");
}
static v8::Handle<Value> PGetter2(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
p_getter_count2++;
v8::Handle<v8::Object> global = Context::GetCurrent()->Global();
CHECK_EQ(info.Holder(), global->Get(v8_str("o1")));
if (name->Equals(v8_str("p1"))) {
CHECK_EQ(info.This(), global->Get(v8_str("o1")));
} else if (name->Equals(v8_str("p2"))) {
CHECK_EQ(info.This(), global->Get(v8_str("o2")));
} else if (name->Equals(v8_str("p3"))) {
CHECK_EQ(info.This(), global->Get(v8_str("o3")));
} else if (name->Equals(v8_str("p4"))) {
CHECK_EQ(info.This(), global->Get(v8_str("o4")));
}
return v8::Undefined();
}
THREADED_TEST(GetterHolders) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New();
obj->SetAccessor(v8_str("p1"), PGetter);
obj->SetAccessor(v8_str("p2"), PGetter);
obj->SetAccessor(v8_str("p3"), PGetter);
obj->SetAccessor(v8_str("p4"), PGetter);
p_getter_count = 0;
RunHolderTest(obj);
CHECK_EQ(40, p_getter_count);
}
THREADED_TEST(PreInterceptorHolders) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New();
obj->SetNamedPropertyHandler(PGetter2);
p_getter_count2 = 0;
RunHolderTest(obj);
CHECK_EQ(40, p_getter_count2);
}
THREADED_TEST(ObjectInstantiation) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessor(v8_str("t"), PGetter2);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
for (int i = 0; i < 100; i++) {
v8::HandleScope inner_scope;
v8::Handle<v8::Object> obj = templ->NewInstance();
CHECK_NE(obj, context->Global()->Get(v8_str("o")));
context->Global()->Set(v8_str("o2"), obj);
v8::Handle<Value> value =
Script::Compile(v8_str("o.__proto__ === o2.__proto__"))->Run();
CHECK_EQ(v8::True(), value);
context->Global()->Set(v8_str("o"), obj);
}
}
THREADED_TEST(StringWrite) {
v8::HandleScope scope;
v8::Handle<String> str = v8_str("abcde");
char buf[100];
int len;
memset(buf, 0x1, sizeof(buf));
len = str->WriteAscii(buf);
CHECK_EQ(len, 5);
CHECK_EQ(strncmp("abcde\0", buf, 6), 0);
memset(buf, 0x1, sizeof(buf));
len = str->WriteAscii(buf, 0, 4);
CHECK_EQ(len, 4);
CHECK_EQ(strncmp("abcd\1", buf, 5), 0);
memset(buf, 0x1, sizeof(buf));
len = str->WriteAscii(buf, 0, 5);
CHECK_EQ(len, 5);
CHECK_EQ(strncmp("abcde\1", buf, 6), 0);
memset(buf, 0x1, sizeof(buf));
len = str->WriteAscii(buf, 0, 6);
CHECK_EQ(len, 5);
CHECK_EQ(strncmp("abcde\0", buf, 6), 0);
memset(buf, 0x1, sizeof(buf));
len = str->WriteAscii(buf, 4, -1);
CHECK_EQ(len, 1);
CHECK_EQ(strncmp("e\0", buf, 2), 0);
memset(buf, 0x1, sizeof(buf));
len = str->WriteAscii(buf, 4, 6);
CHECK_EQ(len, 1);
CHECK_EQ(strncmp("e\0", buf, 2), 0);
memset(buf, 0x1, sizeof(buf));
len = str->WriteAscii(buf, 4, 1);
CHECK_EQ(len, 1);
CHECK_EQ(strncmp("e\1", buf, 2), 0);
}
THREADED_TEST(ToArrayIndex) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<String> str = v8_str("42");
v8::Handle<v8::Uint32> index = str->ToArrayIndex();
CHECK(!index.IsEmpty());
CHECK_EQ(42.0, index->Uint32Value());
str = v8_str("42asdf");
index = str->ToArrayIndex();
CHECK(index.IsEmpty());
str = v8_str("-42");
index = str->ToArrayIndex();
CHECK(index.IsEmpty());
str = v8_str("4294967295");
index = str->ToArrayIndex();
CHECK(!index.IsEmpty());
CHECK_EQ(4294967295.0, index->Uint32Value());
v8::Handle<v8::Number> num = v8::Number::New(1);
index = num->ToArrayIndex();
CHECK(!index.IsEmpty());
CHECK_EQ(1.0, index->Uint32Value());
num = v8::Number::New(-1);
index = num->ToArrayIndex();
CHECK(index.IsEmpty());
v8::Handle<v8::Object> obj = v8::Object::New();
index = obj->ToArrayIndex();
CHECK(index.IsEmpty());
}
THREADED_TEST(ErrorConstruction) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<String> foo = v8_str("foo");
v8::Handle<String> message = v8_str("message");
v8::Handle<Value> range_error = v8::Exception::RangeError(foo);
CHECK(range_error->IsObject());
v8::Handle<v8::Object> range_obj(v8::Handle<v8::Object>::Cast(range_error));
CHECK(v8::Handle<v8::Object>::Cast(range_error)->Get(message)->Equals(foo));
v8::Handle<Value> reference_error = v8::Exception::ReferenceError(foo);
CHECK(reference_error->IsObject());
CHECK(
v8::Handle<v8::Object>::Cast(reference_error)->Get(message)->Equals(foo));
v8::Handle<Value> syntax_error = v8::Exception::SyntaxError(foo);
CHECK(syntax_error->IsObject());
CHECK(v8::Handle<v8::Object>::Cast(syntax_error)->Get(message)->Equals(foo));
v8::Handle<Value> type_error = v8::Exception::TypeError(foo);
CHECK(type_error->IsObject());
CHECK(v8::Handle<v8::Object>::Cast(type_error)->Get(message)->Equals(foo));
v8::Handle<Value> error = v8::Exception::Error(foo);
CHECK(error->IsObject());
CHECK(v8::Handle<v8::Object>::Cast(error)->Get(message)->Equals(foo));
}
static v8::Handle<Value> YGetter(Local<String> name, const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
return v8_num(10);
}
static void YSetter(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
if (info.This()->Has(name)) {
info.This()->Delete(name);
}
info.This()->Set(name, value);
}
THREADED_TEST(DeleteAccessor) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New();
obj->SetAccessor(v8_str("y"), YGetter, YSetter);
LocalContext context;
v8::Handle<v8::Object> holder = obj->NewInstance();
context->Global()->Set(v8_str("holder"), holder);
v8::Handle<Value> result = CompileRun(
"holder.y = 11; holder.y = 12; holder.y");
CHECK_EQ(12, result->Uint32Value());
}
THREADED_TEST(TypeSwitch) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> templ1 = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> templ2 = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> templ3 = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> templs[3] = { templ1, templ2, templ3 };
v8::Handle<v8::TypeSwitch> type_switch = v8::TypeSwitch::New(3, templs);
LocalContext context;
v8::Handle<v8::Object> obj0 = v8::Object::New();
v8::Handle<v8::Object> obj1 = templ1->GetFunction()->NewInstance();
v8::Handle<v8::Object> obj2 = templ2->GetFunction()->NewInstance();
v8::Handle<v8::Object> obj3 = templ3->GetFunction()->NewInstance();
for (int i = 0; i < 10; i++) {
CHECK_EQ(0, type_switch->match(obj0));
CHECK_EQ(1, type_switch->match(obj1));
CHECK_EQ(2, type_switch->match(obj2));
CHECK_EQ(3, type_switch->match(obj3));
CHECK_EQ(3, type_switch->match(obj3));
CHECK_EQ(2, type_switch->match(obj2));
CHECK_EQ(1, type_switch->match(obj1));
CHECK_EQ(0, type_switch->match(obj0));
}
}
// For use within the TestSecurityHandler() test.
static bool g_security_callback_result = false;
static bool NamedSecurityTestCallback(Local<v8::Object> global,
Local<Value> name,
v8::AccessType type,
Local<Value> data) {
// Always allow read access.
if (type == v8::ACCESS_GET)
return true;
// Sometimes allow other access.
return g_security_callback_result;
}
static bool IndexedSecurityTestCallback(Local<v8::Object> global,
uint32_t key,
v8::AccessType type,
Local<Value> data) {
// Always allow read access.
if (type == v8::ACCESS_GET)
return true;
// Sometimes allow other access.
return g_security_callback_result;
}
static int trouble_nesting = 0;
static v8::Handle<Value> TroubleCallback(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
trouble_nesting++;
// Call a JS function that throws an uncaught exception.
Local<v8::Object> arg_this = Context::GetCurrent()->Global();
Local<Value> trouble_callee = (trouble_nesting == 3) ?
arg_this->Get(v8_str("trouble_callee")) :
arg_this->Get(v8_str("trouble_caller"));
CHECK(trouble_callee->IsFunction());
return Function::Cast(*trouble_callee)->Call(arg_this, 0, NULL);
}
static int report_count = 0;
static void ApiUncaughtExceptionTestListener(v8::Handle<v8::Message>,
v8::Handle<Value>) {
report_count++;
}
// Counts uncaught exceptions, but other tests running in parallel
// also have uncaught exceptions.
TEST(ApiUncaughtException) {
report_count = 0;
v8::HandleScope scope;
LocalContext env;
v8::V8::AddMessageListener(ApiUncaughtExceptionTestListener);
Local<v8::FunctionTemplate> fun = v8::FunctionTemplate::New(TroubleCallback);
v8::Local<v8::Object> global = env->Global();
global->Set(v8_str("trouble"), fun->GetFunction());
Script::Compile(v8_str("function trouble_callee() {"
" var x = null;"
" return x.foo;"
"};"
"function trouble_caller() {"
" trouble();"
"};"))->Run();
Local<Value> trouble = global->Get(v8_str("trouble"));
CHECK(trouble->IsFunction());
Local<Value> trouble_callee = global->Get(v8_str("trouble_callee"));
CHECK(trouble_callee->IsFunction());
Local<Value> trouble_caller = global->Get(v8_str("trouble_caller"));
CHECK(trouble_caller->IsFunction());
Function::Cast(*trouble_caller)->Call(global, 0, NULL);
CHECK_EQ(1, report_count);
v8::V8::RemoveMessageListeners(ApiUncaughtExceptionTestListener);
}
TEST(CompilationErrorUsingTryCatchHandler) {
v8::HandleScope scope;
LocalContext env;
v8::TryCatch try_catch;
Script::Compile(v8_str("This doesn't &*&@#$&*^ compile."));
CHECK_NE(NULL, *try_catch.Exception());
CHECK(try_catch.HasCaught());
}
TEST(TryCatchFinallyUsingTryCatchHandler) {
v8::HandleScope scope;
LocalContext env;
v8::TryCatch try_catch;
Script::Compile(v8_str("try { throw ''; } catch (e) {}"))->Run();
CHECK(!try_catch.HasCaught());
Script::Compile(v8_str("try { throw ''; } finally {}"))->Run();
CHECK(try_catch.HasCaught());
try_catch.Reset();
Script::Compile(v8_str("(function() {"
"try { throw ''; } finally { return; }"
"})()"))->Run();
CHECK(!try_catch.HasCaught());
Script::Compile(v8_str("(function()"
" { try { throw ''; } finally { throw 0; }"
"})()"))->Run();
CHECK(try_catch.HasCaught());
}
// SecurityHandler can't be run twice
TEST(SecurityHandler) {
v8::HandleScope scope0;
v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
global_template->SetAccessCheckCallbacks(NamedSecurityTestCallback,
IndexedSecurityTestCallback);
// Create an environment
v8::Persistent<Context> context0 =
Context::New(NULL, global_template);
context0->Enter();
v8::Handle<v8::Object> global0 = context0->Global();
v8::Handle<Script> script0 = v8_compile("foo = 111");
script0->Run();
global0->Set(v8_str("0"), v8_num(999));
v8::Handle<Value> foo0 = global0->Get(v8_str("foo"));
CHECK_EQ(111, foo0->Int32Value());
v8::Handle<Value> z0 = global0->Get(v8_str("0"));
CHECK_EQ(999, z0->Int32Value());
// Create another environment, should fail security checks.
v8::HandleScope scope1;
v8::Persistent<Context> context1 =
Context::New(NULL, global_template);
context1->Enter();
v8::Handle<v8::Object> global1 = context1->Global();
global1->Set(v8_str("othercontext"), global0);
// This set will fail the security check.
v8::Handle<Script> script1 =
v8_compile("othercontext.foo = 222; othercontext[0] = 888;");
script1->Run();
// This read will pass the security check.
v8::Handle<Value> foo1 = global0->Get(v8_str("foo"));
CHECK_EQ(111, foo1->Int32Value());
// This read will pass the security check.
v8::Handle<Value> z1 = global0->Get(v8_str("0"));
CHECK_EQ(999, z1->Int32Value());
// Create another environment, should pass security checks.
{ g_security_callback_result = true; // allow security handler to pass.
v8::HandleScope scope2;
LocalContext context2;
v8::Handle<v8::Object> global2 = context2->Global();
global2->Set(v8_str("othercontext"), global0);
v8::Handle<Script> script2 =
v8_compile("othercontext.foo = 333; othercontext[0] = 888;");
script2->Run();
v8::Handle<Value> foo2 = global0->Get(v8_str("foo"));
CHECK_EQ(333, foo2->Int32Value());
v8::Handle<Value> z2 = global0->Get(v8_str("0"));
CHECK_EQ(888, z2->Int32Value());
}
context1->Exit();
context1.Dispose();
context0->Exit();
context0.Dispose();
}
THREADED_TEST(SecurityChecks) {
v8::HandleScope handle_scope;
LocalContext env1;
v8::Persistent<Context> env2 = Context::New();
Local<Value> foo = v8_str("foo");
Local<Value> bar = v8_str("bar");
// Set to the same domain.
env1->SetSecurityToken(foo);
// Create a function in env1.
Script::Compile(v8_str("spy=function(){return spy;}"))->Run();
Local<Value> spy = env1->Global()->Get(v8_str("spy"));
CHECK(spy->IsFunction());
// Create another function accessing global objects.
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
Script::Compile(v8_str("spy2=function(){return new this.Array();}"))->Run();
Local<Value> spy2 = env1->Global()->Get(v8_str("spy2"));
CHECK(spy2->IsFunction());
// Switch to env2 in the same domain and invoke spy on env2.
{
env2->SetSecurityToken(foo);
// Enter env2
Context::Scope scope_env2(env2);
Local<Value> result = Function::Cast(*spy)->Call(env2->Global(), 0, NULL);
CHECK(result->IsFunction());
}
{
env2->SetSecurityToken(bar);
Context::Scope scope_env2(env2);
// Call cross_domain_call, it should throw an exception
v8::TryCatch try_catch;
Function::Cast(*spy2)->Call(env2->Global(), 0, NULL);
CHECK(try_catch.HasCaught());
}
env2.Dispose();
}
// Regression test case for issue 1183439.
THREADED_TEST(SecurityChecksForPrototypeChain) {
v8::HandleScope scope;
LocalContext current;
v8::Persistent<Context> other = Context::New();
// Change context to be able to get to the Object function in the
// other context without hitting the security checks.
v8::Local<Value> other_object;
{ Context::Scope scope(other);
other_object = other->Global()->Get(v8_str("Object"));
other->Global()->Set(v8_num(42), v8_num(87));
}
current->Global()->Set(v8_str("other"), other->Global());
CHECK(v8_compile("other")->Run()->Equals(other->Global()));
// Make sure the security check fails here and we get an undefined
// result instead of getting the Object function. Repeat in a loop
// to make sure to exercise the IC code.
v8::Local<Script> access_other0 = v8_compile("other.Object");
v8::Local<Script> access_other1 = v8_compile("other[42]");
for (int i = 0; i < 5; i++) {
CHECK(!access_other0->Run()->Equals(other_object));
CHECK(access_other0->Run()->IsUndefined());
CHECK(!access_other1->Run()->Equals(v8_num(87)));
CHECK(access_other1->Run()->IsUndefined());
}
// Create an object that has 'other' in its prototype chain and make
// sure we cannot access the Object function indirectly through
// that. Repeat in a loop to make sure to exercise the IC code.
v8_compile("function F() { };"
"F.prototype = other;"
"var f = new F();")->Run();
v8::Local<Script> access_f0 = v8_compile("f.Object");
v8::Local<Script> access_f1 = v8_compile("f[42]");
for (int j = 0; j < 5; j++) {
CHECK(!access_f0->Run()->Equals(other_object));
CHECK(access_f0->Run()->IsUndefined());
CHECK(!access_f1->Run()->Equals(v8_num(87)));
CHECK(access_f1->Run()->IsUndefined());
}
// Now it gets hairy: Set the prototype for the other global object
// to be the current global object. The prototype chain for 'f' now
// goes through 'other' but ends up in the current global object.
{ Context::Scope scope(other);
other->Global()->Set(v8_str("__proto__"), current->Global());
}
// Set a named and an index property on the current global
// object. To force the lookup to go through the other global object,
// the properties must not exist in the other global object.
current->Global()->Set(v8_str("foo"), v8_num(100));
current->Global()->Set(v8_num(99), v8_num(101));
// Try to read the properties from f and make sure that the access
// gets stopped by the security checks on the other global object.
Local<Script> access_f2 = v8_compile("f.foo");
Local<Script> access_f3 = v8_compile("f[99]");
for (int k = 0; k < 5; k++) {
CHECK(!access_f2->Run()->Equals(v8_num(100)));
CHECK(access_f2->Run()->IsUndefined());
CHECK(!access_f3->Run()->Equals(v8_num(101)));
CHECK(access_f3->Run()->IsUndefined());
}
other.Dispose();
}
THREADED_TEST(CrossDomainDelete) {
v8::HandleScope handle_scope;
LocalContext env1;
v8::Persistent<Context> env2 = Context::New();
Local<Value> foo = v8_str("foo");
Local<Value> bar = v8_str("bar");
// Set to the same domain.
env1->SetSecurityToken(foo);
env2->SetSecurityToken(foo);
env1->Global()->Set(v8_str("prop"), v8_num(3));
env2->Global()->Set(v8_str("env1"), env1->Global());
// Change env2 to a different domain and delete env1.prop.
env2->SetSecurityToken(bar);
{
Context::Scope scope_env2(env2);
Local<Value> result =
Script::Compile(v8_str("delete env1.prop"))->Run();
CHECK(result->IsFalse());
}
// Check that env1.prop still exists.
Local<Value> v = env1->Global()->Get(v8_str("prop"));
CHECK(v->IsNumber());
CHECK_EQ(3, v->Int32Value());
env2.Dispose();
}
THREADED_TEST(CrossDomainIsPropertyEnumerable) {
v8::HandleScope handle_scope;
LocalContext env1;
v8::Persistent<Context> env2 = Context::New();
Local<Value> foo = v8_str("foo");
Local<Value> bar = v8_str("bar");
// Set to the same domain.
env1->SetSecurityToken(foo);
env2->SetSecurityToken(foo);
env1->Global()->Set(v8_str("prop"), v8_num(3));
env2->Global()->Set(v8_str("env1"), env1->Global());
// env1.prop is enumerable in env2.
Local<String> test = v8_str("propertyIsEnumerable.call(env1, 'prop')");
{
Context::Scope scope_env2(env2);
Local<Value> result = Script::Compile(test)->Run();
CHECK(result->IsTrue());
}
// Change env2 to a different domain and test again.
env2->SetSecurityToken(bar);
{
Context::Scope scope_env2(env2);
Local<Value> result = Script::Compile(test)->Run();
CHECK(result->IsFalse());
}
env2.Dispose();
}
THREADED_TEST(CrossDomainForIn) {
v8::HandleScope handle_scope;
LocalContext env1;
v8::Persistent<Context> env2 = Context::New();
Local<Value> foo = v8_str("foo");
Local<Value> bar = v8_str("bar");
// Set to the same domain.
env1->SetSecurityToken(foo);
env2->SetSecurityToken(foo);
env1->Global()->Set(v8_str("prop"), v8_num(3));
env2->Global()->Set(v8_str("env1"), env1->Global());
// Change env2 to a different domain and set env1's global object
// as the __proto__ of an object in env2 and enumerate properties
// in for-in. It shouldn't enumerate properties on env1's global
// object.
env2->SetSecurityToken(bar);
{
Context::Scope scope_env2(env2);
Local<Value> result =
CompileRun("(function(){var obj = {'__proto__':env1};"
"for (var p in obj)"
" if (p == 'prop') return false;"
"return true;})()");
CHECK(result->IsTrue());
}
env2.Dispose();
}
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
TEST(ContextDetachGlobal) {
v8::HandleScope handle_scope;
LocalContext env1;
v8::Persistent<Context> env2 = Context::New();
Local<v8::Object> global1 = env1->Global();
Local<Value> foo = v8_str("foo");
// Set to the same domain.
env1->SetSecurityToken(foo);
env2->SetSecurityToken(foo);
// Enter env2
env2->Enter();
// Create a function in env1
Local<v8::Object> global2 = env2->Global();
global2->Set(v8_str("prop"), v8::Integer::New(1));
CompileRun("function getProp() {return prop;}");
env1->Global()->Set(v8_str("getProp"),
global2->Get(v8_str("getProp")));
// Detach env1's global, and reuse the global object of env1
env2->Exit();
env2->DetachGlobal();
// env2 has a new global object.
CHECK(!env2->Global()->Equals(global2));
v8::Persistent<Context> env3 =
Context::New(0, v8::Handle<v8::ObjectTemplate>(), global2);
env3->SetSecurityToken(v8_str("bar"));
env3->Enter();
Local<v8::Object> global3 = env3->Global();
CHECK_EQ(global2, global3);
CHECK(global3->Get(v8_str("prop"))->IsUndefined());
CHECK(global3->Get(v8_str("getProp"))->IsUndefined());
global3->Set(v8_str("prop"), v8::Integer::New(-1));
global3->Set(v8_str("prop2"), v8::Integer::New(2));
env3->Exit();
// Call getProp in env1, and it should return the value 1
{
Local<Value> get_prop = global1->Get(v8_str("getProp"));
CHECK(get_prop->IsFunction());
v8::TryCatch try_catch;
Local<Value> r = Function::Cast(*get_prop)->Call(global1, 0, NULL);
CHECK(!try_catch.HasCaught());
CHECK_EQ(1, r->Int32Value());
}
// Check that env3 is not accessible from env1
{
Local<Value> r = global3->Get(v8_str("prop2"));
CHECK(r->IsUndefined());
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
}
env2.Dispose();
env3.Dispose();
}
static bool NamedAccessBlocker(Local<v8::Object> global,
Local<Value> name,
v8::AccessType type,
Local<Value> data) {
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
return Context::GetCurrent()->Global()->Equals(global);
}
static bool IndexedAccessBlocker(Local<v8::Object> global,
uint32_t key,
v8::AccessType type,
Local<Value> data) {
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
return Context::GetCurrent()->Global()->Equals(global);
}
static int g_echo_value = -1;
static v8::Handle<Value> EchoGetter(Local<String> name,
const AccessorInfo& info) {
return v8_num(g_echo_value);
}
static void EchoSetter(Local<String> name,
Local<Value> value,
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
const AccessorInfo&) {
if (value->IsNumber())
g_echo_value = value->Int32Value();
}
static v8::Handle<Value> UnreachableGetter(Local<String> name,
const AccessorInfo& info) {
CHECK(false); // This function should not be called..
return v8::Undefined();
}
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
static void UnreachableSetter(Local<String>, Local<Value>,
const AccessorInfo&) {
CHECK(false); // This function should nto be called.
}
THREADED_TEST(AccessControl) {
v8::HandleScope handle_scope;
v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
global_template->SetAccessCheckCallbacks(NamedAccessBlocker,
IndexedAccessBlocker);
// Add an accessor accessible by cross-domain JS code.
global_template->SetAccessor(
v8_str("accessible_prop"),
EchoGetter, EchoSetter,
v8::Handle<Value>(),
v8::AccessControl(v8::ALL_CAN_READ | v8::ALL_CAN_WRITE));
// Add an accessor that is not accessible by cross-domain JS code.
global_template->SetAccessor(v8_str("blocked_prop"),
UnreachableGetter, UnreachableSetter,
v8::Handle<Value>(),
v8::DEFAULT);
// Create an environment
v8::Persistent<Context> context0 = Context::New(NULL, global_template);
context0->Enter();
v8::Handle<v8::Object> global0 = context0->Global();
v8::HandleScope scope1;
v8::Persistent<Context> context1 = Context::New();
context1->Enter();
v8::Handle<v8::Object> global1 = context1->Global();
global1->Set(v8_str("other"), global0);
v8::Handle<Value> value;
// Access blocked property
value = v8_compile("other.blocked_prop = 1")->Run();
value = v8_compile("other.blocked_prop")->Run();
CHECK(value->IsUndefined());
value = v8_compile("propertyIsEnumerable.call(other, 'blocked_prop')")->Run();
CHECK(value->IsFalse());
// Access accessible property
value = v8_compile("other.accessible_prop = 3")->Run();
CHECK(value->IsNumber());
CHECK_EQ(3, value->Int32Value());
value = v8_compile("other.accessible_prop")->Run();
CHECK(value->IsNumber());
CHECK_EQ(3, value->Int32Value());
value =
v8_compile("propertyIsEnumerable.call(other, 'accessible_prop')")->Run();
CHECK(value->IsTrue());
// Enumeration doesn't enumerate accessors from inaccessible objects in
// the prototype chain even if the accessors are in themselves accessible.
Local<Value> result =
CompileRun("(function(){var obj = {'__proto__':other};"
"for (var p in obj)"
" if (p == 'accessible_prop' || p == 'blocked_prop') {"
" return false;"
" }"
"return true;})()");
CHECK(result->IsTrue());
context1->Exit();
context0->Exit();
context1.Dispose();
context0.Dispose();
}
static v8::Handle<Value> ConstTenGetter(Local<String> name,
const AccessorInfo& info) {
return v8_num(10);
}
THREADED_TEST(CrossDomainAccessors) {
v8::HandleScope handle_scope;
v8::Handle<v8::FunctionTemplate> func_template = v8::FunctionTemplate::New();
v8::Handle<v8::ObjectTemplate> global_template =
func_template->InstanceTemplate();
v8::Handle<v8::ObjectTemplate> proto_template =
func_template->PrototypeTemplate();
// Add an accessor to proto that's accessible by cross-domain JS code.
proto_template->SetAccessor(v8_str("accessible"),
ConstTenGetter, 0,
v8::Handle<Value>(),
v8::ALL_CAN_READ);
// Add an accessor that is not accessible by cross-domain JS code.
global_template->SetAccessor(v8_str("unreachable"),
UnreachableGetter, 0,
v8::Handle<Value>(),
v8::DEFAULT);
v8::Persistent<Context> context0 = Context::New(NULL, global_template);
context0->Enter();
Local<v8::Object> global = context0->Global();
// Add a normal property that shadows 'accessible'
global->Set(v8_str("accessible"), v8_num(11));
// Enter a new context.
v8::HandleScope scope1;
v8::Persistent<Context> context1 = Context::New();
context1->Enter();
v8::Handle<v8::Object> global1 = context1->Global();
global1->Set(v8_str("other"), global);
// Should return 10, instead of 11
v8::Handle<Value> value = v8_compile("other.accessible")->Run();
CHECK(value->IsNumber());
CHECK_EQ(10, value->Int32Value());
value = v8_compile("other.unreachable")->Run();
CHECK(value->IsUndefined());
context1->Exit();
context0->Exit();
context1.Dispose();
context0.Dispose();
}
static int named_access_count = 0;
static int indexed_access_count = 0;
static bool NamedAccessCounter(Local<v8::Object> global,
Local<Value> name,
v8::AccessType type,
Local<Value> data) {
named_access_count++;
return true;
}
static bool IndexedAccessCounter(Local<v8::Object> global,
uint32_t key,
v8::AccessType type,
Local<Value> data) {
indexed_access_count++;
return true;
}
// This one is too easily disturbed by other tests.
TEST(AccessControlIC) {
named_access_count = 0;
indexed_access_count = 0;
v8::HandleScope handle_scope;
// Create an environment.
v8::Persistent<Context> context0 = Context::New();
context0->Enter();
// Create an object that requires access-check functions to be
// called for cross-domain access.
v8::Handle<v8::ObjectTemplate> object_template = v8::ObjectTemplate::New();
object_template->SetAccessCheckCallbacks(NamedAccessCounter,
IndexedAccessCounter);
Local<v8::Object> object = object_template->NewInstance();
v8::HandleScope scope1;
// Create another environment.
v8::Persistent<Context> context1 = Context::New();
context1->Enter();
// Make easy access to the object from the other environment.
v8::Handle<v8::Object> global1 = context1->Global();
global1->Set(v8_str("obj"), object);
v8::Handle<Value> value;
// Check that the named access-control function is called every time.
CompileRun("function testProp(obj) {"
" for (var i = 0; i < 10; i++) obj.prop = 1;"
" for (var j = 0; j < 10; j++) obj.prop;"
" return obj.prop"
"}");
value = CompileRun("testProp(obj)");
CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value());
CHECK_EQ(21, named_access_count);
// Check that the named access-control function is called every time.
CompileRun("var p = 'prop';"
"function testKeyed(obj) {"
" for (var i = 0; i < 10; i++) obj[p] = 1;"
" for (var j = 0; j < 10; j++) obj[p];"
" return obj[p];"
"}");
// Use obj which requires access checks. No inline caching is used
// in that case.
value = CompileRun("testKeyed(obj)");
CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value());
CHECK_EQ(42, named_access_count);
// Force the inline caches into generic state and try again.
CompileRun("testKeyed({ a: 0 })");
CompileRun("testKeyed({ b: 0 })");
value = CompileRun("testKeyed(obj)");
CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value());
CHECK_EQ(63, named_access_count);
// Check that the indexed access-control function is called every time.
CompileRun("function testIndexed(obj) {"
" for (var i = 0; i < 10; i++) obj[0] = 1;"
" for (var j = 0; j < 10; j++) obj[0];"
" return obj[0]"
"}");
value = CompileRun("testIndexed(obj)");
CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value());
CHECK_EQ(21, indexed_access_count);
// Force the inline caches into generic state.
CompileRun("testIndexed(new Array(1))");
// Test that the indexed access check is called.
value = CompileRun("testIndexed(obj)");
CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value());
CHECK_EQ(42, indexed_access_count);
// Check that the named access check is called when invoking
// functions on an object that requires access checks.
CompileRun("obj.f = function() {}");
CompileRun("function testCallNormal(obj) {"
" for (var i = 0; i < 10; i++) obj.f();"
"}");
CompileRun("testCallNormal(obj)");
CHECK_EQ(74, named_access_count);
// Force obj into slow case.
value = CompileRun("delete obj.prop");
CHECK(value->BooleanValue());
// Force inline caches into dictionary probing mode.
CompileRun("var o = { x: 0 }; delete o.x; testProp(o);");
// Test that the named access check is called.
value = CompileRun("testProp(obj);");
CHECK(value->IsNumber());
CHECK_EQ(1, value->Int32Value());
CHECK_EQ(96, named_access_count);
// Force the call inline cache into dictionary probing mode.
CompileRun("o.f = function() {}; testCallNormal(o)");
// Test that the named access check is still called for each
// invocation of the function.
value = CompileRun("testCallNormal(obj)");
CHECK_EQ(106, named_access_count);
context1->Exit();
context0->Exit();
context1.Dispose();
context0.Dispose();
}
static bool NamedAccessFlatten(Local<v8::Object> global,
Local<Value> name,
v8::AccessType type,
Local<Value> data) {
char buf[100];
int len;
CHECK(name->IsString());
memset(buf, 0x1, sizeof(buf));
len = Local<String>::Cast(name)->WriteAscii(buf);
CHECK_EQ(4, len);
uint16_t buf2[100];
memset(buf, 0x1, sizeof(buf));
len = Local<String>::Cast(name)->Write(buf2);
CHECK_EQ(4, len);
return true;
}
static bool IndexedAccessFlatten(Local<v8::Object> global,
uint32_t key,
v8::AccessType type,
Local<Value> data) {
return true;
}
// Regression test. In access checks, operations that may cause
// garbage collection are not allowed. It used to be the case that
// using the Write operation on a string could cause a garbage
// collection due to flattening of the string. This is no longer the
// case.
THREADED_TEST(AccessControlFlatten) {
named_access_count = 0;
indexed_access_count = 0;
v8::HandleScope handle_scope;
// Create an environment.
v8::Persistent<Context> context0 = Context::New();
context0->Enter();
// Create an object that requires access-check functions to be
// called for cross-domain access.
v8::Handle<v8::ObjectTemplate> object_template = v8::ObjectTemplate::New();
object_template->SetAccessCheckCallbacks(NamedAccessFlatten,
IndexedAccessFlatten);
Local<v8::Object> object = object_template->NewInstance();
v8::HandleScope scope1;
// Create another environment.
v8::Persistent<Context> context1 = Context::New();
context1->Enter();
// Make easy access to the object from the other environment.
v8::Handle<v8::Object> global1 = context1->Global();
global1->Set(v8_str("obj"), object);
v8::Handle<Value> value;
value = v8_compile("var p = 'as' + 'df';")->Run();
value = v8_compile("obj[p];")->Run();
context1->Exit();
context0->Exit();
context1.Dispose();
context0.Dispose();
}
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
static v8::Handle<Value> AccessControlNamedGetter(
Local<String>, const AccessorInfo&) {
return v8::Integer::New(42);
}
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
static v8::Handle<Value> AccessControlNamedSetter(
Local<String>, Local<Value> value, const AccessorInfo&) {
return value;
}
static v8::Handle<Value> AccessControlIndexedGetter(
uint32_t index,
const AccessorInfo& info) {
return v8_num(42);
}
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
static v8::Handle<Value> AccessControlIndexedSetter(
uint32_t, Local<Value> value, const AccessorInfo&) {
return value;
}
THREADED_TEST(AccessControlInterceptorIC) {
named_access_count = 0;
indexed_access_count = 0;
v8::HandleScope handle_scope;
// Create an environment.
v8::Persistent<Context> context0 = Context::New();
context0->Enter();
// Create an object that requires access-check functions to be
// called for cross-domain access. The object also has interceptors
// interceptor.
v8::Handle<v8::ObjectTemplate> object_template = v8::ObjectTemplate::New();
object_template->SetAccessCheckCallbacks(NamedAccessCounter,
IndexedAccessCounter);
object_template->SetNamedPropertyHandler(AccessControlNamedGetter,
AccessControlNamedSetter);
object_template->SetIndexedPropertyHandler(AccessControlIndexedGetter,
AccessControlIndexedSetter);
Local<v8::Object> object = object_template->NewInstance();
v8::HandleScope scope1;
// Create another environment.
v8::Persistent<Context> context1 = Context::New();
context1->Enter();
// Make easy access to the object from the other environment.
v8::Handle<v8::Object> global1 = context1->Global();
global1->Set(v8_str("obj"), object);
v8::Handle<Value> value;
// Check that the named access-control function is called every time
// eventhough there is an interceptor on the object.
value = v8_compile("for (var i = 0; i < 10; i++) obj.x = 1;")->Run();
value = v8_compile("for (var i = 0; i < 10; i++) obj.x;"
"obj.x")->Run();
CHECK(value->IsNumber());
CHECK_EQ(42, value->Int32Value());
CHECK_EQ(21, named_access_count);
value = v8_compile("var p = 'x';")->Run();
value = v8_compile("for (var i = 0; i < 10; i++) obj[p] = 1;")->Run();
value = v8_compile("for (var i = 0; i < 10; i++) obj[p];"
"obj[p]")->Run();
CHECK(value->IsNumber());
CHECK_EQ(42, value->Int32Value());
CHECK_EQ(42, named_access_count);
// Check that the indexed access-control function is called every
// time eventhough there is an interceptor on the object.
value = v8_compile("for (var i = 0; i < 10; i++) obj[0] = 1;")->Run();
value = v8_compile("for (var i = 0; i < 10; i++) obj[0];"
"obj[0]")->Run();
CHECK(value->IsNumber());
CHECK_EQ(42, value->Int32Value());
CHECK_EQ(21, indexed_access_count);
context1->Exit();
context0->Exit();
context1.Dispose();
context0.Dispose();
}
THREADED_TEST(Version) {
v8::V8::GetVersion();
}
static v8::Handle<Value> InstanceFunctionCallback(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
return v8_num(12);
}
THREADED_TEST(InstanceProperties) {
v8::HandleScope handle_scope;
LocalContext context;
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
Local<ObjectTemplate> instance = t->InstanceTemplate();
instance->Set(v8_str("x"), v8_num(42));
instance->Set(v8_str("f"),
v8::FunctionTemplate::New(InstanceFunctionCallback));
Local<Value> o = t->GetFunction()->NewInstance();
context->Global()->Set(v8_str("i"), o);
Local<Value> value = Script::Compile(v8_str("i.x"))->Run();
CHECK_EQ(42, value->Int32Value());
value = Script::Compile(v8_str("i.f()"))->Run();
CHECK_EQ(12, value->Int32Value());
}
static v8::Handle<Value>
GlobalObjectInstancePropertiesGet(Local<String> key, const AccessorInfo&) {
ApiTestFuzzer::Fuzz();
return v8::Handle<Value>();
}
THREADED_TEST(GlobalObjectInstanceProperties) {
v8::HandleScope handle_scope;
Local<Value> global_object;
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
t->InstanceTemplate()->SetNamedPropertyHandler(
GlobalObjectInstancePropertiesGet);
Local<ObjectTemplate> instance_template = t->InstanceTemplate();
instance_template->Set(v8_str("x"), v8_num(42));
instance_template->Set(v8_str("f"),
v8::FunctionTemplate::New(InstanceFunctionCallback));
{
LocalContext env(NULL, instance_template);
// Hold on to the global object so it can be used again in another
// environment initialization.
global_object = env->Global();
Local<Value> value = Script::Compile(v8_str("x"))->Run();
CHECK_EQ(42, value->Int32Value());
value = Script::Compile(v8_str("f()"))->Run();
CHECK_EQ(12, value->Int32Value());
}
{
// Create new environment reusing the global object.
LocalContext env(NULL, instance_template, global_object);
Local<Value> value = Script::Compile(v8_str("x"))->Run();
CHECK_EQ(42, value->Int32Value());
value = Script::Compile(v8_str("f()"))->Run();
CHECK_EQ(12, value->Int32Value());
}
}
static v8::Handle<Value> ShadowFunctionCallback(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
return v8_num(42);
}
static int shadow_y;
static int shadow_y_setter_call_count;
static int shadow_y_getter_call_count;
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
static void ShadowYSetter(Local<String>, Local<Value>, const AccessorInfo&) {
shadow_y_setter_call_count++;
shadow_y = 42;
}
static v8::Handle<Value> ShadowYGetter(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
shadow_y_getter_call_count++;
return v8_num(shadow_y);
}
static v8::Handle<Value> ShadowIndexedGet(uint32_t index,
const AccessorInfo& info) {
return v8::Handle<Value>();
}
static v8::Handle<Value> ShadowNamedGet(Local<String> key,
const AccessorInfo&) {
return v8::Handle<Value>();
}
THREADED_TEST(ShadowObject) {
shadow_y = shadow_y_setter_call_count = shadow_y_getter_call_count = 0;
v8::HandleScope handle_scope;
Local<ObjectTemplate> global_template = v8::ObjectTemplate::New();
LocalContext context(NULL, global_template);
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
t->InstanceTemplate()->SetNamedPropertyHandler(ShadowNamedGet);
t->InstanceTemplate()->SetIndexedPropertyHandler(ShadowIndexedGet);
Local<ObjectTemplate> proto = t->PrototypeTemplate();
Local<ObjectTemplate> instance = t->InstanceTemplate();
// Only allow calls of f on instances of t.
Local<v8::Signature> signature = v8::Signature::New(t);
proto->Set(v8_str("f"),
v8::FunctionTemplate::New(ShadowFunctionCallback,
Local<Value>(),
signature));
proto->Set(v8_str("x"), v8_num(12));
instance->SetAccessor(v8_str("y"), ShadowYGetter, ShadowYSetter);
Local<Value> o = t->GetFunction()->NewInstance();
context->Global()->Set(v8_str("__proto__"), o);
Local<Value> value =
Script::Compile(v8_str("propertyIsEnumerable(0)"))->Run();
CHECK(value->IsBoolean());
CHECK(!value->BooleanValue());
value = Script::Compile(v8_str("x"))->Run();
CHECK_EQ(12, value->Int32Value());
value = Script::Compile(v8_str("f()"))->Run();
CHECK_EQ(42, value->Int32Value());
Script::Compile(v8_str("y = 42"))->Run();
CHECK_EQ(1, shadow_y_setter_call_count);
value = Script::Compile(v8_str("y"))->Run();
CHECK_EQ(1, shadow_y_getter_call_count);
CHECK_EQ(42, value->Int32Value());
}
THREADED_TEST(HiddenPrototype) {
v8::HandleScope handle_scope;
LocalContext context;
Local<v8::FunctionTemplate> t0 = v8::FunctionTemplate::New();
t0->InstanceTemplate()->Set(v8_str("x"), v8_num(0));
Local<v8::FunctionTemplate> t1 = v8::FunctionTemplate::New();
t1->SetHiddenPrototype(true);
t1->InstanceTemplate()->Set(v8_str("y"), v8_num(1));
Local<v8::FunctionTemplate> t2 = v8::FunctionTemplate::New();
t2->SetHiddenPrototype(true);
t2->InstanceTemplate()->Set(v8_str("z"), v8_num(2));
Local<v8::FunctionTemplate> t3 = v8::FunctionTemplate::New();
t3->InstanceTemplate()->Set(v8_str("u"), v8_num(3));
Local<v8::Object> o0 = t0->GetFunction()->NewInstance();
Local<v8::Object> o1 = t1->GetFunction()->NewInstance();
Local<v8::Object> o2 = t2->GetFunction()->NewInstance();
Local<v8::Object> o3 = t3->GetFunction()->NewInstance();
// Setting the prototype on an object skips hidden prototypes.
CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value());
o0->Set(v8_str("__proto__"), o1);
CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value());
CHECK_EQ(1, o0->Get(v8_str("y"))->Int32Value());
o0->Set(v8_str("__proto__"), o2);
CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value());
CHECK_EQ(1, o0->Get(v8_str("y"))->Int32Value());
CHECK_EQ(2, o0->Get(v8_str("z"))->Int32Value());
o0->Set(v8_str("__proto__"), o3);
CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value());
CHECK_EQ(1, o0->Get(v8_str("y"))->Int32Value());
CHECK_EQ(2, o0->Get(v8_str("z"))->Int32Value());
CHECK_EQ(3, o0->Get(v8_str("u"))->Int32Value());
// Getting the prototype of o0 should get the first visible one
// which is o3. Therefore, z should not be defined on the prototype
// object.
Local<Value> proto = o0->Get(v8_str("__proto__"));
CHECK(proto->IsObject());
CHECK(Local<v8::Object>::Cast(proto)->Get(v8_str("z"))->IsUndefined());
}
THREADED_TEST(GetterSetterExceptions) {
v8::HandleScope handle_scope;
LocalContext context;
CompileRun(
"function Foo() { };"
"function Throw() { throw 5; };"
"var x = { };"
"x.__defineSetter__('set', Throw);"
"x.__defineGetter__('get', Throw);");
Local<v8::Object> x =
Local<v8::Object>::Cast(context->Global()->Get(v8_str("x")));
v8::TryCatch try_catch;
x->Set(v8_str("set"), v8::Integer::New(8));
x->Get(v8_str("get"));
x->Set(v8_str("set"), v8::Integer::New(8));
x->Get(v8_str("get"));
x->Set(v8_str("set"), v8::Integer::New(8));
x->Get(v8_str("get"));
x->Set(v8_str("set"), v8::Integer::New(8));
x->Get(v8_str("get"));
}
THREADED_TEST(Constructor) {
v8::HandleScope handle_scope;
LocalContext context;
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New();
templ->SetClassName(v8_str("Fun"));
Local<Function> cons = templ->GetFunction();
context->Global()->Set(v8_str("Fun"), cons);
Local<v8::Object> inst = cons->NewInstance();
i::Handle<i::JSObject> obj = v8::Utils::OpenHandle(*inst);
Local<Value> value = CompileRun("(new Fun()).constructor === Fun");
CHECK(value->BooleanValue());
}
THREADED_TEST(FunctionDescriptorException) {
v8::HandleScope handle_scope;
LocalContext context;
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New();
templ->SetClassName(v8_str("Fun"));
Local<Function> cons = templ->GetFunction();
context->Global()->Set(v8_str("Fun"), cons);
Local<Value> value = CompileRun(
"function test() {"
" try {"
" (new Fun()).blah()"
" } catch (e) {"
" var str = String(e);"
" if (str.indexOf('TypeError') == -1) return 1;"
" if (str.indexOf('[object Fun]') != -1) return 2;"
" if (str.indexOf('#<a Fun>') == -1) return 3;"
" return 0;"
" }"
" return 4;"
"}"
"test();");
CHECK_EQ(0, value->Int32Value());
}
THREADED_TEST(EvalAliasedDynamic) {
v8::HandleScope scope;
LocalContext current;
// This sets 'global' to the real global object (as opposed to the
// proxy). It is highly implementation dependent, so take care.
current->Global()->Set(v8_str("global"), current->Global()->GetPrototype());
// Tests where aliased eval can only be resolved dynamically.
Local<Script> script =
Script::Compile(v8_str("function f(x) { "
" var foo = 2;"
" with (x) { return eval('foo'); }"
"}"
"foo = 0;"
"result1 = f(new Object());"
"result2 = f(global);"
"var x = new Object();"
"x.eval = function(x) { return 1; };"
"result3 = f(x);"));
script->Run();
CHECK_EQ(2, current->Global()->Get(v8_str("result1"))->Int32Value());
CHECK_EQ(0, current->Global()->Get(v8_str("result2"))->Int32Value());
CHECK_EQ(1, current->Global()->Get(v8_str("result3"))->Int32Value());
v8::TryCatch try_catch;
script =
Script::Compile(v8_str("function f(x) { "
" var bar = 2;"
" with (x) { return eval('bar'); }"
"}"
"f(global)"));
script->Run();
CHECK(try_catch.HasCaught());
try_catch.Reset();
}
THREADED_TEST(CrossEval) {
v8::HandleScope scope;
LocalContext other;
LocalContext current;
Local<String> token = v8_str("<security token>");
other->SetSecurityToken(token);
current->SetSecurityToken(token);
// Setup reference from current to other.
current->Global()->Set(v8_str("other"), other->Global());
// Check that new variables are introduced in other context.
Local<Script> script =
Script::Compile(v8_str("other.eval('var foo = 1234')"));
script->Run();
Local<Value> foo = other->Global()->Get(v8_str("foo"));
CHECK_EQ(1234, foo->Int32Value());
CHECK(!current->Global()->Has(v8_str("foo")));
// Check that writing to non-existing properties introduces them in
// the other context.
script =
Script::Compile(v8_str("other.eval('na = 1234')"));
script->Run();
CHECK_EQ(1234, other->Global()->Get(v8_str("na"))->Int32Value());
CHECK(!current->Global()->Has(v8_str("na")));
// Check that global variables in current context are not visible in other
// context.
v8::TryCatch try_catch;
script =
Script::Compile(v8_str("var bar = 42; other.eval('bar');"));
Local<Value> result = script->Run();
CHECK(try_catch.HasCaught());
try_catch.Reset();
// Check that local variables in current context are not visible in other
// context.
script =
Script::Compile(v8_str("(function() { "
" var baz = 87;"
" return other.eval('baz');"
"})();"));
result = script->Run();
CHECK(try_catch.HasCaught());
try_catch.Reset();
// Check that global variables in the other environment are visible
// when evaluting code.
other->Global()->Set(v8_str("bis"), v8_num(1234));
script = Script::Compile(v8_str("other.eval('bis')"));
CHECK_EQ(1234, script->Run()->Int32Value());
CHECK(!try_catch.HasCaught());
// Check that the 'this' pointer points to the global object evaluating
// code.
other->Global()->Set(v8_str("t"), other->Global());
script = Script::Compile(v8_str("other.eval('this == t')"));
result = script->Run();
CHECK(result->IsTrue());
CHECK(!try_catch.HasCaught());
// Check that variables introduced in with-statement are not visible in
// other context.
script =
Script::Compile(v8_str("with({x:2}){other.eval('x')}"));
result = script->Run();
CHECK(try_catch.HasCaught());
try_catch.Reset();
// Check that you cannot use 'eval.call' with another object than the
// current global object.
script =
Script::Compile(v8_str("other.y = 1; eval.call(other, 'y')"));
result = script->Run();
CHECK(try_catch.HasCaught());
}
THREADED_TEST(CrossLazyLoad) {
v8::HandleScope scope;
LocalContext other;
LocalContext current;
Local<String> token = v8_str("<security token>");
other->SetSecurityToken(token);
current->SetSecurityToken(token);
// Setup reference from current to other.
current->Global()->Set(v8_str("other"), other->Global());
// Trigger lazy loading in other context.
Local<Script> script =
Script::Compile(v8_str("other.eval('new Date(42)')"));
Local<Value> value = script->Run();
CHECK_EQ(42.0, value->NumberValue());
}
static v8::Handle<Value> call_as_function(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
return args[0];
}
// Test that a call handler can be set for objects which will allow
// non-function objects created through the API to be called as
// functions.
THREADED_TEST(CallAsFunction) {
v8::HandleScope scope;
LocalContext context;
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
Local<ObjectTemplate> instance_template = t->InstanceTemplate();
instance_template->SetCallAsFunctionHandler(call_as_function);
Local<v8::Object> instance = t->GetFunction()->NewInstance();
context->Global()->Set(v8_str("obj"), instance);
v8::TryCatch try_catch;
Local<Value> value;
CHECK(!try_catch.HasCaught());
value = CompileRun("obj(42)");
CHECK(!try_catch.HasCaught());
CHECK_EQ(42, value->Int32Value());
value = CompileRun("(function(o){return o(49)})(obj)");
CHECK(!try_catch.HasCaught());
CHECK_EQ(49, value->Int32Value());
// test special case of call as function
value = CompileRun("[obj]['0'](45)");
CHECK(!try_catch.HasCaught());
CHECK_EQ(45, value->Int32Value());
value = CompileRun("obj.call = Function.prototype.call;"
"obj.call(null, 87)");
CHECK(!try_catch.HasCaught());
CHECK_EQ(87, value->Int32Value());
// Regression tests for bug #1116356: Calling call through call/apply
// must work for non-function receivers.
const char* apply_99 = "Function.prototype.call.apply(obj, [this, 99])";
value = CompileRun(apply_99);
CHECK(!try_catch.HasCaught());
CHECK_EQ(99, value->Int32Value());
const char* call_17 = "Function.prototype.call.call(obj, this, 17)";
value = CompileRun(call_17);
CHECK(!try_catch.HasCaught());
CHECK_EQ(17, value->Int32Value());
// Check that the call-as-function handler can be called through
// new. Currently, there is no way to check in the call-as-function
// handler if it has been called through new or not.
value = CompileRun("new obj(42)");
CHECK(!try_catch.HasCaught());
CHECK_EQ(42, value->Int32Value());
}
static int CountHandles() {
return v8::HandleScope::NumberOfHandles();
}
static int Recurse(int depth, int iterations) {
v8::HandleScope scope;
if (depth == 0) return CountHandles();
for (int i = 0; i < iterations; i++) {
Local<v8::Number> n = v8::Integer::New(42);
}
return Recurse(depth - 1, iterations);
}
THREADED_TEST(HandleIteration) {
static const int kIterations = 500;
static const int kNesting = 200;
CHECK_EQ(0, CountHandles());
{
v8::HandleScope scope1;
CHECK_EQ(0, CountHandles());
for (int i = 0; i < kIterations; i++) {
Local<v8::Number> n = v8::Integer::New(42);
CHECK_EQ(i + 1, CountHandles());
}
CHECK_EQ(kIterations, CountHandles());
{
v8::HandleScope scope2;
for (int j = 0; j < kIterations; j++) {
Local<v8::Number> n = v8::Integer::New(42);
CHECK_EQ(j + 1 + kIterations, CountHandles());
}
}
CHECK_EQ(kIterations, CountHandles());
}
CHECK_EQ(0, CountHandles());
CHECK_EQ(kNesting * kIterations, Recurse(kNesting, kIterations));
}
static v8::Handle<Value> InterceptorHasOwnPropertyGetter(
Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
return v8::Handle<Value>();
}
THREADED_TEST(InterceptorHasOwnProperty) {
v8::HandleScope scope;
LocalContext context;
Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
Local<v8::ObjectTemplate> instance_templ = fun_templ->InstanceTemplate();
instance_templ->SetNamedPropertyHandler(InterceptorHasOwnPropertyGetter);
Local<Function> function = fun_templ->GetFunction();
context->Global()->Set(v8_str("constructor"), function);
v8::Handle<Value> value = CompileRun(
"var o = new constructor();"
"o.hasOwnProperty('ostehaps');");
CHECK_EQ(false, value->BooleanValue());
value = CompileRun(
"o.ostehaps = 42;"
"o.hasOwnProperty('ostehaps');");
CHECK_EQ(true, value->BooleanValue());
value = CompileRun(
"var p = new constructor();"
"p.hasOwnProperty('ostehaps');");
CHECK_EQ(false, value->BooleanValue());
}
static v8::Handle<Value> InterceptorHasOwnPropertyGetterGC(
Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
i::Heap::CollectAllGarbage();
return v8::Handle<Value>();
}
THREADED_TEST(InterceptorHasOwnPropertyCausingGC) {
v8::HandleScope scope;
LocalContext context;
Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
Local<v8::ObjectTemplate> instance_templ = fun_templ->InstanceTemplate();
instance_templ->SetNamedPropertyHandler(InterceptorHasOwnPropertyGetterGC);
Local<Function> function = fun_templ->GetFunction();
context->Global()->Set(v8_str("constructor"), function);
// Let's first make some stuff so we can be sure to get a good GC.
CompileRun(
"function makestr(size) {"
" switch (size) {"
" case 1: return 'f';"
" case 2: return 'fo';"
" case 3: return 'foo';"
" }"
" return makestr(size >> 1) + makestr((size + 1) >> 1);"
"}"
"var x = makestr(12345);"
"x = makestr(31415);"
"x = makestr(23456);");
v8::Handle<Value> value = CompileRun(
"var o = new constructor();"
"o.__proto__ = new String(x);"
"o.hasOwnProperty('ostehaps');");
CHECK_EQ(false, value->BooleanValue());
}
static v8::Handle<Value> InterceptorLoadICGetter(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(v8_str("x")->Equals(name));
return v8::Integer::New(42);
}
// This test should hit the load IC for the interceptor case.
THREADED_TEST(InterceptorLoadIC) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorLoadICGetter);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
v8::Handle<Value> value = CompileRun(
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" result = o.x;"
"}");
CHECK_EQ(42, value->Int32Value());
}
static v8::Handle<Value> InterceptorStoreICSetter(
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
Local<String> key, Local<Value> value, const AccessorInfo&) {
CHECK(v8_str("x")->Equals(key));
CHECK_EQ(42, value->Int32Value());
return value;
}
// This test should hit the store IC for the interceptor case.
THREADED_TEST(InterceptorStoreIC) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorLoadICGetter,
InterceptorStoreICSetter);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
v8::Handle<Value> value = CompileRun(
"for (var i = 0; i < 1000; i++) {"
" o.x = 42;"
"}");
}
v8::Handle<Value> call_ic_function;
v8::Handle<Value> call_ic_function2;
v8::Handle<Value> call_ic_function3;
static v8::Handle<Value> InterceptorCallICGetter(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(v8_str("x")->Equals(name));
return call_ic_function;
}
// This test should hit the call IC for the interceptor case.
THREADED_TEST(InterceptorCallIC) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorCallICGetter);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
call_ic_function =
v8_compile("function f(x) { return x + 1; }; f")->Run();
v8::Handle<Value> value = CompileRun(
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" result = o.x(41);"
"}");
CHECK_EQ(42, value->Int32Value());
}
static int interceptor_call_count = 0;
static v8::Handle<Value> InterceptorICRefErrorGetter(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
if (v8_str("x")->Equals(name) && interceptor_call_count++ < 20) {
return call_ic_function2;
}
return v8::Handle<Value>();
}
// This test should hit load and call ICs for the interceptor case.
// Once in a while, the interceptor will reply that a property was not
// found in which case we should get a reference error.
THREADED_TEST(InterceptorICReferenceErrors) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorICRefErrorGetter);
LocalContext context(0, templ, v8::Handle<Value>());
call_ic_function2 = v8_compile("function h(x) { return x; }; h")->Run();
v8::Handle<Value> value = CompileRun(
"function f() {"
" for (var i = 0; i < 1000; i++) {"
" try { x; } catch(e) { return true; }"
" }"
" return false;"
"};"
"f();");
CHECK_EQ(true, value->BooleanValue());
interceptor_call_count = 0;
value = CompileRun(
"function g() {"
" for (var i = 0; i < 1000; i++) {"
" try { x(42); } catch(e) { return true; }"
" }"
" return false;"
"};"
"g();");
CHECK_EQ(true, value->BooleanValue());
}
static int interceptor_ic_exception_get_count = 0;
static v8::Handle<Value> InterceptorICExceptionGetter(
Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
if (v8_str("x")->Equals(name) && ++interceptor_ic_exception_get_count < 20) {
return call_ic_function3;
}
if (interceptor_ic_exception_get_count == 20) {
return v8::ThrowException(v8_num(42));
}
// Do not handle get for properties other than x.
return v8::Handle<Value>();
}
// Test interceptor load/call IC where the interceptor throws an
// exception once in a while.
THREADED_TEST(InterceptorICGetterExceptions) {
interceptor_ic_exception_get_count = 0;
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorICExceptionGetter);
LocalContext context(0, templ, v8::Handle<Value>());
call_ic_function3 = v8_compile("function h(x) { return x; }; h")->Run();
v8::Handle<Value> value = CompileRun(
"function f() {"
" for (var i = 0; i < 100; i++) {"
" try { x; } catch(e) { return true; }"
" }"
" return false;"
"};"
"f();");
CHECK_EQ(true, value->BooleanValue());
interceptor_ic_exception_get_count = 0;
value = CompileRun(
"function f() {"
" for (var i = 0; i < 100; i++) {"
" try { x(42); } catch(e) { return true; }"
" }"
" return false;"
"};"
"f();");
CHECK_EQ(true, value->BooleanValue());
}
static int interceptor_ic_exception_set_count = 0;
static v8::Handle<Value> InterceptorICExceptionSetter(
Split window support from V8. Here is a description of the background and design of split window in Chrome and V8: https://docs.google.com/a/google.com/Doc?id=chhjkpg_47fwddxbfr This change list splits the window object into two parts: 1) an inner window object used as the global object of contexts; 2) an outer window object exposed to JavaScript and accessible by the name 'window'. Firefox did it awhile ago, here are some discussions: https://wiki.mozilla.org/Gecko:SplitWindow. One additional benefit of splitting window in Chrome is that accessing global variables don't need security checks anymore, it can improve applications that use many global variables. V8 support of split window: There are a small number of changes on V8 api to support split window: Security context is removed from V8, so does related API functions; A global object can be detached from its context and reused by a new context; Access checks on an object template can be turned on/off by default; An object can turn on its access checks later; V8 has a new object type, ApiGlobalObject, which is the outer window object type. The existing JSGlobalObject becomes the inner window object type. Security checks are moved from JSGlobalObject to ApiGlobalObject. ApiGlobalObject is the one exposed to JavaScript, it is accessible through Context::Global(). ApiGlobalObject's prototype is set to JSGlobalObject so that property lookups are forwarded to JSGlobalObject. ApiGlobalObject forwards all other property access requests to JSGlobalObject, such as SetProperty, DeleteProperty, etc. Security token is moved to a global context, and ApiGlobalObject has a reference to its global context. JSGlobalObject has a reference to its global context as well. When accessing properties on a global object in JavaScript, the domain security check is performed by comparing the security token of the lexical context (Top::global_context()) to the token of global object's context. The check is only needed when the receiver is a window object, such as 'window.document'. Accessing global variables, such as 'var foo = 3; foo' does not need checks because the receiver is the inner window object. When an outer window is detached from its global context (when a frame navigates away from a page), it is completely detached from the inner window. A new context is created for the new page, and the outer global object is reused. At this point, the access check on the DOMWindow wrapper of the old context is turned on. The code in old context is still able to access DOMWindow properties, but it has to go through domain security checks. It is debatable on how to implement the outer window object. Currently each property access function has to check if the receiver is ApiGlobalObject type. This approach might be error-prone that one may forget to check the receiver when adding new functions. It is unlikely a performance issue because accessing global variables are more common than 'window.foo' style coding. I am still working on the ARM port, and I'd like to hear comments and suggestions on the best way to support it in V8. Review URL: http://codereview.chromium.org/7366 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@540 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-10-21 19:07:58 +00:00
Local<String> key, Local<Value> value, const AccessorInfo&) {
ApiTestFuzzer::Fuzz();
if (++interceptor_ic_exception_set_count > 20) {
return v8::ThrowException(v8_num(42));
}
// Do not actually handle setting.
return v8::Handle<Value>();
}
// Test interceptor store IC where the interceptor throws an exception
// once in a while.
THREADED_TEST(InterceptorICSetterExceptions) {
interceptor_ic_exception_set_count = 0;
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(0, InterceptorICExceptionSetter);
LocalContext context(0, templ, v8::Handle<Value>());
v8::Handle<Value> value = CompileRun(
"function f() {"
" for (var i = 0; i < 100; i++) {"
" try { x = 42; } catch(e) { return true; }"
" }"
" return false;"
"};"
"f();");
CHECK_EQ(true, value->BooleanValue());
}
// Test that we ignore null interceptors.
THREADED_TEST(NullNamedInterceptor) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(0);
LocalContext context;
templ->Set("x", v8_num(42));
v8::Handle<v8::Object> obj = templ->NewInstance();
context->Global()->Set(v8_str("obj"), obj);
v8::Handle<Value> value = CompileRun("obj.x");
CHECK(value->IsInt32());
CHECK_EQ(42, value->Int32Value());
}
// Test that we ignore null interceptors.
THREADED_TEST(NullIndexedInterceptor) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(0);
LocalContext context;
templ->Set("42", v8_num(42));
v8::Handle<v8::Object> obj = templ->NewInstance();
context->Global()->Set(v8_str("obj"), obj);
v8::Handle<Value> value = CompileRun("obj[42]");
CHECK(value->IsInt32());
CHECK_EQ(42, value->Int32Value());
}
static v8::Handle<Value> ParentGetter(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
return v8_num(1);
}
static v8::Handle<Value> ChildGetter(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
return v8_num(42);
}
THREADED_TEST(Overriding) {
v8::HandleScope scope;
LocalContext context;
// Parent template.
Local<v8::FunctionTemplate> parent_templ = v8::FunctionTemplate::New();
Local<ObjectTemplate> parent_instance_templ =
parent_templ->InstanceTemplate();
parent_instance_templ->SetAccessor(v8_str("f"), ParentGetter);
// Template that inherits from the parent template.
Local<v8::FunctionTemplate> child_templ = v8::FunctionTemplate::New();
Local<ObjectTemplate> child_instance_templ =
child_templ->InstanceTemplate();
child_templ->Inherit(parent_templ);
// Override 'f'. The child version of 'f' should get called for child
// instances.
child_instance_templ->SetAccessor(v8_str("f"), ChildGetter);
// Add 'g' twice. The 'g' added last should get called for instances.
child_instance_templ->SetAccessor(v8_str("g"), ParentGetter);
child_instance_templ->SetAccessor(v8_str("g"), ChildGetter);
// Add 'h' as an accessor to the proto template with ReadOnly attributes
// so 'h' can be shadowed on the instance object.
Local<ObjectTemplate> child_proto_templ = child_templ->PrototypeTemplate();
child_proto_templ->SetAccessor(v8_str("h"), ParentGetter, 0,
v8::Handle<Value>(), v8::DEFAULT, v8::ReadOnly);
// Add 'i' as an accessor to the instance template with ReadOnly attributes
// but the attribute does not have effect because it is duplicated with
// NULL setter.
child_instance_templ->SetAccessor(v8_str("i"), ChildGetter, 0,
v8::Handle<Value>(), v8::DEFAULT, v8::ReadOnly);
// Instantiate the child template.
Local<v8::Object> instance = child_templ->GetFunction()->NewInstance();
// Check that the child function overrides the parent one.
context->Global()->Set(v8_str("o"), instance);
Local<Value> value = v8_compile("o.f")->Run();
// Check that the 'g' that was added last is hit.
CHECK_EQ(42, value->Int32Value());
value = v8_compile("o.g")->Run();
CHECK_EQ(42, value->Int32Value());
// Check 'h' can be shadowed.
value = v8_compile("o.h = 3; o.h")->Run();
CHECK_EQ(3, value->Int32Value());
// Check 'i' is cannot be shadowed or changed.
value = v8_compile("o.i = 3; o.i")->Run();
CHECK_EQ(42, value->Int32Value());
}
static v8::Handle<Value> IsConstructHandler(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
if (args.IsConstructCall()) {
return v8::Boolean::New(true);
}
return v8::Boolean::New(false);
}
THREADED_TEST(IsConstructCall) {
v8::HandleScope scope;
// Function template with call handler.
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New();
templ->SetCallHandler(IsConstructHandler);
LocalContext context;
context->Global()->Set(v8_str("f"), templ->GetFunction());
Local<Value> value = v8_compile("f()")->Run();
CHECK(!value->BooleanValue());
value = v8_compile("new f()")->Run();
CHECK(value->BooleanValue());
}
THREADED_TEST(ObjectProtoToString) {
v8::HandleScope scope;
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New();
templ->SetClassName(v8_str("MyClass"));
LocalContext context;
Local<String> customized_tostring = v8_str("customized toString");
// Replace Object.prototype.toString
v8_compile("Object.prototype.toString = function() {"
" return 'customized toString';"
"}")->Run();
// Normal ToString call should call replaced Object.prototype.toString
Local<v8::Object> instance = templ->GetFunction()->NewInstance();
Local<String> value = instance->ToString();
CHECK(value->IsString() && value->Equals(customized_tostring));
// ObjectProtoToString should not call replace toString function.
value = instance->ObjectProtoToString();
CHECK(value->IsString() && value->Equals(v8_str("[object MyClass]")));
// Check global
value = context->Global()->ObjectProtoToString();
CHECK(value->IsString() && value->Equals(v8_str("[object global]")));
// Check ordinary object
Local<Value> object = v8_compile("new Object()")->Run();
value = Local<v8::Object>::Cast(object)->ObjectProtoToString();
CHECK(value->IsString() && value->Equals(v8_str("[object Object]")));
}
bool ApiTestFuzzer::fuzzing_ = false;
v8::internal::Semaphore* ApiTestFuzzer::all_tests_done_=
v8::internal::OS::CreateSemaphore(0);
int ApiTestFuzzer::active_tests_;
int ApiTestFuzzer::tests_being_run_;
int ApiTestFuzzer::current_;
// We are in a callback and want to switch to another thread (if we
// are currently running the thread fuzzing test).
void ApiTestFuzzer::Fuzz() {
if (!fuzzing_) return;
ApiTestFuzzer* test = RegisterThreadedTest::nth(current_)->fuzzer_;
test->ContextSwitch();
}
// Let the next thread go. Since it is also waiting on the V8 lock it may
// not start immediately.
bool ApiTestFuzzer::NextThread() {
int test_position = GetNextTestNumber();
int test_number = RegisterThreadedTest::nth(current_)->fuzzer_->test_number_;
if (test_position == current_) {
printf("Stay with %d\n", test_number);
return false;
}
printf("Switch from %d to %d\n",
current_ < 0 ? 0 : test_number, test_position < 0 ? 0 : test_number);
current_ = test_position;
RegisterThreadedTest::nth(current_)->fuzzer_->gate_->Signal();
return true;
}
void ApiTestFuzzer::Run() {
// When it is our turn...
gate_->Wait();
{
// ... get the V8 lock and start running the test.
v8::Locker locker;
CallTest();
}
// This test finished.
active_ = false;
active_tests_--;
// If it was the last then signal that fact.
if (active_tests_ == 0) {
all_tests_done_->Signal();
} else {
// Otherwise select a new test and start that.
NextThread();
}
}
static unsigned linear_congruential_generator;
void ApiTestFuzzer::Setup(PartOfTest part) {
linear_congruential_generator = i::FLAG_testing_prng_seed;
fuzzing_ = true;
int start = (part == FIRST_PART) ? 0 : (RegisterThreadedTest::count() >> 1);
int end = (part == FIRST_PART)
? (RegisterThreadedTest::count() >> 1)
: RegisterThreadedTest::count();
active_tests_ = tests_being_run_ = end - start;
for (int i = 0; i < tests_being_run_; i++) {
RegisterThreadedTest::nth(i)->fuzzer_ = new ApiTestFuzzer(i + start);
}
for (int i = 0; i < active_tests_; i++) {
RegisterThreadedTest::nth(i)->fuzzer_->Start();
}
}
static void CallTestNumber(int test_number) {
(RegisterThreadedTest::nth(test_number)->callback())();
}
void ApiTestFuzzer::RunAllTests() {
// Set off the first test.
current_ = -1;
NextThread();
// Wait till they are all done.
all_tests_done_->Wait();
}
int ApiTestFuzzer::GetNextTestNumber() {
int next_test;
do {
next_test = (linear_congruential_generator >> 16) % tests_being_run_;
linear_congruential_generator *= 1664525u;
linear_congruential_generator += 1013904223u;
} while (!RegisterThreadedTest::nth(next_test)->fuzzer_->active_);
return next_test;
}
void ApiTestFuzzer::ContextSwitch() {
// If the new thread is the same as the current thread there is nothing to do.
if (NextThread()) {
// Now it can start.
v8::Unlocker unlocker;
// Wait till someone starts us again.
gate_->Wait();
// And we're off.
}
}
void ApiTestFuzzer::TearDown() {
fuzzing_ = false;
for (int i = 0; i < RegisterThreadedTest::count(); i++) {
ApiTestFuzzer *fuzzer = RegisterThreadedTest::nth(i)->fuzzer_;
if (fuzzer != NULL) fuzzer->Join();
}
}
// Lets not be needlessly self-referential.
TEST(Threading) {
ApiTestFuzzer::Setup(ApiTestFuzzer::FIRST_PART);
ApiTestFuzzer::RunAllTests();
ApiTestFuzzer::TearDown();
}
TEST(Threading2) {
ApiTestFuzzer::Setup(ApiTestFuzzer::SECOND_PART);
ApiTestFuzzer::RunAllTests();
ApiTestFuzzer::TearDown();
}
void ApiTestFuzzer::CallTest() {
printf("Start test %d\n", test_number_);
CallTestNumber(test_number_);
printf("End test %d\n", test_number_);
}
static v8::Handle<Value> ThrowInJS(const v8::Arguments& args) {
CHECK(v8::Locker::IsLocked());
ApiTestFuzzer::Fuzz();
v8::Unlocker unlocker;
const char* code = "throw 7;";
{
v8::Locker nested_locker;
v8::HandleScope scope;
v8::Handle<Value> exception;
{ v8::TryCatch try_catch;
v8::Handle<Value> value = CompileRun(code);
CHECK(value.IsEmpty());
CHECK(try_catch.HasCaught());
// Make sure to wrap the exception in a new handle because
// the handle returned from the TryCatch is destroyed
// when the TryCatch is destroyed.
exception = Local<Value>::New(try_catch.Exception());
}
return v8::ThrowException(exception);
}
}
static v8::Handle<Value> ThrowInJSNoCatch(const v8::Arguments& args) {
CHECK(v8::Locker::IsLocked());
ApiTestFuzzer::Fuzz();
v8::Unlocker unlocker;
const char* code = "throw 7;";
{
v8::Locker nested_locker;
v8::HandleScope scope;
v8::Handle<Value> value = CompileRun(code);
CHECK(value.IsEmpty());
return v8_str("foo");
}
}
// These are locking tests that don't need to be run again
// as part of the locking aggregation tests.
TEST(NestedLockers) {
v8::Locker locker;
CHECK(v8::Locker::IsLocked());
v8::HandleScope scope;
LocalContext env;
Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(ThrowInJS);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("throw_in_js"), fun);
Local<Script> script = v8_compile("(function () {"
" try {"
" throw_in_js();"
" return 42;"
" } catch (e) {"
" return e * 13;"
" }"
"})();");
CHECK_EQ(91, script->Run()->Int32Value());
}
// These are locking tests that don't need to be run again
// as part of the locking aggregation tests.
TEST(NestedLockersNoTryCatch) {
v8::Locker locker;
v8::HandleScope scope;
LocalContext env;
Local<v8::FunctionTemplate> fun_templ =
v8::FunctionTemplate::New(ThrowInJSNoCatch);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("throw_in_js"), fun);
Local<Script> script = v8_compile("(function () {"
" try {"
" throw_in_js();"
" return 42;"
" } catch (e) {"
" return e * 13;"
" }"
"})();");
CHECK_EQ(91, script->Run()->Int32Value());
}
THREADED_TEST(RecursiveLocking) {
v8::Locker locker;
{
v8::Locker locker2;
CHECK(v8::Locker::IsLocked());
}
}
static v8::Handle<Value> UnlockForAMoment(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
v8::Unlocker unlocker;
return v8::Undefined();
}
THREADED_TEST(LockUnlockLock) {
{
v8::Locker locker;
v8::HandleScope scope;
LocalContext env;
Local<v8::FunctionTemplate> fun_templ =
v8::FunctionTemplate::New(UnlockForAMoment);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("unlock_for_a_moment"), fun);
Local<Script> script = v8_compile("(function () {"
" unlock_for_a_moment();"
" return 42;"
"})();");
CHECK_EQ(42, script->Run()->Int32Value());
}
{
v8::Locker locker;
v8::HandleScope scope;
LocalContext env;
Local<v8::FunctionTemplate> fun_templ =
v8::FunctionTemplate::New(UnlockForAMoment);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("unlock_for_a_moment"), fun);
Local<Script> script = v8_compile("(function () {"
" unlock_for_a_moment();"
" return 42;"
"})();");
CHECK_EQ(42, script->Run()->Int32Value());
}
}
static int GetSurvivingGlobalObjectsCount() {
int count = 0;
// We need to collect all garbage twice to be sure that everything
// has been collected. This is because inline caches are cleared in
// the first garbage collection but some of the maps have already
// been marked at that point. Therefore some of the maps are not
// collected until the second garbage collection.
v8::internal::Heap::CollectAllGarbage();
v8::internal::Heap::CollectAllGarbage();
v8::internal::HeapIterator it;
while (it.has_next()) {
v8::internal::HeapObject* object = it.next();
if (object->IsJSGlobalObject()) {
count++;
}
}
#ifdef DEBUG
if (count > 0) v8::internal::Heap::TracePathToGlobal();
#endif
return count;
}
TEST(DontLeakGlobalObjects) {
// Regression test for issues 1139850 and 1174891.
v8::V8::Initialize();
int count = GetSurvivingGlobalObjectsCount();
for (int i = 0; i < 5; i++) {
{ v8::HandleScope scope;
LocalContext context;
}
CHECK_EQ(count, GetSurvivingGlobalObjectsCount());
{ v8::HandleScope scope;
LocalContext context;
v8_compile("Date")->Run();
}
CHECK_EQ(count, GetSurvivingGlobalObjectsCount());
{ v8::HandleScope scope;
LocalContext context;
v8_compile("/aaa/")->Run();
}
CHECK_EQ(count, GetSurvivingGlobalObjectsCount());
{ v8::HandleScope scope;
const char* extension_list[] = { "v8/gc" };
v8::ExtensionConfiguration extensions(1, extension_list);
LocalContext context(&extensions);
v8_compile("gc();")->Run();
}
CHECK_EQ(count, GetSurvivingGlobalObjectsCount());
}
}
THREADED_TEST(CheckForCrossContextObjectLiterals) {
v8::V8::Initialize();
const int nof = 2;
const char* sources[nof] = {
"try { [ 2, 3, 4 ].forEach(5); } catch(e) { e.toString(); }",
"Object()"
};
for (int i = 0; i < nof; i++) {
const char* source = sources[i];
{ v8::HandleScope scope;
LocalContext context;
CompileRun(source);
}
{ v8::HandleScope scope;
LocalContext context;
CompileRun(source);
}
}
}
static v8::Handle<Value> NestedScope(v8::Persistent<Context> env) {
v8::HandleScope inner;
env->Enter();
v8::Handle<Value> three = v8_num(3);
v8::Handle<Value> value = inner.Close(three);
env->Exit();
return value;
}
THREADED_TEST(NestedHandleScopeAndContexts) {
v8::HandleScope outer;
v8::Persistent<Context> env = Context::New();
env->Enter();
v8::Handle<Value> value = NestedScope(env);
v8::Handle<String> str = value->ToString();
env->Exit();
env.Dispose();
}
THREADED_TEST(ExternalAllocatedMemory) {
v8::HandleScope outer;
const int kSize = 1024*1024;
CHECK_EQ(v8::V8::AdjustAmountOfExternalAllocatedMemory(kSize), kSize);
CHECK_EQ(v8::V8::AdjustAmountOfExternalAllocatedMemory(-kSize), 0);
}
THREADED_TEST(DisposeEnteredContext) {
v8::HandleScope scope;
LocalContext outer;
{ v8::Persistent<v8::Context> inner = v8::Context::New();
inner->Enter();
inner.Dispose();
inner.Clear();
inner->Exit();
}
}
// Regression test for issue 54, object templates with internal fields
// but no accessors or interceptors did not get their internal field
// count set on instances.
THREADED_TEST(Regress54) {
v8::HandleScope outer;
LocalContext context;
static v8::Persistent<v8::ObjectTemplate> templ;
if (templ.IsEmpty()) {
v8::HandleScope inner;
v8::Handle<v8::ObjectTemplate> local = v8::ObjectTemplate::New();
local->SetInternalFieldCount(1);
templ = v8::Persistent<v8::ObjectTemplate>::New(inner.Close(local));
}
v8::Handle<v8::Object> result = templ->NewInstance();
CHECK_EQ(1, result->InternalFieldCount());
}
// If part of the threaded tests, this test makes ThreadingTest fail
// on mac.
TEST(CatchStackOverflow) {
v8::HandleScope scope;
LocalContext context;
v8::TryCatch try_catch;
v8::Handle<v8::Script> script = v8::Script::Compile(v8::String::New(
"function f() {"
" return f();"
"}"
""
"f();"));
v8::Handle<v8::Value> result = script->Run();
CHECK(result.IsEmpty());
}
static void CheckTryCatchSourceInfo(v8::Handle<v8::Script> script,
const char* resource_name,
int line_offset) {
v8::HandleScope scope;
v8::TryCatch try_catch;
v8::Handle<v8::Value> result = script->Run();
CHECK(result.IsEmpty());
CHECK(try_catch.HasCaught());
v8::Handle<v8::Message> message = try_catch.Message();
CHECK(!message.IsEmpty());
CHECK_EQ(10 + line_offset, message->GetLineNumber());
CHECK_EQ(91, message->GetStartPosition());
CHECK_EQ(92, message->GetEndPosition());
CHECK_EQ(2, message->GetStartColumn());
CHECK_EQ(3, message->GetEndColumn());
v8::String::AsciiValue line(message->GetSourceLine());
CHECK_EQ(" throw 'nirk';", *line);
v8::String::AsciiValue name(message->GetScriptResourceName());
CHECK_EQ(resource_name, *name);
}
THREADED_TEST(TryCatchSourceInfo) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<v8::String> source = v8::String::New(
"function Foo() {\n"
" return Bar();\n"
"}\n"
"\n"
"function Bar() {\n"
" return Baz();\n"
"}\n"
"\n"
"function Baz() {\n"
" throw 'nirk';\n"
"}\n"
"\n"
"Foo();\n");
const char* resource_name;
v8::Handle<v8::Script> script;
resource_name = "test.js";
script = v8::Script::Compile(source, v8::String::New(resource_name));
CheckTryCatchSourceInfo(script, resource_name, 0);
resource_name = "test1.js";
v8::ScriptOrigin origin1(v8::String::New(resource_name));
script = v8::Script::Compile(source, &origin1);
CheckTryCatchSourceInfo(script, resource_name, 0);
resource_name = "test2.js";
v8::ScriptOrigin origin2(v8::String::New(resource_name), v8::Integer::New(7));
script = v8::Script::Compile(source, &origin2);
CheckTryCatchSourceInfo(script, resource_name, 7);
}
THREADED_TEST(CompilationCache) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<v8::String> source0 = v8::String::New("1234");
v8::Handle<v8::String> source1 = v8::String::New("1234");
v8::Handle<v8::Script> script0 =
v8::Script::Compile(source0, v8::String::New("test.js"));
v8::Handle<v8::Script> script1 =
v8::Script::Compile(source1, v8::String::New("test.js"));
v8::Handle<v8::Script> script2 =
v8::Script::Compile(source0); // different origin
CHECK_EQ(1234, script0->Run()->Int32Value());
CHECK_EQ(1234, script1->Run()->Int32Value());
CHECK_EQ(1234, script2->Run()->Int32Value());
}
static v8::Handle<Value> FunctionNameCallback(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
return v8_num(42);
}
THREADED_TEST(CallbackFunctionName) {
v8::HandleScope scope;
LocalContext context;
Local<ObjectTemplate> t = ObjectTemplate::New();
t->Set(v8_str("asdf"), v8::FunctionTemplate::New(FunctionNameCallback));
context->Global()->Set(v8_str("obj"), t->NewInstance());
v8::Handle<v8::Value> value = CompileRun("obj.asdf.name");
CHECK(value->IsString());
v8::String::AsciiValue name(value);
CHECK_EQ("asdf", *name);
}
THREADED_TEST(DateAccess) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<v8::Value> date = v8::Date::New(1224744689038.0);
CHECK(date->IsDate());
CHECK_EQ(1224744689038.0, v8::Handle<v8::Date>::Cast(date)->NumberValue());
}
void CheckProperties(v8::Handle<v8::Value> val, int elmc, const char* elmv[]) {
v8::Handle<v8::Object> obj = v8::Handle<v8::Object>::Cast(val);
v8::Handle<v8::Array> props = obj->GetPropertyNames();
CHECK_EQ(elmc, props->Length());
for (int i = 0; i < elmc; i++) {
v8::String::Utf8Value elm(props->Get(v8::Integer::New(i)));
CHECK_EQ(elmv[i], *elm);
}
}
THREADED_TEST(PropertyEnumeration) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<v8::Value> obj = v8::Script::Compile(v8::String::New(
"var result = [];"
"result[0] = {};"
"result[1] = {a: 1, b: 2};"
"result[2] = [1, 2, 3];"
"var proto = {x: 1, y: 2, z: 3};"
"var x = { __proto__: proto, w: 0, z: 1 };"
"result[3] = x;"
"result;"))->Run();
v8::Handle<v8::Array> elms = v8::Handle<v8::Array>::Cast(obj);
CHECK_EQ(4, elms->Length());
int elmc0 = 0;
const char** elmv0 = NULL;
CheckProperties(elms->Get(v8::Integer::New(0)), elmc0, elmv0);
int elmc1 = 2;
const char* elmv1[] = {"a", "b"};
CheckProperties(elms->Get(v8::Integer::New(1)), elmc1, elmv1);
int elmc2 = 3;
const char* elmv2[] = {"0", "1", "2"};
CheckProperties(elms->Get(v8::Integer::New(2)), elmc2, elmv2);
int elmc3 = 4;
const char* elmv3[] = {"w", "z", "x", "y"};
CheckProperties(elms->Get(v8::Integer::New(3)), elmc3, elmv3);
}
static v8::Handle<Value> AccessorProhibitsOverwritingGetter(
Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
return v8::True();
}
THREADED_TEST(AccessorProhibitsOverwriting) {
v8::HandleScope scope;
LocalContext context;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessor(v8_str("x"),
AccessorProhibitsOverwritingGetter,
0,
v8::Handle<Value>(),
v8::PROHIBITS_OVERWRITING,
v8::ReadOnly);
Local<v8::Object> instance = templ->NewInstance();
context->Global()->Set(v8_str("obj"), instance);
Local<Value> value = CompileRun(
"obj.__defineGetter__('x', function() { return false; });"
"obj.x");
CHECK(value->BooleanValue());
value = CompileRun(
"var setter_called = false;"
"obj.__defineSetter__('x', function() { setter_called = true; });"
"obj.x = 42;"
"setter_called");
CHECK(!value->BooleanValue());
value = CompileRun(
"obj2 = {};"
"obj2.__proto__ = obj;"
"obj2.__defineGetter__('x', function() { return false; });"
"obj2.x");
CHECK(value->BooleanValue());
value = CompileRun(
"var setter_called = false;"
"obj2 = {};"
"obj2.__proto__ = obj;"
"obj2.__defineSetter__('x', function() { setter_called = true; });"
"obj2.x = 42;"
"setter_called");
CHECK(!value->BooleanValue());
}
static bool NamedSetAccessBlocker(Local<v8::Object> obj,
Local<Value> name,
v8::AccessType type,
Local<Value> data) {
return type != v8::ACCESS_SET;
}
static bool IndexedSetAccessBlocker(Local<v8::Object> obj,
uint32_t key,
v8::AccessType type,
Local<Value> data) {
return type != v8::ACCESS_SET;
}
THREADED_TEST(DisableAccessChecksWhileConfiguring) {
v8::HandleScope scope;
LocalContext context;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessCheckCallbacks(NamedSetAccessBlocker,
IndexedSetAccessBlocker);
templ->Set(v8_str("x"), v8::True());
Local<v8::Object> instance = templ->NewInstance();
context->Global()->Set(v8_str("obj"), instance);
Local<Value> value = CompileRun("obj.x");
CHECK(value->BooleanValue());
}
static bool NamedGetAccessBlocker(Local<v8::Object> obj,
Local<Value> name,
v8::AccessType type,
Local<Value> data) {
return false;
}
static bool IndexedGetAccessBlocker(Local<v8::Object> obj,
uint32_t key,
v8::AccessType type,
Local<Value> data) {
return false;
}
THREADED_TEST(AccessChecksReenabledCorrectly) {
v8::HandleScope scope;
LocalContext context;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessCheckCallbacks(NamedGetAccessBlocker,
IndexedGetAccessBlocker);
templ->Set(v8_str("a"), v8_str("a"));
// Add more than 8 (see kMaxFastProperties) properties
// so that the constructor will force copying map.
// Cannot sprintf, gcc complains unsafety.
char buf[4];
for (char i = '0'; i <= '9' ; i++) {
buf[0] = i;
for (char j = '0'; j <= '9'; j++) {
buf[1] = j;
for (char k = '0'; k <= '9'; k++) {
buf[2] = k;
buf[3] = 0;
templ->Set(v8_str(buf), v8::Number::New(k));
}
}
}
Local<v8::Object> instance_1 = templ->NewInstance();
context->Global()->Set(v8_str("obj_1"), instance_1);
Local<Value> value_1 = CompileRun("obj_1.a");
CHECK(value_1->IsUndefined());
Local<v8::Object> instance_2 = templ->NewInstance();
context->Global()->Set(v8_str("obj_2"), instance_2);
Local<Value> value_2 = CompileRun("obj_2.a");
CHECK(value_2->IsUndefined());
}
// This tests that access check information remains on the global
// object template when creating contexts.
THREADED_TEST(AccessControlRepeatedContextCreation) {
v8::HandleScope handle_scope;
v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
global_template->SetAccessCheckCallbacks(NamedSetAccessBlocker,
IndexedSetAccessBlocker);
i::Handle<i::ObjectTemplateInfo> internal_template =
v8::Utils::OpenHandle(*global_template);
CHECK(!internal_template->constructor()->IsUndefined());
i::Handle<i::FunctionTemplateInfo> constructor(
i::FunctionTemplateInfo::cast(internal_template->constructor()));
CHECK(!constructor->access_check_info()->IsUndefined());
v8::Persistent<Context> context0 = Context::New(NULL, global_template);
CHECK(!constructor->access_check_info()->IsUndefined());
}
// This test verifies that pre-compilation (aka preparsing) can be called
// without initializing the whole VM. Thus we cannot run this test in a
// multi-threaded setup.
TEST(PreCompile) {
// TODO(155): This test would break without the initialization of V8. This is
// a workaround for now to make this test not fail.
v8::V8::Initialize();
const char *script = "function foo(a) { return a+1; }";
v8::ScriptData *sd = v8::ScriptData::PreCompile(script, strlen(script));
CHECK_NE(sd->Length(), 0);
CHECK_NE(sd->Data(), NULL);
delete sd;
}
// This tests that we do not allow dictionary load/call inline caches
// to use functions that have not yet been compiled. The potential
// problem of loading a function that has not yet been compiled can
// arise because we share code between contexts via the compilation
// cache.
THREADED_TEST(DictionaryICLoadedFunction) {
v8::HandleScope scope;
// Test LoadIC.
for (int i = 0; i < 2; i++) {
LocalContext context;
context->Global()->Set(v8_str("tmp"), v8::True());
context->Global()->Delete(v8_str("tmp"));
CompileRun("for (var j = 0; j < 10; j++) new RegExp('');");
}
// Test CallIC.
for (int i = 0; i < 2; i++) {
LocalContext context;
context->Global()->Set(v8_str("tmp"), v8::True());
context->Global()->Delete(v8_str("tmp"));
CompileRun("for (var j = 0; j < 10; j++) RegExp('')");
}
}
// Test that cross-context new calls use the context of the callee to
// create the new JavaScript object.
THREADED_TEST(CrossContextNew) {
v8::HandleScope scope;
v8::Persistent<Context> context0 = Context::New();
v8::Persistent<Context> context1 = Context::New();
// Allow cross-domain access.
Local<String> token = v8_str("<security token>");
context0->SetSecurityToken(token);
context1->SetSecurityToken(token);
// Set an 'x' property on the Object prototype and define a
// constructor function in context0.
context0->Enter();
CompileRun("Object.prototype.x = 42; function C() {};");
context0->Exit();
// Call the constructor function from context0 and check that the
// result has the 'x' property.
context1->Enter();
context1->Global()->Set(v8_str("other"), context0->Global());
Local<Value> value = CompileRun("var instance = new other.C(); instance.x");
CHECK(value->IsInt32());
CHECK_EQ(42, value->Int32Value());
context1->Exit();
// Dispose the contexts to allow them to be garbage collected.
context0.Dispose();
context1.Dispose();
}
class RegExpInterruptTest {
public:
RegExpInterruptTest() : block_(NULL) {}
~RegExpInterruptTest() { delete block_; }
void RunTest() {
block_ = i::OS::CreateSemaphore(0);
gc_count_ = 0;
gc_during_regexp_ = 0;
regexp_success_ = false;
gc_success_ = false;
GCThread gc_thread(this);
gc_thread.Start();
v8::Locker::StartPreemption(1);
LongRunningRegExp();
{
v8::Unlocker unlock;
gc_thread.Join();
}
v8::Locker::StopPreemption();
CHECK(regexp_success_);
CHECK(gc_success_);
}
private:
// Number of garbage collections required.
static const int kRequiredGCs = 5;
class GCThread : public i::Thread {
public:
explicit GCThread(RegExpInterruptTest* test)
: test_(test) {}
virtual void Run() {
test_->CollectGarbage();
}
private:
RegExpInterruptTest* test_;
};
void CollectGarbage() {
block_->Wait();
while (gc_during_regexp_ < kRequiredGCs) {
{
v8::Locker lock;
// TODO(lrn): Perhaps create some garbage before collecting.
i::Heap::CollectAllGarbage();
gc_count_++;
}
i::OS::Sleep(1);
}
gc_success_ = true;
}
void LongRunningRegExp() {
block_->Signal(); // Enable garbage collection thread on next preemption.
int rounds = 0;
while (gc_during_regexp_ < kRequiredGCs) {
int gc_before = gc_count_;
{
// Match 15-30 "a"'s against 14 and a "b".
const char* c_source =
"/a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaa/"
".exec('aaaaaaaaaaaaaaab') === null";
Local<String> source = String::New(c_source);
Local<Script> script = Script::Compile(source);
Local<Value> result = script->Run();
if (!result->BooleanValue()) {
gc_during_regexp_ = kRequiredGCs; // Allow gc thread to exit.
return;
}
}
{
// Match 15-30 "a"'s against 15 and a "b".
const char* c_source =
"/a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaa/"
".exec('aaaaaaaaaaaaaaaab')[0] === 'aaaaaaaaaaaaaaaa'";
Local<String> source = String::New(c_source);
Local<Script> script = Script::Compile(source);
Local<Value> result = script->Run();
if (!result->BooleanValue()) {
gc_during_regexp_ = kRequiredGCs;
return;
}
}
int gc_after = gc_count_;
gc_during_regexp_ += gc_after - gc_before;
rounds++;
i::OS::Sleep(1);
}
regexp_success_ = true;
}
i::Semaphore* block_;
int gc_count_;
int gc_during_regexp_;
bool regexp_success_;
bool gc_success_;
};
// Test that a regular expression execution can be interrupted and
// survive a garbage collection.
TEST(RegExpInterruption) {
v8::Locker lock;
v8::V8::Initialize();
v8::HandleScope scope;
Local<Context> local_env;
{
LocalContext env;
local_env = env.local();
}
// Local context should still be live.
CHECK(!local_env.IsEmpty());
local_env->Enter();
// Should complete without problems.
RegExpInterruptTest().RunTest();
local_env->Exit();
}
class ApplyInterruptTest {
public:
ApplyInterruptTest() : block_(NULL) {}
~ApplyInterruptTest() { delete block_; }
void RunTest() {
block_ = i::OS::CreateSemaphore(0);
gc_count_ = 0;
gc_during_apply_ = 0;
apply_success_ = false;
gc_success_ = false;
GCThread gc_thread(this);
gc_thread.Start();
v8::Locker::StartPreemption(1);
LongRunningApply();
{
v8::Unlocker unlock;
gc_thread.Join();
}
v8::Locker::StopPreemption();
CHECK(apply_success_);
CHECK(gc_success_);
}
private:
// Number of garbage collections required.
static const int kRequiredGCs = 2;
class GCThread : public i::Thread {
public:
explicit GCThread(ApplyInterruptTest* test)
: test_(test) {}
virtual void Run() {
test_->CollectGarbage();
}
private:
ApplyInterruptTest* test_;
};
void CollectGarbage() {
block_->Wait();
while (gc_during_apply_ < kRequiredGCs) {
{
v8::Locker lock;
i::Heap::CollectAllGarbage();
gc_count_++;
}
i::OS::Sleep(1);
}
gc_success_ = true;
}
void LongRunningApply() {
block_->Signal();
int rounds = 0;
while (gc_during_apply_ < kRequiredGCs) {
int gc_before = gc_count_;
{
const char* c_source =
"function do_very_little(bar) {"
" this.foo = bar;"
"}"
"for (var i = 0; i < 100000; i++) {"
" do_very_little.apply(this, ['bar']);"
"}";
Local<String> source = String::New(c_source);
Local<Script> script = Script::Compile(source);
Local<Value> result = script->Run();
// Check that no exception was thrown.
CHECK(!result.IsEmpty());
}
int gc_after = gc_count_;
gc_during_apply_ += gc_after - gc_before;
rounds++;
}
apply_success_ = true;
}
i::Semaphore* block_;
int gc_count_;
int gc_during_apply_;
bool apply_success_;
bool gc_success_;
};
// Test that nothing bad happens if we get a preemption just when we were
// about to do an apply().
TEST(ApplyInterruption) {
v8::Locker lock;
v8::V8::Initialize();
v8::HandleScope scope;
Local<Context> local_env;
{
LocalContext env;
local_env = env.local();
}
// Local context should still be live.
CHECK(!local_env.IsEmpty());
local_env->Enter();
// Should complete without problems.
ApplyInterruptTest().RunTest();
local_env->Exit();
}
// Verify that we can clone an object
TEST(ObjectClone) {
v8::HandleScope scope;
LocalContext env;
const char* sample =
"var rv = {};" \
"rv.alpha = 'hello';" \
"rv.beta = 123;" \
"rv;";
// Create an object, verify basics.
Local<Value> val = CompileRun(sample);
CHECK(val->IsObject());
Local<v8::Object> obj = Local<v8::Object>::Cast(val);
obj->Set(v8_str("gamma"), v8_str("cloneme"));
CHECK_EQ(v8_str("hello"), obj->Get(v8_str("alpha")));
CHECK_EQ(v8::Integer::New(123), obj->Get(v8_str("beta")));
CHECK_EQ(v8_str("cloneme"), obj->Get(v8_str("gamma")));
// Clone it.
Local<v8::Object> clone = obj->Clone();
CHECK_EQ(v8_str("hello"), clone->Get(v8_str("alpha")));
CHECK_EQ(v8::Integer::New(123), clone->Get(v8_str("beta")));
CHECK_EQ(v8_str("cloneme"), clone->Get(v8_str("gamma")));
// Set a property on the clone, verify each object.
clone->Set(v8_str("beta"), v8::Integer::New(456));
CHECK_EQ(v8::Integer::New(123), obj->Get(v8_str("beta")));
CHECK_EQ(v8::Integer::New(456), clone->Get(v8_str("beta")));
}
class AsciiVectorResource : public v8::String::ExternalAsciiStringResource {
public:
explicit AsciiVectorResource(i::Vector<const char> vector)
: data_(vector) {}
virtual ~AsciiVectorResource() {}
virtual size_t length() const { return data_.length(); }
virtual const char* data() const { return data_.start(); }
private:
i::Vector<const char> data_;
};
class UC16VectorResource : public v8::String::ExternalStringResource {
public:
explicit UC16VectorResource(i::Vector<const i::uc16> vector)
: data_(vector) {}
virtual ~UC16VectorResource() {}
virtual size_t length() const { return data_.length(); }
virtual const i::uc16* data() const { return data_.start(); }
private:
i::Vector<const i::uc16> data_;
};
static void MorphAString(i::String* string,
AsciiVectorResource* ascii_resource,
UC16VectorResource* uc16_resource) {
CHECK(i::StringShape(string).IsExternal());
if (string->IsAsciiRepresentation()) {
// Check old map is not symbol or long.
CHECK(string->map() == i::Heap::short_external_ascii_string_map() ||
string->map() == i::Heap::medium_external_ascii_string_map());
// Morph external string to be TwoByte string.
if (string->length() <= i::String::kMaxShortStringSize) {
string->set_map(i::Heap::short_external_string_map());
} else {
string->set_map(i::Heap::medium_external_string_map());
}
i::ExternalTwoByteString* morphed =
i::ExternalTwoByteString::cast(string);
morphed->set_resource(uc16_resource);
} else {
// Check old map is not symbol or long.
CHECK(string->map() == i::Heap::short_external_string_map() ||
string->map() == i::Heap::medium_external_string_map());
// Morph external string to be ASCII string.
if (string->length() <= i::String::kMaxShortStringSize) {
string->set_map(i::Heap::short_external_ascii_string_map());
} else {
string->set_map(i::Heap::medium_external_ascii_string_map());
}
i::ExternalAsciiString* morphed =
i::ExternalAsciiString::cast(string);
morphed->set_resource(ascii_resource);
}
}
// Test that we can still flatten a string if the components it is built up
// from have been turned into 16 bit strings in the mean time.
THREADED_TEST(MorphCompositeStringTest) {
const char* c_string = "Now is the time for all good men"
" to come to the aid of the party";
uint16_t* two_byte_string = AsciiToTwoByteString(c_string);
{
v8::HandleScope scope;
LocalContext env;
AsciiVectorResource ascii_resource(
i::Vector<const char>(c_string, strlen(c_string)));
UC16VectorResource uc16_resource(
i::Vector<const uint16_t>(two_byte_string, strlen(c_string)));
Local<String> lhs(v8::Utils::ToLocal(
i::Factory::NewExternalStringFromAscii(&ascii_resource)));
Local<String> rhs(v8::Utils::ToLocal(
i::Factory::NewExternalStringFromAscii(&ascii_resource)));
env->Global()->Set(v8_str("lhs"), lhs);
env->Global()->Set(v8_str("rhs"), rhs);
CompileRun(
"var cons = lhs + rhs;"
"var slice = lhs.substring(1, lhs.length - 1);"
"var slice_on_cons = (lhs + rhs).substring(1, lhs.length *2 - 1);");
MorphAString(*v8::Utils::OpenHandle(*lhs), &ascii_resource, &uc16_resource);
MorphAString(*v8::Utils::OpenHandle(*rhs), &ascii_resource, &uc16_resource);
// Now do some stuff to make sure the strings are flattened, etc.
CompileRun(
"/[^a-z]/.test(cons);"
"/[^a-z]/.test(slice);"
"/[^a-z]/.test(slice_on_cons);");
const char* expected_cons =
"Now is the time for all good men to come to the aid of the party"
"Now is the time for all good men to come to the aid of the party";
const char* expected_slice =
"ow is the time for all good men to come to the aid of the part";
const char* expected_slice_on_cons =
"ow is the time for all good men to come to the aid of the party"
"Now is the time for all good men to come to the aid of the part";
CHECK_EQ(String::New(expected_cons),
env->Global()->Get(v8_str("cons")));
CHECK_EQ(String::New(expected_slice),
env->Global()->Get(v8_str("slice")));
CHECK_EQ(String::New(expected_slice_on_cons),
env->Global()->Get(v8_str("slice_on_cons")));
}
}
class RegExpStringModificationTest {
public:
RegExpStringModificationTest()
: block_(i::OS::CreateSemaphore(0)),
morphs_(0),
morphs_during_regexp_(0),
ascii_resource_(i::Vector<const char>("aaaaaaaaaaaaaab", 15)),
uc16_resource_(i::Vector<const uint16_t>(two_byte_content_, 15)) {}
~RegExpStringModificationTest() { delete block_; }
void RunTest() {
regexp_success_ = false;
morph_success_ = false;
// Initialize the contents of two_byte_content_ to be a uc16 representation
// of "aaaaaaaaaaaaaab".
for (int i = 0; i < 14; i++) {
two_byte_content_[i] = 'a';
}
two_byte_content_[14] = 'b';
// Create the input string for the regexp - the one we are going to change
// properties of.
input_ = i::Factory::NewExternalStringFromAscii(&ascii_resource_);
// Inject the input as a global variable.
i::Handle<i::String> input_name =
i::Factory::NewStringFromAscii(i::Vector<const char>("input", 5));
i::Top::global_context()->global()->SetProperty(*input_name, *input_, NONE);
MorphThread morph_thread(this);
morph_thread.Start();
v8::Locker::StartPreemption(1);
LongRunningRegExp();
{
v8::Unlocker unlock;
morph_thread.Join();
}
v8::Locker::StopPreemption();
CHECK(regexp_success_);
CHECK(morph_success_);
}
private:
// Number of string modifications required.
static const int kRequiredModifications = 5;
static const int kMaxModifications = 100;
class MorphThread : public i::Thread {
public:
explicit MorphThread(RegExpStringModificationTest* test)
: test_(test) {}
virtual void Run() {
test_->MorphString();
}
private:
RegExpStringModificationTest* test_;
};
void MorphString() {
block_->Wait();
while (morphs_during_regexp_ < kRequiredModifications &&
morphs_ < kMaxModifications) {
{
v8::Locker lock;
// Swap string between ascii and two-byte representation.
i::String* string = *input_;
MorphAString(string, &ascii_resource_, &uc16_resource_);
morphs_++;
}
i::OS::Sleep(1);
}
morph_success_ = true;
}
void LongRunningRegExp() {
block_->Signal(); // Enable morphing thread on next preemption.
while (morphs_during_regexp_ < kRequiredModifications &&
morphs_ < kMaxModifications) {
int morphs_before = morphs_;
{
// Match 15-30 "a"'s against 14 and a "b".
const char* c_source =
"/a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaa/"
".exec(input) === null";
Local<String> source = String::New(c_source);
Local<Script> script = Script::Compile(source);
Local<Value> result = script->Run();
CHECK(result->IsTrue());
}
int morphs_after = morphs_;
morphs_during_regexp_ += morphs_after - morphs_before;
}
regexp_success_ = true;
}
i::uc16 two_byte_content_[15];
i::Semaphore* block_;
int morphs_;
int morphs_during_regexp_;
bool regexp_success_;
bool morph_success_;
i::Handle<i::String> input_;
AsciiVectorResource ascii_resource_;
UC16VectorResource uc16_resource_;
};
// Test that a regular expression execution can be interrupted and
// the string changed without failing.
TEST(RegExpStringModification) {
v8::Locker lock;
v8::V8::Initialize();
v8::HandleScope scope;
Local<Context> local_env;
{
LocalContext env;
local_env = env.local();
}
// Local context should still be live.
CHECK(!local_env.IsEmpty());
local_env->Enter();
// Should complete without problems.
RegExpStringModificationTest().RunTest();
local_env->Exit();
}
// Test that we can set a property on the global object even if there
// is a read-only property in the prototype chain.
TEST(ReadOnlyPropertyInGlobalProto) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = v8::ObjectTemplate::New();
LocalContext context(0, templ);
v8::Handle<v8::Object> global = context->Global();
v8::Handle<v8::Object> global_proto =
v8::Handle<v8::Object>::Cast(global->Get(v8_str("__proto__")));
global_proto->Set(v8_str("x"), v8::Integer::New(0), v8::ReadOnly);
global_proto->Set(v8_str("y"), v8::Integer::New(0), v8::ReadOnly);
// Check without 'eval' or 'with'.
v8::Handle<v8::Value> res =
CompileRun("function f() { x = 42; return x; }; f()");
// Check with 'eval'.
res = CompileRun("function f() { eval('1'); y = 42; return y; }; f()");
CHECK_EQ(v8::Integer::New(42), res);
// Check with 'with'.
res = CompileRun("function f() { with (this) { y = 42 }; return y; }; f()");
CHECK_EQ(v8::Integer::New(42), res);
}
static int force_set_set_count = 0;
static int force_set_get_count = 0;
bool pass_on_get = false;
static v8::Handle<v8::Value> ForceSetGetter(v8::Local<v8::String> name,
const v8::AccessorInfo& info) {
force_set_get_count++;
if (pass_on_get) {
return v8::Handle<v8::Value>();
} else {
return v8::Int32::New(3);
}
}
static void ForceSetSetter(v8::Local<v8::String> name,
v8::Local<v8::Value> value,
const v8::AccessorInfo& info) {
force_set_set_count++;
}
static v8::Handle<v8::Value> ForceSetInterceptSetter(
v8::Local<v8::String> name,
v8::Local<v8::Value> value,
const v8::AccessorInfo& info) {
force_set_set_count++;
return v8::Undefined();
}
TEST(ForceSet) {
force_set_get_count = 0;
force_set_set_count = 0;
pass_on_get = false;
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = v8::ObjectTemplate::New();
v8::Handle<v8::String> access_property = v8::String::New("a");
templ->SetAccessor(access_property, ForceSetGetter, ForceSetSetter);
LocalContext context(NULL, templ);
v8::Handle<v8::Object> global = context->Global();
// Ordinary properties
v8::Handle<v8::String> simple_property = v8::String::New("p");
global->Set(simple_property, v8::Int32::New(4), v8::ReadOnly);
CHECK_EQ(4, global->Get(simple_property)->Int32Value());
// This should fail because the property is read-only
global->Set(simple_property, v8::Int32::New(5));
CHECK_EQ(4, global->Get(simple_property)->Int32Value());
// This should succeed even though the property is read-only
global->ForceSet(simple_property, v8::Int32::New(6));
CHECK_EQ(6, global->Get(simple_property)->Int32Value());
// Accessors
CHECK_EQ(0, force_set_set_count);
CHECK_EQ(0, force_set_get_count);
CHECK_EQ(3, global->Get(access_property)->Int32Value());
// CHECK_EQ the property shouldn't override it, just call the setter
// which in this case does nothing.
global->Set(access_property, v8::Int32::New(7));
CHECK_EQ(3, global->Get(access_property)->Int32Value());
CHECK_EQ(1, force_set_set_count);
CHECK_EQ(2, force_set_get_count);
// Forcing the property to be set should override the accessor without
// calling it
global->ForceSet(access_property, v8::Int32::New(8));
CHECK_EQ(8, global->Get(access_property)->Int32Value());
CHECK_EQ(1, force_set_set_count);
CHECK_EQ(2, force_set_get_count);
}
TEST(ForceSetWithInterceptor) {
force_set_get_count = 0;
force_set_set_count = 0;
pass_on_get = false;
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = v8::ObjectTemplate::New();
templ->SetNamedPropertyHandler(ForceSetGetter, ForceSetInterceptSetter);
LocalContext context(NULL, templ);
v8::Handle<v8::Object> global = context->Global();
v8::Handle<v8::String> some_property = v8::String::New("a");
CHECK_EQ(0, force_set_set_count);
CHECK_EQ(0, force_set_get_count);
CHECK_EQ(3, global->Get(some_property)->Int32Value());
// Setting the property shouldn't override it, just call the setter
// which in this case does nothing.
global->Set(some_property, v8::Int32::New(7));
CHECK_EQ(3, global->Get(some_property)->Int32Value());
CHECK_EQ(1, force_set_set_count);
CHECK_EQ(2, force_set_get_count);
// Getting the property when the interceptor returns an empty handle
// should yield undefined, since the property isn't present on the
// object itself yet.
pass_on_get = true;
CHECK(global->Get(some_property)->IsUndefined());
CHECK_EQ(1, force_set_set_count);
CHECK_EQ(3, force_set_get_count);
// Forcing the property to be set should cause the value to be
// set locally without calling the interceptor.
global->ForceSet(some_property, v8::Int32::New(8));
CHECK_EQ(8, global->Get(some_property)->Int32Value());
CHECK_EQ(1, force_set_set_count);
CHECK_EQ(4, force_set_get_count);
// Reenabling the interceptor should cause it to take precedence over
// the property
pass_on_get = false;
CHECK_EQ(3, global->Get(some_property)->Int32Value());
CHECK_EQ(1, force_set_set_count);
CHECK_EQ(5, force_set_get_count);
// The interceptor should also work for other properties
CHECK_EQ(3, global->Get(v8::String::New("b"))->Int32Value());
CHECK_EQ(1, force_set_set_count);
CHECK_EQ(6, force_set_get_count);
}