v8/test/cctest/test-api.cc

18165 lines
595 KiB
C++
Raw Normal View History

// Copyright 2012 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 <limits.h>
#ifndef WIN32
#include <signal.h> // kill
#include <unistd.h> // getpid
#endif // WIN32
#include "v8.h"
#include "api.h"
#include "isolate.h"
#include "compilation-cache.h"
#include "execution.h"
#include "objects.h"
#include "snapshot.h"
#include "platform.h"
#include "utils.h"
#include "cctest.h"
#include "parser.h"
#include "unicode-inl.h"
static const bool kLogThreading = false;
static bool IsNaN(double x) {
#ifdef WIN32
return _isnan(x);
#else
return isnan(x);
#endif
}
using ::v8::AccessorInfo;
using ::v8::Arguments;
using ::v8::Context;
using ::v8::Extension;
using ::v8::Function;
using ::v8::FunctionTemplate;
using ::v8::Handle;
using ::v8::HandleScope;
using ::v8::Local;
using ::v8::Message;
using ::v8::MessageCallback;
using ::v8::Object;
using ::v8::ObjectTemplate;
using ::v8::Persistent;
using ::v8::Script;
using ::v8::StackTrace;
using ::v8::String;
using ::v8::TryCatch;
using ::v8::Undefined;
using ::v8::V8;
using ::v8::Value;
static void ExpectString(const char* code, const char* expected) {
Local<Value> result = CompileRun(code);
CHECK(result->IsString());
String::AsciiValue ascii(result);
CHECK_EQ(expected, *ascii);
}
static void ExpectInt32(const char* code, int expected) {
Local<Value> result = CompileRun(code);
CHECK(result->IsInt32());
CHECK_EQ(expected, result->Int32Value());
}
static void ExpectBoolean(const char* code, bool expected) {
Local<Value> result = CompileRun(code);
CHECK(result->IsBoolean());
CHECK_EQ(expected, result->BooleanValue());
}
static void ExpectTrue(const char* code) {
ExpectBoolean(code, true);
}
static void ExpectFalse(const char* code) {
ExpectBoolean(code, false);
}
static void ExpectObject(const char* code, Local<Value> expected) {
Local<Value> result = CompileRun(code);
CHECK(result->Equals(expected));
}
static void ExpectUndefined(const char* code) {
Local<Value> result = CompileRun(code);
CHECK(result->IsUndefined());
}
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();
}
THREADED_TEST(IsolateOfContext) {
v8::HandleScope scope;
v8::Persistent<Context> env = Context::New();
CHECK(!env->InContext());
CHECK(env->GetIsolate() == v8::Isolate::GetCurrent());
env->Enter();
CHECK(env->InContext());
CHECK(env->GetIsolate() == v8::Isolate::GetCurrent());
env->Exit();
CHECK(!env->InContext());
CHECK(env->GetIsolate() == v8::Isolate::GetCurrent());
env.Dispose();
}
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(AccessElement) {
v8::HandleScope scope;
LocalContext env;
Local<v8::Object> obj = v8::Object::New();
Local<Value> before = obj->Get(1);
CHECK(before->IsUndefined());
Local<String> bar_str = v8_str("bar");
obj->Set(1, bar_str);
Local<Value> after = obj->Get(1);
CHECK(!after->IsUndefined());
CHECK(after->IsString());
CHECK_EQ(bar_str, after);
Local<v8::Array> value = CompileRun("[\"a\", \"b\"]").As<v8::Array>();
CHECK_EQ(v8_str("a"), value->Get(0));
CHECK_EQ(v8_str("b"), value->Get(1));
}
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) {
int array_length = i::StrLength(source) + 1;
uint16_t* converted = i::NewArray<uint16_t>(array_length);
for (int i = 0; i < array_length; i++) converted[i] = source[i];
return converted;
}
class TestResource: public String::ExternalStringResource {
public:
explicit TestResource(uint16_t* data, int* counter = NULL)
: data_(data), length_(0), counter_(counter) {
while (data[length_]) ++length_;
}
~TestResource() {
i::DeleteArray(data_);
if (counter_ != NULL) ++*counter_;
}
const uint16_t* data() const {
return data_;
}
size_t length() const {
return length_;
}
private:
uint16_t* data_;
size_t length_;
int* counter_;
};
class TestAsciiResource: public String::ExternalAsciiStringResource {
public:
explicit TestAsciiResource(const char* data, int* counter = NULL)
: data_(data), length_(strlen(data)), counter_(counter) { }
~TestAsciiResource() {
i::DeleteArray(data_);
if (counter_ != NULL) ++*counter_;
}
const char* data() const {
return data_;
}
size_t length() const {
return length_;
}
private:
const char* data_;
size_t length_;
int* counter_;
};
THREADED_TEST(ScriptUsingStringResource) {
int 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, &dispose_count);
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()));
String::Encoding encoding = String::UNKNOWN_ENCODING;
CHECK_EQ(static_cast<const String::ExternalStringResourceBase*>(resource),
source->GetExternalStringResourceBase(&encoding));
CHECK_EQ(String::TWO_BYTE_ENCODING, encoding);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK_EQ(0, dispose_count);
}
v8::internal::Isolate::Current()->compilation_cache()->Clear();
HEAP->CollectAllAvailableGarbage();
CHECK_EQ(1, dispose_count);
}
THREADED_TEST(ScriptUsingAsciiStringResource) {
int dispose_count = 0;
const char* c_source = "1 + 2 * 3";
{
v8::HandleScope scope;
LocalContext env;
TestAsciiResource* resource = new TestAsciiResource(i::StrDup(c_source),
&dispose_count);
Local<String> source = String::NewExternal(resource);
CHECK(source->IsExternalAscii());
CHECK_EQ(static_cast<const String::ExternalStringResourceBase*>(resource),
source->GetExternalAsciiStringResource());
String::Encoding encoding = String::UNKNOWN_ENCODING;
CHECK_EQ(static_cast<const String::ExternalStringResourceBase*>(resource),
source->GetExternalStringResourceBase(&encoding));
CHECK_EQ(String::ASCII_ENCODING, encoding);
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(7, value->Int32Value());
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK_EQ(0, dispose_count);
}
i::Isolate::Current()->compilation_cache()->Clear();
HEAP->CollectAllAvailableGarbage();
CHECK_EQ(1, dispose_count);
}
THREADED_TEST(ScriptMakingExternalString) {
int 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);
// Trigger GCs so that the newly allocated string moves to old gen.
HEAP->CollectGarbage(i::NEW_SPACE); // in survivor space now
HEAP->CollectGarbage(i::NEW_SPACE); // in old gen now
CHECK_EQ(source->IsExternal(), false);
CHECK_EQ(source->IsExternalAscii(), false);
String::Encoding encoding = String::UNKNOWN_ENCODING;
CHECK_EQ(NULL, source->GetExternalStringResourceBase(&encoding));
CHECK_EQ(String::ASCII_ENCODING, encoding);
bool success = source->MakeExternal(new TestResource(two_byte_source,
&dispose_count));
CHECK(success);
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(7, value->Int32Value());
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK_EQ(0, dispose_count);
}
i::Isolate::Current()->compilation_cache()->Clear();
HEAP->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
CHECK_EQ(1, dispose_count);
}
THREADED_TEST(ScriptMakingExternalAsciiString) {
int dispose_count = 0;
const char* c_source = "1 + 2 * 3";
{
v8::HandleScope scope;
LocalContext env;
Local<String> source = v8_str(c_source);
// Trigger GCs so that the newly allocated string moves to old gen.
HEAP->CollectGarbage(i::NEW_SPACE); // in survivor space now
HEAP->CollectGarbage(i::NEW_SPACE); // in old gen now
bool success = source->MakeExternal(
new TestAsciiResource(i::StrDup(c_source), &dispose_count));
CHECK(success);
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(7, value->Int32Value());
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK_EQ(0, dispose_count);
}
i::Isolate::Current()->compilation_cache()->Clear();
HEAP->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
CHECK_EQ(1, dispose_count);
}
TEST(MakingExternalStringConditions) {
v8::HandleScope scope;
LocalContext env;
// Free some space in the new space so that we can check freshness.
HEAP->CollectGarbage(i::NEW_SPACE);
HEAP->CollectGarbage(i::NEW_SPACE);
uint16_t* two_byte_string = AsciiToTwoByteString("s1");
Local<String> small_string = String::New(two_byte_string);
i::DeleteArray(two_byte_string);
// We should refuse to externalize newly created small string.
CHECK(!small_string->CanMakeExternal());
// Trigger GCs so that the newly allocated string moves to old gen.
HEAP->CollectGarbage(i::NEW_SPACE); // in survivor space now
HEAP->CollectGarbage(i::NEW_SPACE); // in old gen now
// Old space strings should be accepted.
CHECK(small_string->CanMakeExternal());
two_byte_string = AsciiToTwoByteString("small string 2");
small_string = String::New(two_byte_string);
i::DeleteArray(two_byte_string);
// We should refuse externalizing newly created small string.
CHECK(!small_string->CanMakeExternal());
for (int i = 0; i < 100; i++) {
String::Value value(small_string);
}
// Frequently used strings should be accepted.
CHECK(small_string->CanMakeExternal());
const int buf_size = 10 * 1024;
char* buf = i::NewArray<char>(buf_size);
memset(buf, 'a', buf_size);
buf[buf_size - 1] = '\0';
two_byte_string = AsciiToTwoByteString(buf);
Local<String> large_string = String::New(two_byte_string);
i::DeleteArray(buf);
i::DeleteArray(two_byte_string);
// Large strings should be immediately accepted.
CHECK(large_string->CanMakeExternal());
}
TEST(MakingExternalAsciiStringConditions) {
v8::HandleScope scope;
LocalContext env;
// Free some space in the new space so that we can check freshness.
HEAP->CollectGarbage(i::NEW_SPACE);
HEAP->CollectGarbage(i::NEW_SPACE);
Local<String> small_string = String::New("s1");
// We should refuse to externalize newly created small string.
CHECK(!small_string->CanMakeExternal());
// Trigger GCs so that the newly allocated string moves to old gen.
HEAP->CollectGarbage(i::NEW_SPACE); // in survivor space now
HEAP->CollectGarbage(i::NEW_SPACE); // in old gen now
// Old space strings should be accepted.
CHECK(small_string->CanMakeExternal());
small_string = String::New("small string 2");
// We should refuse externalizing newly created small string.
CHECK(!small_string->CanMakeExternal());
for (int i = 0; i < 100; i++) {
String::Value value(small_string);
}
// Frequently used strings should be accepted.
CHECK(small_string->CanMakeExternal());
const int buf_size = 10 * 1024;
char* buf = i::NewArray<char>(buf_size);
memset(buf, 'a', buf_size);
buf[buf_size - 1] = '\0';
Local<String> large_string = String::New(buf);
i::DeleteArray(buf);
// Large strings should be immediately accepted.
CHECK(large_string->CanMakeExternal());
}
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.
HEAP->CollectGarbage(i::NEW_SPACE); // in survivor space now
HEAP->CollectGarbage(i::NEW_SPACE); // in old gen now
i::Handle<i::String> isymbol = FACTORY->SymbolFromString(istring);
CHECK(isymbol->IsSymbol());
}
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
}
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.
HEAP->CollectGarbage(i::NEW_SPACE); // in survivor space now
HEAP->CollectGarbage(i::NEW_SPACE); // in old gen now
i::Handle<i::String> isymbol = FACTORY->SymbolFromString(istring);
CHECK(isymbol->IsSymbol());
}
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
}
THREADED_TEST(ScavengeExternalString) {
i::FLAG_stress_compaction = false;
i::FLAG_gc_global = false;
int dispose_count = 0;
bool in_new_space = false;
{
v8::HandleScope scope;
uint16_t* two_byte_string = AsciiToTwoByteString("test string");
Local<String> string =
String::NewExternal(new TestResource(two_byte_string,
&dispose_count));
i::Handle<i::String> istring = v8::Utils::OpenHandle(*string);
HEAP->CollectGarbage(i::NEW_SPACE);
in_new_space = HEAP->InNewSpace(*istring);
CHECK(in_new_space || HEAP->old_data_space()->Contains(*istring));
CHECK_EQ(0, dispose_count);
}
HEAP->CollectGarbage(in_new_space ? i::NEW_SPACE : i::OLD_DATA_SPACE);
CHECK_EQ(1, dispose_count);
}
THREADED_TEST(ScavengeExternalAsciiString) {
i::FLAG_stress_compaction = false;
i::FLAG_gc_global = false;
int dispose_count = 0;
bool in_new_space = false;
{
v8::HandleScope scope;
const char* one_byte_string = "test string";
Local<String> string = String::NewExternal(
new TestAsciiResource(i::StrDup(one_byte_string), &dispose_count));
i::Handle<i::String> istring = v8::Utils::OpenHandle(*string);
HEAP->CollectGarbage(i::NEW_SPACE);
in_new_space = HEAP->InNewSpace(*istring);
CHECK(in_new_space || HEAP->old_data_space()->Contains(*istring));
CHECK_EQ(0, dispose_count);
}
HEAP->CollectGarbage(in_new_space ? i::NEW_SPACE : i::OLD_DATA_SPACE);
CHECK_EQ(1, dispose_count);
}
class TestAsciiResourceWithDisposeControl: public TestAsciiResource {
public:
// Only used by non-threaded tests, so it can use static fields.
static int dispose_calls;
static int dispose_count;
TestAsciiResourceWithDisposeControl(const char* data, bool dispose)
: TestAsciiResource(data, &dispose_count),
dispose_(dispose) { }
void Dispose() {
++dispose_calls;
if (dispose_) delete this;
}
private:
bool dispose_;
};
int TestAsciiResourceWithDisposeControl::dispose_count = 0;
int TestAsciiResourceWithDisposeControl::dispose_calls = 0;
TEST(ExternalStringWithDisposeHandling) {
const char* c_source = "1 + 2 * 3";
// Use a stack allocated external string resource allocated object.
TestAsciiResourceWithDisposeControl::dispose_count = 0;
TestAsciiResourceWithDisposeControl::dispose_calls = 0;
TestAsciiResourceWithDisposeControl res_stack(i::StrDup(c_source), false);
{
v8::HandleScope scope;
LocalContext env;
Local<String> source = String::NewExternal(&res_stack);
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(7, value->Int32Value());
HEAP->CollectAllAvailableGarbage();
CHECK_EQ(0, TestAsciiResourceWithDisposeControl::dispose_count);
}
i::Isolate::Current()->compilation_cache()->Clear();
HEAP->CollectAllAvailableGarbage();
CHECK_EQ(1, TestAsciiResourceWithDisposeControl::dispose_calls);
CHECK_EQ(0, TestAsciiResourceWithDisposeControl::dispose_count);
// Use a heap allocated external string resource allocated object.
TestAsciiResourceWithDisposeControl::dispose_count = 0;
TestAsciiResourceWithDisposeControl::dispose_calls = 0;
TestAsciiResource* res_heap =
new TestAsciiResourceWithDisposeControl(i::StrDup(c_source), true);
{
v8::HandleScope scope;
LocalContext env;
Local<String> source = String::NewExternal(res_heap);
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(7, value->Int32Value());
HEAP->CollectAllAvailableGarbage();
CHECK_EQ(0, TestAsciiResourceWithDisposeControl::dispose_count);
}
i::Isolate::Current()->compilation_cache()->Clear();
HEAP->CollectAllAvailableGarbage();
CHECK_EQ(1, TestAsciiResourceWithDisposeControl::dispose_calls);
CHECK_EQ(1, TestAsciiResourceWithDisposeControl::dispose_count);
}
THREADED_TEST(StringConcat) {
{
v8::HandleScope scope;
LocalContext env;
const char* one_byte_string_1 = "function a_times_t";
const char* two_byte_string_1 = "wo_plus_b(a, b) {return ";
const char* one_byte_extern_1 = "a * 2 + b;} a_times_two_plus_b(4, 8) + ";
const char* two_byte_extern_1 = "a_times_two_plus_b(4, 8) + ";
const char* one_byte_string_2 = "a_times_two_plus_b(4, 8) + ";
const char* two_byte_string_2 = "a_times_two_plus_b(4, 8) + ";
const char* two_byte_extern_2 = "a_times_two_plus_b(1, 2);";
Local<String> left = v8_str(one_byte_string_1);
uint16_t* two_byte_source = AsciiToTwoByteString(two_byte_string_1);
Local<String> right = String::New(two_byte_source);
i::DeleteArray(two_byte_source);
Local<String> source = String::Concat(left, right);
right = String::NewExternal(
new TestAsciiResource(i::StrDup(one_byte_extern_1)));
source = String::Concat(source, right);
right = String::NewExternal(
new TestResource(AsciiToTwoByteString(two_byte_extern_1)));
source = String::Concat(source, right);
right = v8_str(one_byte_string_2);
source = String::Concat(source, right);
two_byte_source = AsciiToTwoByteString(two_byte_string_2);
right = String::New(two_byte_source);
i::DeleteArray(two_byte_source);
source = String::Concat(source, right);
right = String::NewExternal(
new TestResource(AsciiToTwoByteString(two_byte_extern_2)));
source = String::Concat(source, right);
Local<Script> script = Script::Compile(source);
Local<Value> value = script->Run();
CHECK(value->IsNumber());
CHECK_EQ(68, value->Int32Value());
}
i::Isolate::Current()->compilation_cache()->Clear();
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
}
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();
}
static v8::Handle<Value> Return239(Local<String> name, const AccessorInfo&) {
ApiTestFuzzer::Fuzz();
return v8_num(239);
}
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"));
fun_templ->InstanceTemplate()->SetAccessor(v8_str("m"), Return239);
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);
result = v8_compile("(new obj()).m")->Run();
CHECK_EQ(239, result->Int32Value());
}
}
THREADED_TEST(FunctionTemplateSetLength) {
v8::HandleScope scope;
LocalContext env;
{
Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(
handle_call, Handle<v8::Value>(), Handle<v8::Signature>(), 23);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("obj"), fun);
Local<Script> script = v8_compile("obj.length");
CHECK_EQ(23, script->Run()->Int32Value());
}
{
Local<v8::FunctionTemplate> fun_templ =
v8::FunctionTemplate::New(handle_call);
fun_templ->SetLength(22);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("obj"), fun);
Local<Script> script = v8_compile("obj.length");
CHECK_EQ(22, script->Run()->Int32Value());
}
{
// Without setting length it defaults to 0.
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.length");
CHECK_EQ(0, script->Run()->Int32Value());
}
}
static void* expected_ptr;
static v8::Handle<v8::Value> callback(const v8::Arguments& args) {
void* ptr = v8::External::Cast(*args.Data())->Value();
CHECK_EQ(expected_ptr, ptr);
return v8::True();
}
static void TestExternalPointerWrapping() {
v8::HandleScope scope;
LocalContext env;
v8::Handle<v8::Value> data = v8::External::New(expected_ptr);
v8::Handle<v8::Object> obj = v8::Object::New();
obj->Set(v8_str("func"),
v8::FunctionTemplate::New(callback, data)->GetFunction());
env->Global()->Set(v8_str("obj"), obj);
CHECK(CompileRun(
"function foo() {\n"
" for (var i = 0; i < 13; i++) obj.func();\n"
"}\n"
"foo(), true")->BooleanValue());
}
THREADED_TEST(ExternalWrap) {
// Check heap allocated object.
int* ptr = new int;
expected_ptr = ptr;
TestExternalPointerWrapping();
delete ptr;
// Check stack allocated object.
int foo;
expected_ptr = &foo;
TestExternalPointerWrapping();
// Check not aligned addresses.
const int n = 100;
char* s = new char[n];
for (int i = 0; i < n; i++) {
expected_ptr = s + i;
TestExternalPointerWrapping();
}
delete[] s;
// Check several invalid addresses.
expected_ptr = reinterpret_cast<void*>(1);
TestExternalPointerWrapping();
expected_ptr = reinterpret_cast<void*>(0xdeadbeef);
TestExternalPointerWrapping();
expected_ptr = reinterpret_cast<void*>(0xdeadbeef + 1);
TestExternalPointerWrapping();
#if defined(V8_HOST_ARCH_X64)
// Check a value with a leading 1 bit in x64 Smi encoding.
expected_ptr = reinterpret_cast<void*>(0x400000000);
TestExternalPointerWrapping();
expected_ptr = reinterpret_cast<void*>(0xdeadbeefdeadbeef);
TestExternalPointerWrapping();
expected_ptr = reinterpret_cast<void*>(0xdeadbeefdeadbeef + 1);
TestExternalPointerWrapping();
#endif
}
THREADED_TEST(FindInstanceInPrototypeChain) {
v8::HandleScope scope;
LocalContext env;
Local<v8::FunctionTemplate> base = v8::FunctionTemplate::New();
Local<v8::FunctionTemplate> derived = v8::FunctionTemplate::New();
Local<v8::FunctionTemplate> other = v8::FunctionTemplate::New();
derived->Inherit(base);
Local<v8::Function> base_function = base->GetFunction();
Local<v8::Function> derived_function = derived->GetFunction();
Local<v8::Function> other_function = other->GetFunction();
Local<v8::Object> base_instance = base_function->NewInstance();
Local<v8::Object> derived_instance = derived_function->NewInstance();
Local<v8::Object> derived_instance2 = derived_function->NewInstance();
Local<v8::Object> other_instance = other_function->NewInstance();
derived_instance2->Set(v8_str("__proto__"), derived_instance);
other_instance->Set(v8_str("__proto__"), derived_instance2);
// base_instance is only an instance of base.
CHECK_EQ(base_instance,
base_instance->FindInstanceInPrototypeChain(base));
CHECK(base_instance->FindInstanceInPrototypeChain(derived).IsEmpty());
CHECK(base_instance->FindInstanceInPrototypeChain(other).IsEmpty());
// derived_instance is an instance of base and derived.
CHECK_EQ(derived_instance,
derived_instance->FindInstanceInPrototypeChain(base));
CHECK_EQ(derived_instance,
derived_instance->FindInstanceInPrototypeChain(derived));
CHECK(derived_instance->FindInstanceInPrototypeChain(other).IsEmpty());
// other_instance is an instance of other and its immediate
// prototype derived_instance2 is an instance of base and derived.
// Note, derived_instance is an instance of base and derived too,
// but it comes after derived_instance2 in the prototype chain of
// other_instance.
CHECK_EQ(derived_instance2,
other_instance->FindInstanceInPrototypeChain(base));
CHECK_EQ(derived_instance2,
other_instance->FindInstanceInPrototypeChain(derived));
CHECK_EQ(other_instance,
other_instance->FindInstanceInPrototypeChain(other));
}
THREADED_TEST(TinyInteger) {
v8::HandleScope scope;
LocalContext env;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
int32_t value = 239;
Local<v8::Integer> value_obj = v8::Integer::New(value);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
value_obj = v8::Integer::New(value, isolate);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
}
THREADED_TEST(BigSmiInteger) {
v8::HandleScope scope;
LocalContext env;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
int32_t value = i::Smi::kMaxValue;
// We cannot add one to a Smi::kMaxValue without wrapping.
if (i::kSmiValueSize < 32) {
CHECK(i::Smi::IsValid(value));
CHECK(!i::Smi::IsValid(value + 1));
Local<v8::Integer> value_obj = v8::Integer::New(value);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
value_obj = v8::Integer::New(value, isolate);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
}
}
THREADED_TEST(BigInteger) {
v8::HandleScope scope;
LocalContext env;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
// We cannot add one to a Smi::kMaxValue without wrapping.
if (i::kSmiValueSize < 32) {
// The casts allow this to compile, even if Smi::kMaxValue is 2^31-1.
// The code will not be run in that case, due to the "if" guard.
int32_t value =
static_cast<int32_t>(static_cast<uint32_t>(i::Smi::kMaxValue) + 1);
CHECK(value > i::Smi::kMaxValue);
CHECK(!i::Smi::IsValid(value));
Local<v8::Integer> value_obj = v8::Integer::New(value);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
value_obj = v8::Integer::New(value, isolate);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
}
}
THREADED_TEST(TinyUnsignedInteger) {
v8::HandleScope scope;
LocalContext env;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
uint32_t value = 239;
Local<v8::Integer> value_obj = v8::Integer::NewFromUnsigned(value);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
value_obj = v8::Integer::NewFromUnsigned(value, isolate);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
}
THREADED_TEST(BigUnsignedSmiInteger) {
v8::HandleScope scope;
LocalContext env;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
uint32_t value = static_cast<uint32_t>(i::Smi::kMaxValue);
CHECK(i::Smi::IsValid(value));
CHECK(!i::Smi::IsValid(value + 1));
Local<v8::Integer> value_obj = v8::Integer::NewFromUnsigned(value);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
value_obj = v8::Integer::NewFromUnsigned(value, isolate);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
}
THREADED_TEST(BigUnsignedInteger) {
v8::HandleScope scope;
LocalContext env;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
uint32_t value = static_cast<uint32_t>(i::Smi::kMaxValue) + 1;
CHECK(value > static_cast<uint32_t>(i::Smi::kMaxValue));
CHECK(!i::Smi::IsValid(value));
Local<v8::Integer> value_obj = v8::Integer::NewFromUnsigned(value);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
value_obj = v8::Integer::NewFromUnsigned(value, isolate);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
}
THREADED_TEST(OutOfSignedRangeUnsignedInteger) {
v8::HandleScope scope;
LocalContext env;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
uint32_t INT32_MAX_AS_UINT = (1U << 31) - 1;
uint32_t value = INT32_MAX_AS_UINT + 1;
CHECK(value > INT32_MAX_AS_UINT); // No overflow.
Local<v8::Integer> value_obj = v8::Integer::NewFromUnsigned(value);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
value_obj = v8::Integer::NewFromUnsigned(value, isolate);
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
}
THREADED_TEST(IsNativeError) {
v8::HandleScope scope;
LocalContext env;
v8::Handle<Value> syntax_error = CompileRun(
"var out = 0; try { eval(\"#\"); } catch(x) { out = x; } out; ");
CHECK(syntax_error->IsNativeError());
v8::Handle<Value> not_error = CompileRun("{a:42}");
CHECK(!not_error->IsNativeError());
v8::Handle<Value> not_object = CompileRun("42");
CHECK(!not_object->IsNativeError());
}
THREADED_TEST(StringObject) {
v8::HandleScope scope;
LocalContext env;
v8::Handle<Value> boxed_string = CompileRun("new String(\"test\")");
CHECK(boxed_string->IsStringObject());
v8::Handle<Value> unboxed_string = CompileRun("\"test\"");
CHECK(!unboxed_string->IsStringObject());
v8::Handle<Value> boxed_not_string = CompileRun("new Number(42)");
CHECK(!boxed_not_string->IsStringObject());
v8::Handle<Value> not_object = CompileRun("0");
CHECK(!not_object->IsStringObject());
v8::Handle<v8::StringObject> as_boxed = boxed_string.As<v8::StringObject>();
CHECK(!as_boxed.IsEmpty());
Local<v8::String> the_string = as_boxed->StringValue();
CHECK(!the_string.IsEmpty());
ExpectObject("\"test\"", the_string);
v8::Handle<v8::Value> new_boxed_string = v8::StringObject::New(the_string);
CHECK(new_boxed_string->IsStringObject());
as_boxed = new_boxed_string.As<v8::StringObject>();
the_string = as_boxed->StringValue();
CHECK(!the_string.IsEmpty());
ExpectObject("\"test\"", the_string);
}
THREADED_TEST(NumberObject) {
v8::HandleScope scope;
LocalContext env;
v8::Handle<Value> boxed_number = CompileRun("new Number(42)");
CHECK(boxed_number->IsNumberObject());
v8::Handle<Value> unboxed_number = CompileRun("42");
CHECK(!unboxed_number->IsNumberObject());
v8::Handle<Value> boxed_not_number = CompileRun("new Boolean(false)");
CHECK(!boxed_not_number->IsNumberObject());
v8::Handle<v8::NumberObject> as_boxed = boxed_number.As<v8::NumberObject>();
CHECK(!as_boxed.IsEmpty());
double the_number = as_boxed->NumberValue();
CHECK_EQ(42.0, the_number);
v8::Handle<v8::Value> new_boxed_number = v8::NumberObject::New(43);
CHECK(new_boxed_number->IsNumberObject());
as_boxed = new_boxed_number.As<v8::NumberObject>();
the_number = as_boxed->NumberValue();
CHECK_EQ(43.0, the_number);
}
THREADED_TEST(BooleanObject) {
v8::HandleScope scope;
LocalContext env;
v8::Handle<Value> boxed_boolean = CompileRun("new Boolean(true)");
CHECK(boxed_boolean->IsBooleanObject());
v8::Handle<Value> unboxed_boolean = CompileRun("true");
CHECK(!unboxed_boolean->IsBooleanObject());
v8::Handle<Value> boxed_not_boolean = CompileRun("new Number(42)");
CHECK(!boxed_not_boolean->IsBooleanObject());
v8::Handle<v8::BooleanObject> as_boxed =
boxed_boolean.As<v8::BooleanObject>();
CHECK(!as_boxed.IsEmpty());
bool the_boolean = as_boxed->BooleanValue();
CHECK_EQ(true, the_boolean);
v8::Handle<v8::Value> boxed_true = v8::BooleanObject::New(true);
v8::Handle<v8::Value> boxed_false = v8::BooleanObject::New(false);
CHECK(boxed_true->IsBooleanObject());
CHECK(boxed_false->IsBooleanObject());
as_boxed = boxed_true.As<v8::BooleanObject>();
CHECK_EQ(true, as_boxed->BooleanValue());
as_boxed = boxed_false.As<v8::BooleanObject>();
CHECK_EQ(false, as_boxed->BooleanValue());
}
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 = v8::Date::New(PI);
CHECK_EQ(3.0, date->NumberValue());
date.As<v8::Date>()->Set(v8_str("property"), v8::Integer::New(42));
CHECK_EQ(42, date.As<v8::Date>()->Get(v8_str("property"))->Int32Value());
}
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<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());
}
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;
}
// Helper functions for Interceptor/Accessor interaction tests
Handle<Value> SimpleAccessorGetter(Local<String> name,
const AccessorInfo& info) {
Handle<Object> self = info.This();
return self->Get(String::Concat(v8_str("accessor_"), name));
}
void SimpleAccessorSetter(Local<String> name, Local<Value> value,
const AccessorInfo& info) {
Handle<Object> self = info.This();
self->Set(String::Concat(v8_str("accessor_"), name), value);
}
Handle<Value> EmptyInterceptorGetter(Local<String> name,
const AccessorInfo& info) {
return Handle<Value>();
}
Handle<Value> EmptyInterceptorSetter(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
return Handle<Value>();
}
Handle<Value> InterceptorGetter(Local<String> name,
const AccessorInfo& info) {
// Intercept names that start with 'interceptor_'.
String::AsciiValue ascii(name);
char* name_str = *ascii;
char prefix[] = "interceptor_";
int i;
for (i = 0; name_str[i] && prefix[i]; ++i) {
if (name_str[i] != prefix[i]) return Handle<Value>();
}
Handle<Object> self = info.This();
return self->GetHiddenValue(v8_str(name_str + i));
}
Handle<Value> InterceptorSetter(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
// Intercept accesses that set certain integer values.
if (value->IsInt32() && value->Int32Value() < 10000) {
Handle<Object> self = info.This();
self->SetHiddenValue(name, value);
return value;
}
return Handle<Value>();
}
void AddAccessor(Handle<FunctionTemplate> templ,
Handle<String> name,
v8::AccessorGetter getter,
v8::AccessorSetter setter) {
templ->PrototypeTemplate()->SetAccessor(name, getter, setter);
}
void AddInterceptor(Handle<FunctionTemplate> templ,
v8::NamedPropertyGetter getter,
v8::NamedPropertySetter setter) {
templ->InstanceTemplate()->SetNamedPropertyHandler(getter, setter);
}
THREADED_TEST(EmptyInterceptorDoesNotShadowAccessors) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddAccessor(parent, v8_str("age"),
SimpleAccessorGetter, SimpleAccessorSetter);
AddInterceptor(child, EmptyInterceptorGetter, EmptyInterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"child.age = 10;");
ExpectBoolean("child.hasOwnProperty('age')", false);
ExpectInt32("child.age", 10);
ExpectInt32("child.accessor_age", 10);
}
THREADED_TEST(EmptyInterceptorDoesNotShadowJSAccessors) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddInterceptor(child, EmptyInterceptorGetter, EmptyInterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"var parent = child.__proto__;"
"Object.defineProperty(parent, 'age', "
" {get: function(){ return this.accessor_age; }, "
" set: function(v){ this.accessor_age = v; }, "
" enumerable: true, configurable: true});"
"child.age = 10;");
ExpectBoolean("child.hasOwnProperty('age')", false);
ExpectInt32("child.age", 10);
ExpectInt32("child.accessor_age", 10);
}
THREADED_TEST(EmptyInterceptorDoesNotAffectJSProperties) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddInterceptor(child, EmptyInterceptorGetter, EmptyInterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"var parent = child.__proto__;"
"parent.name = 'Alice';");
ExpectBoolean("child.hasOwnProperty('name')", false);
ExpectString("child.name", "Alice");
CompileRun("child.name = 'Bob';");
ExpectString("child.name", "Bob");
ExpectBoolean("child.hasOwnProperty('name')", true);
ExpectString("parent.name", "Alice");
}
THREADED_TEST(SwitchFromInterceptorToAccessor) {
v8::HandleScope scope;
Handle<FunctionTemplate> templ = FunctionTemplate::New();
AddAccessor(templ, v8_str("age"),
SimpleAccessorGetter, SimpleAccessorSetter);
AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Obj"), templ->GetFunction());
CompileRun("var obj = new Obj;"
"function setAge(i){ obj.age = i; };"
"for(var i = 0; i <= 10000; i++) setAge(i);");
// All i < 10000 go to the interceptor.
ExpectInt32("obj.interceptor_age", 9999);
// The last i goes to the accessor.
ExpectInt32("obj.accessor_age", 10000);
}
THREADED_TEST(SwitchFromAccessorToInterceptor) {
v8::HandleScope scope;
Handle<FunctionTemplate> templ = FunctionTemplate::New();
AddAccessor(templ, v8_str("age"),
SimpleAccessorGetter, SimpleAccessorSetter);
AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Obj"), templ->GetFunction());
CompileRun("var obj = new Obj;"
"function setAge(i){ obj.age = i; };"
"for(var i = 20000; i >= 9999; i--) setAge(i);");
// All i >= 10000 go to the accessor.
ExpectInt32("obj.accessor_age", 10000);
// The last i goes to the interceptor.
ExpectInt32("obj.interceptor_age", 9999);
}
THREADED_TEST(SwitchFromInterceptorToAccessorWithInheritance) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddAccessor(parent, v8_str("age"),
SimpleAccessorGetter, SimpleAccessorSetter);
AddInterceptor(child, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"function setAge(i){ child.age = i; };"
"for(var i = 0; i <= 10000; i++) setAge(i);");
// All i < 10000 go to the interceptor.
ExpectInt32("child.interceptor_age", 9999);
// The last i goes to the accessor.
ExpectInt32("child.accessor_age", 10000);
}
THREADED_TEST(SwitchFromAccessorToInterceptorWithInheritance) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddAccessor(parent, v8_str("age"),
SimpleAccessorGetter, SimpleAccessorSetter);
AddInterceptor(child, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"function setAge(i){ child.age = i; };"
"for(var i = 20000; i >= 9999; i--) setAge(i);");
// All i >= 10000 go to the accessor.
ExpectInt32("child.accessor_age", 10000);
// The last i goes to the interceptor.
ExpectInt32("child.interceptor_age", 9999);
}
THREADED_TEST(SwitchFromInterceptorToJSAccessor) {
v8::HandleScope scope;
Handle<FunctionTemplate> templ = FunctionTemplate::New();
AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Obj"), templ->GetFunction());
CompileRun("var obj = new Obj;"
"function setter(i) { this.accessor_age = i; };"
"function getter() { return this.accessor_age; };"
"function setAge(i) { obj.age = i; };"
"Object.defineProperty(obj, 'age', { get:getter, set:setter });"
"for(var i = 0; i <= 10000; i++) setAge(i);");
// All i < 10000 go to the interceptor.
ExpectInt32("obj.interceptor_age", 9999);
// The last i goes to the JavaScript accessor.
ExpectInt32("obj.accessor_age", 10000);
// The installed JavaScript getter is still intact.
// This last part is a regression test for issue 1651 and relies on the fact
// that both interceptor and accessor are being installed on the same object.
ExpectInt32("obj.age", 10000);
ExpectBoolean("obj.hasOwnProperty('age')", true);
ExpectUndefined("Object.getOwnPropertyDescriptor(obj, 'age').value");
}
THREADED_TEST(SwitchFromJSAccessorToInterceptor) {
v8::HandleScope scope;
Handle<FunctionTemplate> templ = FunctionTemplate::New();
AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Obj"), templ->GetFunction());
CompileRun("var obj = new Obj;"
"function setter(i) { this.accessor_age = i; };"
"function getter() { return this.accessor_age; };"
"function setAge(i) { obj.age = i; };"
"Object.defineProperty(obj, 'age', { get:getter, set:setter });"
"for(var i = 20000; i >= 9999; i--) setAge(i);");
// All i >= 10000 go to the accessor.
ExpectInt32("obj.accessor_age", 10000);
// The last i goes to the interceptor.
ExpectInt32("obj.interceptor_age", 9999);
// The installed JavaScript getter is still intact.
// This last part is a regression test for issue 1651 and relies on the fact
// that both interceptor and accessor are being installed on the same object.
ExpectInt32("obj.age", 10000);
ExpectBoolean("obj.hasOwnProperty('age')", true);
ExpectUndefined("Object.getOwnPropertyDescriptor(obj, 'age').value");
}
THREADED_TEST(SwitchFromInterceptorToProperty) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddInterceptor(child, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"function setAge(i){ child.age = i; };"
"for(var i = 0; i <= 10000; i++) setAge(i);");
// All i < 10000 go to the interceptor.
ExpectInt32("child.interceptor_age", 9999);
// The last i goes to child's own property.
ExpectInt32("child.age", 10000);
}
THREADED_TEST(SwitchFromPropertyToInterceptor) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddInterceptor(child, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"function setAge(i){ child.age = i; };"
"for(var i = 20000; i >= 9999; i--) setAge(i);");
// All i >= 10000 go to child's own property.
ExpectInt32("child.age", 10000);
// The last i goes to the interceptor.
ExpectInt32("child.interceptor_age", 9999);
}
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::Integer> CheckThisIndexedPropertyQuery(
uint32_t index,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<v8::Integer>();
}
v8::Handle<v8::Integer> CheckThisNamedPropertyQuery(Local<String> property,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.This()->Equals(bottom));
return v8::Handle<v8::Integer>();
}
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::Integer> PrePropertyHandlerQuery(Local<String> key,
const AccessorInfo&) {
if (v8_str("pre")->Equals(key)) {
return v8::Integer::New(v8::None);
}
return v8::Handle<v8::Integer>(); // do not intercept the call
}
THREADED_TEST(PrePropertyHandler) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> desc = v8::FunctionTemplate::New();
desc->InstanceTemplate()->SetNamedPropertyHandler(PrePropertyHandlerGet,
0,
PrePropertyHandlerQuery);
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 = 200; // 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 function.As<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()");
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);
}
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(GlobalObjectInternalFields) {
v8::HandleScope scope;
Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
global_template->SetInternalFieldCount(1);
LocalContext env(NULL, global_template);
v8::Handle<v8::Object> global_proxy = env->Global();
v8::Handle<v8::Object> global = global_proxy->GetPrototype().As<v8::Object>();
CHECK_EQ(1, global->InternalFieldCount());
CHECK(global->GetInternalField(0)->IsUndefined());
global->SetInternalField(0, v8_num(17));
CHECK_EQ(17, global->GetInternalField(0)->Int32Value());
}
static void CheckAlignedPointerInInternalField(Handle<v8::Object> obj,
void* value) {
CHECK_EQ(0, static_cast<int>(reinterpret_cast<uintptr_t>(value) & 0x1));
obj->SetAlignedPointerInInternalField(0, value);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK_EQ(value, obj->GetAlignedPointerFromInternalField(0));
}
THREADED_TEST(InternalFieldsAlignedPointers) {
v8::HandleScope scope;
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());
CheckAlignedPointerInInternalField(obj, NULL);
int* heap_allocated = new int[100];
CheckAlignedPointerInInternalField(obj, heap_allocated);
delete[] heap_allocated;
int stack_allocated[100];
CheckAlignedPointerInInternalField(obj, stack_allocated);
void* huge = reinterpret_cast<void*>(~static_cast<uintptr_t>(1));
CheckAlignedPointerInInternalField(obj, huge);
}
static void CheckAlignedPointerInEmbedderData(LocalContext* env,
int index,
void* value) {
CHECK_EQ(0, static_cast<int>(reinterpret_cast<uintptr_t>(value) & 0x1));
(*env)->SetAlignedPointerInEmbedderData(index, value);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK_EQ(value, (*env)->GetAlignedPointerFromEmbedderData(index));
}
static void* AlignedTestPointer(int i) {
return reinterpret_cast<void*>(i * 1234);
}
THREADED_TEST(EmbedderDataAlignedPointers) {
v8::HandleScope scope;
LocalContext env;
CheckAlignedPointerInEmbedderData(&env, 0, NULL);
int* heap_allocated = new int[100];
CheckAlignedPointerInEmbedderData(&env, 1, heap_allocated);
delete[] heap_allocated;
int stack_allocated[100];
CheckAlignedPointerInEmbedderData(&env, 2, stack_allocated);
void* huge = reinterpret_cast<void*>(~static_cast<uintptr_t>(1));
CheckAlignedPointerInEmbedderData(&env, 3, huge);
// Test growing of the embedder data's backing store.
for (int i = 0; i < 100; i++) {
env->SetAlignedPointerInEmbedderData(i, AlignedTestPointer(i));
}
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
for (int i = 0; i < 100; i++) {
CHECK_EQ(AlignedTestPointer(i), env->GetAlignedPointerFromEmbedderData(i));
}
}
static void CheckEmbedderData(LocalContext* env,
int index,
v8::Handle<Value> data) {
(*env)->SetEmbedderData(index, data);
CHECK((*env)->GetEmbedderData(index)->StrictEquals(data));
}
THREADED_TEST(EmbedderData) {
v8::HandleScope scope;
LocalContext env;
CheckEmbedderData(&env, 3, v8::String::New("The quick brown fox jumps"));
CheckEmbedderData(&env, 2, v8::String::New("over the lazy dog."));
CheckEmbedderData(&env, 1, v8::Number::New(1.2345));
CheckEmbedderData(&env, 0, v8::Boolean::New(true));
}
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.
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
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);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
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);
// Check identity hashes behaviour in the presence of JS accessors.
// Put a getter for 'v8::IdentityHash' on the Object's prototype:
{
CompileRun("Object.prototype['v8::IdentityHash'] = 42;\n");
Local<v8::Object> o1 = v8::Object::New();
Local<v8::Object> o2 = v8::Object::New();
CHECK_NE(o1->GetIdentityHash(), o2->GetIdentityHash());
}
{
CompileRun(
"function cnst() { return 42; };\n"
"Object.prototype.__defineGetter__('v8::IdentityHash', cnst);\n");
Local<v8::Object> o1 = v8::Object::New();
Local<v8::Object> o2 = v8::Object::New();
CHECK_NE(o1->GetIdentityHash(), o2->GetIdentityHash());
}
}
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");
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
// 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());
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
// 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());
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
// 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());
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK(obj->SetHiddenValue(key, Handle<Value>()));
CHECK(obj->GetHiddenValue(key).IsEmpty());
CHECK(obj->SetHiddenValue(key, v8::Integer::New(2002)));
CHECK(obj->DeleteHiddenValue(key));
CHECK(obj->GetHiddenValue(key).IsEmpty());
}
THREADED_TEST(Regress97784) {
// Regression test for crbug.com/97784
// Messing with the Object.prototype should not have effect on
// hidden properties.
v8::HandleScope scope;
LocalContext env;
v8::Local<v8::Object> obj = v8::Object::New();
v8::Local<v8::String> key = v8_str("hidden");
CompileRun(
"set_called = false;"
"Object.defineProperty("
" Object.prototype,"
" 'hidden',"
" {get: function() { return 45; },"
" set: function() { set_called = true; }})");
CHECK(obj->GetHiddenValue(key).IsEmpty());
// Make sure that the getter and setter from Object.prototype is not invoked.
// If it did we would have full access to the hidden properties in
// the accessor.
CHECK(obj->SetHiddenValue(key, v8::Integer::New(42)));
ExpectFalse("set_called");
CHECK_EQ(42, obj->GetHiddenValue(key)->Int32Value());
}
static bool interceptor_for_hidden_properties_called;
static v8::Handle<Value> InterceptorForHiddenProperties(
Local<String> name, const AccessorInfo& info) {
interceptor_for_hidden_properties_called = true;
return v8::Handle<Value>();
}
THREADED_TEST(HiddenPropertiesWithInterceptors) {
v8::HandleScope scope;
LocalContext context;
interceptor_for_hidden_properties_called = false;
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());
CHECK(!interceptor_for_hidden_properties_called);
}
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 = reext_obj.As<v8::External>();
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::New(&data[0]);
Local<v8::Value> one = v8::External::New(&data[1]);
Local<v8::Value> two = v8::External::New(&data[2]);
Local<v8::Value> three = v8::External::New(&data[3]);
char* char_ptr = reinterpret_cast<char*>(v8::External::Cast(*zero)->Value());
CHECK_EQ('0', *char_ptr);
char_ptr = reinterpret_cast<char*>(v8::External::Cast(*one)->Value());
CHECK_EQ('1', *char_ptr);
char_ptr = reinterpret_cast<char*>(v8::External::Cast(*two)->Value());
CHECK_EQ('2', *char_ptr);
char_ptr = reinterpret_cast<char*>(v8::External::Cast(*three)->Value());
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();
{
v8::HandleScope scope;
Local<String> str = v8_str("str");
global = v8::Persistent<String>::New(str);
}
CHECK_EQ(global->Length(), 3);
global.Dispose(v8::Isolate::GetCurrent());
}
THREADED_TEST(LocalHandle) {
v8::HandleScope scope;
v8::Local<String> local = v8::Local<String>::New(v8_str("str"));
CHECK_EQ(local->Length(), 3);
local = v8::Local<String>::New(v8::Isolate::GetCurrent(), v8_str("str"));
CHECK_EQ(local->Length(), 3);
}
class WeakCallCounter {
public:
explicit WeakCallCounter(int id) : id_(id), number_of_weak_calls_(0) { }
int id() { return id_; }
void increment() { number_of_weak_calls_++; }
int NumberOfWeakCalls() { return number_of_weak_calls_; }
private:
int id_;
int number_of_weak_calls_;
};
static void WeakPointerCallback(Persistent<Value> handle, void* id) {
WeakCallCounter* counter = reinterpret_cast<WeakCallCounter*>(id);
CHECK_EQ(1234, counter->id());
counter->increment();
handle.Dispose();
}
THREADED_TEST(ApiObjectGroups) {
HandleScope scope;
LocalContext env;
Persistent<Object> g1s1;
Persistent<Object> g1s2;
Persistent<Object> g1c1;
Persistent<Object> g2s1;
Persistent<Object> g2s2;
Persistent<Object> g2c1;
WeakCallCounter counter(1234);
{
HandleScope scope;
g1s1 = Persistent<Object>::New(Object::New());
g1s2 = Persistent<Object>::New(Object::New());
g1c1 = Persistent<Object>::New(Object::New());
g1s1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g1s2.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g1c1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g2s1 = Persistent<Object>::New(Object::New());
g2s2 = Persistent<Object>::New(Object::New());
g2c1 = Persistent<Object>::New(Object::New());
g2s1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g2s2.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g2c1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
}
Persistent<Object> root = Persistent<Object>::New(g1s1); // make a root.
// Connect group 1 and 2, make a cycle.
CHECK(g1s2->Set(0, g2s2));
CHECK(g2s1->Set(0, g1s1));
{
Persistent<Value> g1_objects[] = { g1s1, g1s2 };
Persistent<Value> g1_children[] = { g1c1 };
Persistent<Value> g2_objects[] = { g2s1, g2s2 };
Persistent<Value> g2_children[] = { g2c1 };
V8::AddObjectGroup(g1_objects, 2);
V8::AddImplicitReferences(g1s1, g1_children, 1);
V8::AddObjectGroup(g2_objects, 2);
V8::AddImplicitReferences(g2s2, g2_children, 1);
}
// Do a single full GC, ensure incremental marking is stopped.
HEAP->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
// All object should be alive.
CHECK_EQ(0, counter.NumberOfWeakCalls());
// Weaken the root.
root.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
// But make children strong roots---all the objects (except for children)
// should be collectable now.
g1c1.ClearWeak();
g2c1.ClearWeak();
// Groups are deleted, rebuild groups.
{
Persistent<Value> g1_objects[] = { g1s1, g1s2 };
Persistent<Value> g1_children[] = { g1c1 };
Persistent<Value> g2_objects[] = { g2s1, g2s2 };
Persistent<Value> g2_children[] = { g2c1 };
V8::AddObjectGroup(g1_objects, 2);
V8::AddImplicitReferences(g1s1, g1_children, 1);
V8::AddObjectGroup(g2_objects, 2);
V8::AddImplicitReferences(g2s2, g2_children, 1);
}
HEAP->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
// All objects should be gone. 5 global handles in total.
CHECK_EQ(5, counter.NumberOfWeakCalls());
// And now make children weak again and collect them.
g1c1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g2c1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
HEAP->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
CHECK_EQ(7, counter.NumberOfWeakCalls());
}
THREADED_TEST(ApiObjectGroupsCycle) {
HandleScope scope;
LocalContext env;
WeakCallCounter counter(1234);
Persistent<Object> g1s1;
Persistent<Object> g1s2;
Persistent<Object> g2s1;
Persistent<Object> g2s2;
Persistent<Object> g3s1;
Persistent<Object> g3s2;
Persistent<Object> g4s1;
Persistent<Object> g4s2;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
{
HandleScope scope;
g1s1 = Persistent<Object>::New(Object::New());
g1s2 = Persistent<Object>::New(Object::New());
g1s1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g1s2.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
CHECK(g1s1.IsWeak());
CHECK(g1s2.IsWeak());
g2s1 = Persistent<Object>::New(Object::New());
g2s2 = Persistent<Object>::New(Object::New());
g2s1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g2s2.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
CHECK(g2s1.IsWeak());
CHECK(g2s2.IsWeak());
g3s1 = Persistent<Object>::New(Object::New());
g3s2 = Persistent<Object>::New(Object::New());
g3s1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g3s2.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
CHECK(g3s1.IsWeak());
CHECK(g3s2.IsWeak());
g4s1 = Persistent<Object>::New(Object::New());
g4s2 = Persistent<Object>::New(Object::New());
g4s1.MakeWeak(isolate,
reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g4s2.MakeWeak(isolate,
reinterpret_cast<void*>(&counter), &WeakPointerCallback);
CHECK(g4s1.IsWeak(isolate));
CHECK(g4s2.IsWeak(isolate));
}
Persistent<Object> root = Persistent<Object>::New(g1s1); // make a root.
// Connect groups. We're building the following cycle:
// G1: { g1s1, g2s1 }, g1s1 implicitly references g2s1, ditto for other
// groups.
{
Persistent<Value> g1_objects[] = { g1s1, g1s2 };
Persistent<Value> g1_children[] = { g2s1 };
Persistent<Value> g2_objects[] = { g2s1, g2s2 };
Persistent<Value> g2_children[] = { g3s1 };
Persistent<Value> g3_objects[] = { g3s1, g3s2 };
Persistent<Value> g3_children[] = { g4s1 };
Persistent<Value> g4_objects[] = { g4s1, g4s2 };
Persistent<Value> g4_children[] = { g1s1 };
V8::AddObjectGroup(g1_objects, 2);
V8::AddImplicitReferences(g1s1, g1_children, 1);
V8::AddObjectGroup(g2_objects, 2);
V8::AddImplicitReferences(g2s1, g2_children, 1);
V8::AddObjectGroup(g3_objects, 2);
V8::AddImplicitReferences(g3s1, g3_children, 1);
V8::AddObjectGroup(isolate, g4_objects, 2);
V8::AddImplicitReferences(g4s1, g4_children, 1);
}
// Do a single full GC
HEAP->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
// All object should be alive.
CHECK_EQ(0, counter.NumberOfWeakCalls());
// Weaken the root.
root.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
// Groups are deleted, rebuild groups.
{
Persistent<Value> g1_objects[] = { g1s1, g1s2 };
Persistent<Value> g1_children[] = { g2s1 };
Persistent<Value> g2_objects[] = { g2s1, g2s2 };
Persistent<Value> g2_children[] = { g3s1 };
Persistent<Value> g3_objects[] = { g3s1, g3s2 };
Persistent<Value> g3_children[] = { g4s1 };
Persistent<Value> g4_objects[] = { g4s1, g4s2 };
Persistent<Value> g4_children[] = { g1s1 };
V8::AddObjectGroup(g1_objects, 2);
V8::AddImplicitReferences(g1s1, g1_children, 1);
V8::AddObjectGroup(g2_objects, 2);
V8::AddImplicitReferences(g2s1, g2_children, 1);
V8::AddObjectGroup(g3_objects, 2);
V8::AddImplicitReferences(g3s1, g3_children, 1);
V8::AddObjectGroup(g4_objects, 2);
V8::AddImplicitReferences(g4s1, g4_children, 1);
}
HEAP->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
// All objects should be gone. 9 global handles in total.
CHECK_EQ(9, counter.NumberOfWeakCalls());
}
// TODO(mstarzinger): This should be a THREADED_TEST but causes failures
// on the buildbots, so was made non-threaded for the time being.
TEST(ApiObjectGroupsCycleForScavenger) {
i::FLAG_stress_compaction = false;
i::FLAG_gc_global = false;
HandleScope scope;
LocalContext env;
WeakCallCounter counter(1234);
Persistent<Object> g1s1;
Persistent<Object> g1s2;
Persistent<Object> g2s1;
Persistent<Object> g2s2;
Persistent<Object> g3s1;
Persistent<Object> g3s2;
{
HandleScope scope;
g1s1 = Persistent<Object>::New(Object::New());
g1s2 = Persistent<Object>::New(Object::New());
g1s1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g1s2.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g2s1 = Persistent<Object>::New(Object::New());
g2s2 = Persistent<Object>::New(Object::New());
g2s1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g2s2.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g3s1 = Persistent<Object>::New(Object::New());
g3s2 = Persistent<Object>::New(Object::New());
g3s1.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
g3s2.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
}
// Make a root.
Persistent<Object> root = Persistent<Object>::New(g1s1);
root.MarkPartiallyDependent();
// Connect groups. We're building the following cycle:
// G1: { g1s1, g2s1 }, g1s1 implicitly references g2s1, ditto for other
// groups.
{
g1s1.MarkPartiallyDependent();
g1s2.MarkPartiallyDependent();
g2s1.MarkPartiallyDependent();
g2s2.MarkPartiallyDependent();
g3s1.MarkPartiallyDependent();
g3s2.MarkPartiallyDependent();
Persistent<Value> g1_objects[] = { g1s1, g1s2 };
Persistent<Value> g2_objects[] = { g2s1, g2s2 };
Persistent<Value> g3_objects[] = { g3s1, g3s2 };
V8::AddObjectGroup(g1_objects, 2);
g1s1->Set(v8_str("x"), g2s1);
V8::AddObjectGroup(g2_objects, 2);
g2s1->Set(v8_str("x"), g3s1);
V8::AddObjectGroup(g3_objects, 2);
g3s1->Set(v8_str("x"), g1s1);
}
HEAP->CollectGarbage(i::NEW_SPACE);
// All objects should be alive.
CHECK_EQ(0, counter.NumberOfWeakCalls());
// Weaken the root.
root.MakeWeak(reinterpret_cast<void*>(&counter), &WeakPointerCallback);
root.MarkPartiallyDependent();
v8::Isolate* isolate = v8::Isolate::GetCurrent();
// Groups are deleted, rebuild groups.
{
g1s1.MarkPartiallyDependent(isolate);
g1s2.MarkPartiallyDependent(isolate);
g2s1.MarkPartiallyDependent(isolate);
g2s2.MarkPartiallyDependent(isolate);
g3s1.MarkPartiallyDependent(isolate);
g3s2.MarkPartiallyDependent(isolate);
Persistent<Value> g1_objects[] = { g1s1, g1s2 };
Persistent<Value> g2_objects[] = { g2s1, g2s2 };
Persistent<Value> g3_objects[] = { g3s1, g3s2 };
V8::AddObjectGroup(g1_objects, 2);
g1s1->Set(v8_str("x"), g2s1);
V8::AddObjectGroup(g2_objects, 2);
g2s1->Set(v8_str("x"), g3s1);
V8::AddObjectGroup(g3_objects, 2);
g3s1->Set(v8_str("x"), g1s1);
}
HEAP->CollectGarbage(i::NEW_SPACE);
// All objects should be gone. 7 global handles in total.
CHECK_EQ(7, counter.NumberOfWeakCalls());
}
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!");
}
TEST(TryCatchCustomException) {
v8::HandleScope scope;
LocalContext env;
v8::TryCatch try_catch;
CompileRun("function CustomError() { this.a = 'b'; }"
"(function f() { throw new CustomError(); })();");
CHECK(try_catch.HasCaught());
CHECK(try_catch.Exception()->ToObject()->
Get(v8_str("a"))->Equals(v8_str("b")));
}
bool message_received;
static void check_message_0(v8::Handle<v8::Message> message,
v8::Handle<Value> data) {
CHECK_EQ(6.75, message->GetScriptResourceName()->NumberValue());
CHECK_EQ(7.56, message->GetScriptData()->NumberValue());
message_received = true;
}
THREADED_TEST(MessageHandler0) {
message_received = false;
v8::HandleScope scope;
CHECK(!message_received);
v8::V8::AddMessageListener(check_message_0);
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_0);
}
static void check_message_1(v8::Handle<v8::Message> message,
v8::Handle<Value> data) {
CHECK(data->IsNumber());
CHECK_EQ(1337, data->Int32Value());
message_received = true;
}
TEST(MessageHandler1) {
message_received = false;
v8::HandleScope scope;
CHECK(!message_received);
v8::V8::AddMessageListener(check_message_1);
LocalContext context;
CompileRun("throw 1337;");
CHECK(message_received);
// clear out the message listener
v8::V8::RemoveMessageListeners(check_message_1);
}
static void check_message_2(v8::Handle<v8::Message> message,
v8::Handle<Value> data) {
LocalContext context;
CHECK(data->IsObject());
v8::Local<v8::Value> hidden_property =
v8::Object::Cast(*data)->GetHiddenValue(v8_str("hidden key"));
CHECK(v8_str("hidden value")->Equals(hidden_property));
message_received = true;
}
TEST(MessageHandler2) {
message_received = false;
v8::HandleScope scope;
CHECK(!message_received);
v8::V8::AddMessageListener(check_message_2);
LocalContext context;
v8::Local<v8::Value> error = v8::Exception::Error(v8_str("custom error"));
v8::Object::Cast(*error)->SetHiddenValue(v8_str("hidden key"),
v8_str("hidden value"));
context->Global()->Set(v8_str("error"), error);
CompileRun("throw error;");
CHECK(message_received);
// clear out the message listener
v8::V8::RemoveMessageListeners(check_message_2);
}
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;
// none
Local<String> prop = v8_str("none");
context->Global()->Set(prop, v8_num(7));
CHECK_EQ(v8::None, context->Global()->GetPropertyAttributes(prop));
// read-only
prop = v8_str("read_only");
context->Global()->Set(prop, v8_num(7), v8::ReadOnly);
CHECK_EQ(7, context->Global()->Get(prop)->Int32Value());
CHECK_EQ(v8::ReadOnly, context->Global()->GetPropertyAttributes(prop));
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());
CHECK_EQ(v8::DontDelete, context->Global()->GetPropertyAttributes(prop));
// dont-enum
prop = v8_str("dont_enum");
context->Global()->Set(prop, v8_num(28), v8::DontEnum);
CHECK_EQ(v8::DontEnum, context->Global()->GetPropertyAttributes(prop));
// absent
prop = v8_str("absent");
CHECK_EQ(v8::None, context->Global()->GetPropertyAttributes(prop));
Local<Value> fake_prop = v8_num(1);
CHECK_EQ(v8::None, context->Global()->GetPropertyAttributes(fake_prop));
// exception
TryCatch try_catch;
Local<Value> exception =
CompileRun("({ toString: function() { throw 'exception';} })");
CHECK_EQ(v8::None, context->Global()->GetPropertyAttributes(exception));
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value(try_catch.Exception());
CHECK_EQ("exception", *exception_value);
try_catch.Reset();
}
THREADED_TEST(Array) {
v8::HandleScope scope;
LocalContext context;
Local<v8::Array> array = v8::Array::New();
CHECK_EQ(0, array->Length());
CHECK(array->Get(0)->IsUndefined());
CHECK(!array->Has(0));
CHECK(array->Get(100)->IsUndefined());
CHECK(!array->Has(100));
array->Set(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(2)->Int32Value());
Local<Value> obj = Script::Compile(v8_str("[1, 2, 3]"))->Run();
Local<v8::Array> arr = obj.As<v8::Array>();
CHECK_EQ(3, arr->Length());
CHECK_EQ(1, arr->Get(0)->Int32Value());
CHECK_EQ(2, arr->Get(1)->Int32Value());
CHECK_EQ(3, arr->Get(2)->Int32Value());
array = v8::Array::New(27);
CHECK_EQ(27, array->Length());
array = v8::Array::New(-27);
CHECK_EQ(0, array->Length());
}
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(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 = CompileRun(fun).As<v8::Array>();
CHECK_EQ(0, a0->Length());
const char* fun2 = "f(11)";
Local<v8::Array> a1 = CompileRun(fun2).As<v8::Array>();
CHECK_EQ(1, a1->Length());
CHECK_EQ(11, a1->Get(0)->Int32Value());
const char* fun3 = "f(12, 13)";
Local<v8::Array> a2 = CompileRun(fun3).As<v8::Array>();
CHECK_EQ(2, a2->Length());
CHECK_EQ(12, a2->Get(0)->Int32Value());
CHECK_EQ(13, a2->Get(1)->Int32Value());
const char* fun4 = "f(14, 15, 16)";
Local<v8::Array> a3 = CompileRun(fun4).As<v8::Array>();
CHECK_EQ(3, a3->Length());
CHECK_EQ(14, a3->Get(0)->Int32Value());
CHECK_EQ(15, a3->Get(1)->Int32Value());
CHECK_EQ(16, a3->Get(2)->Int32Value());
const char* fun5 = "f(17, 18, 19, 20)";
Local<v8::Array> a4 = CompileRun(fun5).As<v8::Array>();
CHECK_EQ(4, a4->Length());
CHECK_EQ(17, a4->Get(0)->Int32Value());
CHECK_EQ(18, a4->Get(1)->Int32Value());
CHECK_EQ(19, a4->Get(2)->Int32Value());
CHECK_EQ(20, a4->Get(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 (i::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 (i::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 (i::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(3 * K * K);
v8::SetResourceConstraints(&constraints);
// Execute a script that causes out of memory.
v8::V8::IgnoreOutOfMemoryException();
v8::HandleScope scope;
LocalContext context;
// 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 < 22; 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(ConversionNumber) {
v8::HandleScope scope;
LocalContext env;
// Very large number.
CompileRun("var obj = Math.pow(2,32) * 1237;");
Local<Value> obj = env->Global()->Get(v8_str("obj"));
CHECK_EQ(5312874545152.0, obj->ToNumber()->Value());
CHECK_EQ(0, obj->ToInt32()->Value());
CHECK(0u == obj->ToUint32()->Value()); // NOLINT - no CHECK_EQ for unsigned.
// Large number.
CompileRun("var obj = -1234567890123;");
obj = env->Global()->Get(v8_str("obj"));
CHECK_EQ(-1234567890123.0, obj->ToNumber()->Value());
CHECK_EQ(-1912276171, obj->ToInt32()->Value());
CHECK(2382691125u == obj->ToUint32()->Value()); // NOLINT
// Small positive integer.
CompileRun("var obj = 42;");
obj = env->Global()->Get(v8_str("obj"));
CHECK_EQ(42.0, obj->ToNumber()->Value());
CHECK_EQ(42, obj->ToInt32()->Value());
CHECK(42u == obj->ToUint32()->Value()); // NOLINT
// Negative integer.
CompileRun("var obj = -37;");
obj = env->Global()->Get(v8_str("obj"));
CHECK_EQ(-37.0, obj->ToNumber()->Value());
CHECK_EQ(-37, obj->ToInt32()->Value());
CHECK(4294967259u == obj->ToUint32()->Value()); // NOLINT
// Positive non-int32 integer.
CompileRun("var obj = 0x81234567;");
obj = env->Global()->Get(v8_str("obj"));
CHECK_EQ(2166572391.0, obj->ToNumber()->Value());
CHECK_EQ(-2128394905, obj->ToInt32()->Value());
CHECK(2166572391u == obj->ToUint32()->Value()); // NOLINT
// Fraction.
CompileRun("var obj = 42.3;");
obj = env->Global()->Get(v8_str("obj"));
CHECK_EQ(42.3, obj->ToNumber()->Value());
CHECK_EQ(42, obj->ToInt32()->Value());
CHECK(42u == obj->ToUint32()->Value()); // NOLINT
// Large negative fraction.
CompileRun("var obj = -5726623061.75;");
obj = env->Global()->Get(v8_str("obj"));
CHECK_EQ(-5726623061.75, obj->ToNumber()->Value());
CHECK_EQ(-1431655765, obj->ToInt32()->Value());
CHECK(2863311531u == obj->ToUint32()->Value()); // NOLINT
}
THREADED_TEST(isNumberType) {
v8::HandleScope scope;
LocalContext env;
// Very large number.
CompileRun("var obj = Math.pow(2,32) * 1237;");
Local<Value> obj = env->Global()->Get(v8_str("obj"));
CHECK(!obj->IsInt32());
CHECK(!obj->IsUint32());
// Large negative number.
CompileRun("var obj = -1234567890123;");
obj = env->Global()->Get(v8_str("obj"));
CHECK(!obj->IsInt32());
CHECK(!obj->IsUint32());
// Small positive integer.
CompileRun("var obj = 42;");
obj = env->Global()->Get(v8_str("obj"));
CHECK(obj->IsInt32());
CHECK(obj->IsUint32());
// Negative integer.
CompileRun("var obj = -37;");
obj = env->Global()->Get(v8_str("obj"));
CHECK(obj->IsInt32());
CHECK(!obj->IsUint32());
// Positive non-int32 integer.
CompileRun("var obj = 0x81234567;");
obj = env->Global()->Get(v8_str("obj"));
CHECK(!obj->IsInt32());
CHECK(obj->IsUint32());
// Fraction.
CompileRun("var obj = 42.3;");
obj = env->Global()->Get(v8_str("obj"));
CHECK(!obj->IsInt32());
CHECK(!obj->IsUint32());
// Large negative fraction.
CompileRun("var obj = -5726623061.75;");
obj = env->Global()->Get(v8_str("obj"));
CHECK(!obj->IsInt32());
CHECK(!obj->IsUint32());
// Positive zero
CompileRun("var obj = 0.0;");
obj = env->Global()->Get(v8_str("obj"));
CHECK(obj->IsInt32());
CHECK(obj->IsUint32());
// Positive zero
CompileRun("var obj = -0.0;");
obj = env->Global()->Get(v8_str("obj"));
CHECK(!obj->IsInt32());
CHECK(!obj->IsUint32());
}
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::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 check_reference_error_message(
v8::Handle<v8::Message> message,
v8::Handle<v8::Value> data) {
const char* reference_error = "Uncaught ReferenceError: asdf is not defined";
CHECK(message->Get()->Equals(v8_str(reference_error)));
}
static v8::Handle<Value> Fail(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
CHECK(false);
return v8::Undefined();
}
// Test that overwritten methods are not invoked on uncaught exception
// formatting. However, they are invoked when performing normal error
// string conversions.
TEST(APIThrowMessageOverwrittenToString) {
v8::HandleScope scope;
v8::V8::AddMessageListener(check_reference_error_message);
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("fail"), v8::FunctionTemplate::New(Fail));
LocalContext context(NULL, templ);
CompileRun("asdf;");
CompileRun("var limit = {};"
"limit.valueOf = fail;"
"Error.stackTraceLimit = limit;");
CompileRun("asdf");
CompileRun("Array.prototype.pop = fail;");
CompileRun("Object.prototype.hasOwnProperty = fail;");
CompileRun("Object.prototype.toString = function f() { return 'Yikes'; }");
CompileRun("Number.prototype.toString = function f() { return 'Yikes'; }");
CompileRun("String.prototype.toString = function f() { return 'Yikes'; }");
CompileRun("ReferenceError.prototype.toString ="
" function() { return 'Whoops' }");
CompileRun("asdf;");
CompileRun("ReferenceError.prototype.constructor.name = void 0;");
CompileRun("asdf;");
CompileRun("ReferenceError.prototype.constructor = void 0;");
CompileRun("asdf;");
CompileRun("ReferenceError.prototype.__proto__ = new Object();");
CompileRun("asdf;");
CompileRun("ReferenceError.prototype = new Object();");
CompileRun("asdf;");
v8::Handle<Value> string = CompileRun("try { asdf; } catch(e) { e + ''; }");
CHECK(string->Equals(v8_str("Whoops")));
CompileRun("ReferenceError.prototype.constructor = new Object();"
"ReferenceError.prototype.constructor.name = 1;"
"Number.prototype.toString = function() { return 'Whoops'; };"
"ReferenceError.prototype.toString = Object.prototype.toString;");
CompileRun("asdf;");
v8::V8::RemoveMessageListeners(check_reference_error_message);
}
static void check_custom_error_message(
v8::Handle<v8::Message> message,
v8::Handle<v8::Value> data) {
const char* uncaught_error = "Uncaught MyError toString";
CHECK(message->Get()->Equals(v8_str(uncaught_error)));
}
TEST(CustomErrorToString) {
v8::HandleScope scope;
v8::V8::AddMessageListener(check_custom_error_message);
LocalContext context;
CompileRun(
"function MyError(name, message) { "
" this.name = name; "
" this.message = message; "
"} "
"MyError.prototype = Object.create(Error.prototype); "
"MyError.prototype.toString = function() { "
" return 'MyError toString'; "
"}; "
"throw new MyError('my name', 'my message'); ");
v8::V8::RemoveMessageListeners(check_custom_error_message);
}
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(receive_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(receive_message);
}
TEST(APIStackOverflowAndVerboseTryCatch) {
message_received = false;
v8::HandleScope scope;
v8::V8::AddMessageListener(receive_message);
LocalContext context;
v8::TryCatch try_catch;
try_catch.SetVerbose(true);
Local<Value> result = CompileRun("function foo() { foo(); } foo();");
CHECK(try_catch.HasCaught());
CHECK(result.IsEmpty());
CHECK(message_received);
v8::V8::RemoveMessageListeners(receive_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 = fun.As<Function>()->Call(global, 4, argv);
int expected = args[3]->Int32Value();
if (try_catch.HasCaught()) {
CHECK_EQ(expected, count);
CHECK(result.IsEmpty());
CHECK(!i::Isolate::Current()->has_scheduled_exception());
} else {
CHECK_NE(expected, count);
}
return result;
} else {
return fun.As<Function>()->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(TryCatchAndFinallyHidingException) {
v8::HandleScope scope;
LocalContext context;
v8::TryCatch try_catch;
CHECK(!try_catch.HasCaught());
CompileRun("function f(k) { try { this[k]; } finally { return 0; } };");
CompileRun("f({toString: function() { throw 42; }});");
CHECK(!try_catch.HasCaught());
}
v8::Handle<v8::Value> WithTryCatch(const v8::Arguments& args) {
v8::TryCatch try_catch;
return v8::Undefined();
}
THREADED_TEST(TryCatchAndFinally) {
v8::HandleScope scope;
LocalContext context;
context->Global()->Set(
v8_str("native_with_try_catch"),
v8::FunctionTemplate::New(WithTryCatch)->GetFunction());
v8::TryCatch try_catch;
CHECK(!try_catch.HasCaught());
CompileRun(
"try {\n"
" throw new Error('a');\n"
"} finally {\n"
" native_with_try_catch();\n"
"}\n");
CHECK(try_catch.HasCaught());
}
static void TryCatchNestedHelper(int depth) {
if (depth > 0) {
v8::TryCatch try_catch;
try_catch.SetVerbose(true);
TryCatchNestedHelper(depth - 1);
CHECK(try_catch.HasCaught());
try_catch.ReThrow();
} else {
v8::ThrowException(v8_str("back"));
}
}
TEST(TryCatchNested) {
v8::V8::Initialize();
v8::HandleScope scope;
LocalContext context;
v8::TryCatch try_catch;
TryCatchNestedHelper(5);
CHECK(try_catch.HasCaught());
CHECK_EQ(0, strcmp(*v8::String::Utf8Value(try_catch.Exception()), "back"));
}
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"));
}
}
THREADED_TEST(DefinePropertyOnAPIAccessor) {
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());
// Uses getOwnPropertyDescriptor to check the configurable status
Local<Script> script_desc
= Script::Compile(v8_str("var prop = Object.getOwnPropertyDescriptor( "
"obj, 'x');"
"prop.configurable;"));
Local<Value> result = script_desc->Run();
CHECK_EQ(result->BooleanValue(), true);
// Redefine get - but still configurable
Local<Script> script_define
= Script::Compile(v8_str("var desc = { get: function(){return 42; },"
" configurable: true };"
"Object.defineProperty(obj, 'x', desc);"
"obj.x"));
result = script_define->Run();
CHECK_EQ(result, v8_num(42));
// Check that the accessor is still configurable
result = script_desc->Run();
CHECK_EQ(result->BooleanValue(), true);
// Redefine to a non-configurable
script_define
= Script::Compile(v8_str("var desc = { get: function(){return 43; },"
" configurable: false };"
"Object.defineProperty(obj, 'x', desc);"
"obj.x"));
result = script_define->Run();
CHECK_EQ(result, v8_num(43));
result = script_desc->Run();
CHECK_EQ(result->BooleanValue(), false);
// Make sure that it is not possible to redefine again
v8::TryCatch try_catch;
result = script_define->Run();
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value(try_catch.Exception());
CHECK_EQ(*exception_value, "TypeError: Cannot redefine property: x");
}
THREADED_TEST(DefinePropertyOnDefineGetterSetter) {
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_desc = Script::Compile(v8_str("var prop ="
"Object.getOwnPropertyDescriptor( "
"obj, 'x');"
"prop.configurable;"));
Local<Value> result = script_desc->Run();
CHECK_EQ(result->BooleanValue(), true);
Local<Script> script_define =
Script::Compile(v8_str("var desc = {get: function(){return 42; },"
" configurable: true };"
"Object.defineProperty(obj, 'x', desc);"
"obj.x"));
result = script_define->Run();
CHECK_EQ(result, v8_num(42));
result = script_desc->Run();
CHECK_EQ(result->BooleanValue(), true);
script_define =
Script::Compile(v8_str("var desc = {get: function(){return 43; },"
" configurable: false };"
"Object.defineProperty(obj, 'x', desc);"
"obj.x"));
result = script_define->Run();
CHECK_EQ(result, v8_num(43));
result = script_desc->Run();
CHECK_EQ(result->BooleanValue(), false);
v8::TryCatch try_catch;
result = script_define->Run();
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value(try_catch.Exception());
CHECK_EQ(*exception_value, "TypeError: Cannot redefine property: x");
}
static v8::Handle<v8::Object> GetGlobalProperty(LocalContext* context,
char const* name) {
return v8::Handle<v8::Object>::Cast((*context)->Global()->Get(v8_str(name)));
}
THREADED_TEST(DefineAPIAccessorOnObject) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
LocalContext context;
context->Global()->Set(v8_str("obj1"), templ->NewInstance());
CompileRun("var obj2 = {};");
CHECK(CompileRun("obj1.x")->IsUndefined());
CHECK(CompileRun("obj2.x")->IsUndefined());
CHECK(GetGlobalProperty(&context, "obj1")->
SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
ExpectString("obj1.x", "x");
CHECK(CompileRun("obj2.x")->IsUndefined());
CHECK(GetGlobalProperty(&context, "obj2")->
SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
ExpectString("obj1.x", "x");
ExpectString("obj2.x", "x");
ExpectTrue("Object.getOwnPropertyDescriptor(obj1, 'x').configurable");
ExpectTrue("Object.getOwnPropertyDescriptor(obj2, 'x').configurable");
CompileRun("Object.defineProperty(obj1, 'x',"
"{ get: function() { return 'y'; }, configurable: true })");
ExpectString("obj1.x", "y");
ExpectString("obj2.x", "x");
CompileRun("Object.defineProperty(obj2, 'x',"
"{ get: function() { return 'y'; }, configurable: true })");
ExpectString("obj1.x", "y");
ExpectString("obj2.x", "y");
ExpectTrue("Object.getOwnPropertyDescriptor(obj1, 'x').configurable");
ExpectTrue("Object.getOwnPropertyDescriptor(obj2, 'x').configurable");
CHECK(GetGlobalProperty(&context, "obj1")->
SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
CHECK(GetGlobalProperty(&context, "obj2")->
SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
ExpectString("obj1.x", "x");
ExpectString("obj2.x", "x");
ExpectTrue("Object.getOwnPropertyDescriptor(obj1, 'x').configurable");
ExpectTrue("Object.getOwnPropertyDescriptor(obj2, 'x').configurable");
// Define getters/setters, but now make them not configurable.
CompileRun("Object.defineProperty(obj1, 'x',"
"{ get: function() { return 'z'; }, configurable: false })");
CompileRun("Object.defineProperty(obj2, 'x',"
"{ get: function() { return 'z'; }, configurable: false })");
ExpectTrue("!Object.getOwnPropertyDescriptor(obj1, 'x').configurable");
ExpectTrue("!Object.getOwnPropertyDescriptor(obj2, 'x').configurable");
ExpectString("obj1.x", "z");
ExpectString("obj2.x", "z");
CHECK(!GetGlobalProperty(&context, "obj1")->
SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
CHECK(!GetGlobalProperty(&context, "obj2")->
SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
ExpectString("obj1.x", "z");
ExpectString("obj2.x", "z");
}
THREADED_TEST(DontDeleteAPIAccessorsCannotBeOverriden) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
LocalContext context;
context->Global()->Set(v8_str("obj1"), templ->NewInstance());
CompileRun("var obj2 = {};");
CHECK(GetGlobalProperty(&context, "obj1")->SetAccessor(
v8_str("x"),
GetXValue, NULL,
v8_str("donut"), v8::DEFAULT, v8::DontDelete));
CHECK(GetGlobalProperty(&context, "obj2")->SetAccessor(
v8_str("x"),
GetXValue, NULL,
v8_str("donut"), v8::DEFAULT, v8::DontDelete));
ExpectString("obj1.x", "x");
ExpectString("obj2.x", "x");
ExpectTrue("!Object.getOwnPropertyDescriptor(obj1, 'x').configurable");
ExpectTrue("!Object.getOwnPropertyDescriptor(obj2, 'x').configurable");
CHECK(!GetGlobalProperty(&context, "obj1")->
SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
CHECK(!GetGlobalProperty(&context, "obj2")->
SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
{
v8::TryCatch try_catch;
CompileRun("Object.defineProperty(obj1, 'x',"
"{get: function() { return 'func'; }})");
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value(try_catch.Exception());
CHECK_EQ(*exception_value, "TypeError: Cannot redefine property: x");
}
{
v8::TryCatch try_catch;
CompileRun("Object.defineProperty(obj2, 'x',"
"{get: function() { return 'func'; }})");
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value(try_catch.Exception());
CHECK_EQ(*exception_value, "TypeError: Cannot redefine property: x");
}
}
static v8::Handle<Value> Get239Value(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK_EQ(info.Data(), v8_str("donut"));
CHECK_EQ(name, v8_str("239"));
return name;
}
THREADED_TEST(ElementAPIAccessor) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
LocalContext context;
context->Global()->Set(v8_str("obj1"), templ->NewInstance());
CompileRun("var obj2 = {};");
CHECK(GetGlobalProperty(&context, "obj1")->SetAccessor(
v8_str("239"),
Get239Value, NULL,
v8_str("donut")));
CHECK(GetGlobalProperty(&context, "obj2")->SetAccessor(
v8_str("239"),
Get239Value, NULL,
v8_str("donut")));
ExpectString("obj1[239]", "239");
ExpectString("obj2[239]", "239");
ExpectString("obj1['239']", "239");
ExpectString("obj2['239']", "239");
}
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>();
}
}
THREADED_TEST(SetterOnly) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessor(v8_str("x"), NULL, SetXValue, v8_str("donut"));
LocalContext context;
context->Global()->Set(v8_str("obj"), templ->NewInstance());
Local<Script> script = Script::Compile(v8_str("obj.x = 4; obj.x"));
for (int i = 0; i < 10; i++) {
CHECK(xValue.IsEmpty());
script->Run();
CHECK_EQ(v8_num(4), xValue);
xValue.Dispose();
xValue = v8::Persistent<Value>();
}
}
THREADED_TEST(NoAccessors) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessor(v8_str("x"), NULL, NULL, v8_str("donut"));
LocalContext context;
context->Global()->Set(v8_str("obj"), templ->NewInstance());
Local<Script> script = Script::Compile(v8_str("obj.x = 4; obj.x"));
for (int i = 0; i < 10; i++) {
script->Run();
}
}
static v8::Handle<Value> XPropertyGetter(Local<String> property,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(info.Data()->IsUndefined());
return property;
}
THREADED_TEST(NamedInterceptorPropertyRead) {
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(NamedInterceptorDictionaryIC) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(XPropertyGetter);
LocalContext context;
// Create an object with a named interceptor.
context->Global()->Set(v8_str("interceptor_obj"), templ->NewInstance());
Local<Script> script = Script::Compile(v8_str("interceptor_obj.x"));
for (int i = 0; i < 10; i++) {
Local<Value> result = script->Run();
CHECK_EQ(result, v8_str("x"));
}
// Create a slow case object and a function accessing a property in
// that slow case object (with dictionary probing in generated
// code). Then force object with a named interceptor into slow-case,
// pass it to the function, and check that the interceptor is called
// instead of accessing the local property.
Local<Value> result =
CompileRun("function get_x(o) { return o.x; };"
"var obj = { x : 42, y : 0 };"
"delete obj.y;"
"for (var i = 0; i < 10; i++) get_x(obj);"
"interceptor_obj.x = 42;"
"interceptor_obj.y = 10;"
"delete interceptor_obj.y;"
"get_x(interceptor_obj)");
CHECK_EQ(result, v8_str("x"));
}
THREADED_TEST(NamedInterceptorDictionaryICMultipleContext) {
v8::HandleScope scope;
v8::Persistent<Context> context1 = Context::New();
context1->Enter();
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(XPropertyGetter);
// Create an object with a named interceptor.
v8::Local<v8::Object> object = templ->NewInstance();
context1->Global()->Set(v8_str("interceptor_obj"), object);
// Force the object into the slow case.
CompileRun("interceptor_obj.y = 0;"
"delete interceptor_obj.y;");
context1->Exit();
{
// Introduce the object into a different context.
// Repeat named loads to exercise ICs.
LocalContext context2;
context2->Global()->Set(v8_str("interceptor_obj"), object);
Local<Value> result =
CompileRun("function get_x(o) { return o.x; }"
"interceptor_obj.x = 42;"
"for (var i=0; i != 10; i++) {"
" get_x(interceptor_obj);"
"}"
"get_x(interceptor_obj)");
// Check that the interceptor was actually invoked.
CHECK_EQ(result, v8_str("x"));
}
// Return to the original context and force some object to the slow case
// to cause the NormalizedMapCache to verify.
context1->Enter();
CompileRun("var obj = { x : 0 }; delete obj.x;");
context1->Exit();
context1.Dispose();
}
static v8::Handle<Value> SetXOnPrototypeGetter(Local<String> property,
const AccessorInfo& info) {
// Set x on the prototype object and do not handle the get request.
v8::Handle<v8::Value> proto = info.Holder()->GetPrototype();
proto.As<v8::Object>()->Set(v8_str("x"), v8::Integer::New(23));
return v8::Handle<Value>();
}
// This is a regression test for http://crbug.com/20104. Map
// transitions should not interfere with post interceptor lookup.
THREADED_TEST(NamedInterceptorMapTransitionRead) {
v8::HandleScope scope;
Local<v8::FunctionTemplate> function_template = v8::FunctionTemplate::New();
Local<v8::ObjectTemplate> instance_template
= function_template->InstanceTemplate();
instance_template->SetNamedPropertyHandler(SetXOnPrototypeGetter);
LocalContext context;
context->Global()->Set(v8_str("F"), function_template->GetFunction());
// Create an instance of F and introduce a map transition for x.
CompileRun("var o = new F(); o.x = 23;");
// Create an instance of F and invoke the getter. The result should be 23.
Local<Value> result = CompileRun("o = new F(); o.x");
CHECK_EQ(result->Int32Value(), 23);
}
static v8::Handle<Value> IndexedPropertyGetter(uint32_t index,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
if (index == 37) {
return v8::Handle<Value>(v8_num(625));
}
return v8::Handle<Value>();
}
static v8::Handle<Value> IndexedPropertySetter(uint32_t index,
Local<Value> value,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
if (index == 39) {
return value;
}
return v8::Handle<Value>();
}
THREADED_TEST(IndexedInterceptorWithIndexedAccessor) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(IndexedPropertyGetter,
IndexedPropertySetter);
LocalContext context;
context->Global()->Set(v8_str("obj"), templ->NewInstance());
Local<Script> getter_script = Script::Compile(v8_str(
"obj.__defineGetter__(\"3\", function(){return 5;});obj[3];"));
Local<Script> setter_script = Script::Compile(v8_str(
"obj.__defineSetter__(\"17\", function(val){this.foo = val;});"
"obj[17] = 23;"
"obj.foo;"));
Local<Script> interceptor_setter_script = Script::Compile(v8_str(
"obj.__defineSetter__(\"39\", function(val){this.foo = \"hit\";});"
"obj[39] = 47;"
"obj.foo;")); // This setter should not run, due to the interceptor.
Local<Script> interceptor_getter_script = Script::Compile(v8_str(
"obj[37];"));
Local<Value> result = getter_script->Run();
CHECK_EQ(v8_num(5), result);
result = setter_script->Run();
CHECK_EQ(v8_num(23), result);
result = interceptor_setter_script->Run();
CHECK_EQ(v8_num(23), result);
result = interceptor_getter_script->Run();
CHECK_EQ(v8_num(625), result);
}
static v8::Handle<Value> UnboxedDoubleIndexedPropertyGetter(
uint32_t index,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
if (index < 25) {
return v8::Handle<Value>(v8_num(index));
}
return v8::Handle<Value>();
}
static v8::Handle<Value> UnboxedDoubleIndexedPropertySetter(
uint32_t index,
Local<Value> value,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
if (index < 25) {
return v8::Handle<Value>(v8_num(index));
}
return v8::Handle<Value>();
}
Handle<v8::Array> UnboxedDoubleIndexedPropertyEnumerator(
const AccessorInfo& info) {
// Force the list of returned keys to be stored in a FastDoubleArray.
Local<Script> indexed_property_names_script = Script::Compile(v8_str(
"keys = new Array(); keys[125000] = 1;"
"for(i = 0; i < 80000; i++) { keys[i] = i; };"
"keys.length = 25; keys;"));
Local<Value> result = indexed_property_names_script->Run();
return Local<v8::Array>(::v8::Array::Cast(*result));
}
// Make sure that the the interceptor code in the runtime properly handles
// merging property name lists for double-array-backed arrays.
THREADED_TEST(IndexedInterceptorUnboxedDoubleWithIndexedAccessor) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(UnboxedDoubleIndexedPropertyGetter,
UnboxedDoubleIndexedPropertySetter,
0,
0,
UnboxedDoubleIndexedPropertyEnumerator);
LocalContext context;
context->Global()->Set(v8_str("obj"), templ->NewInstance());
// When obj is created, force it to be Stored in a FastDoubleArray.
Local<Script> create_unboxed_double_script = Script::Compile(v8_str(
"obj[125000] = 1; for(i = 0; i < 80000; i+=2) { obj[i] = i; } "
"key_count = 0; "
"for (x in obj) {key_count++;};"
"obj;"));
Local<Value> result = create_unboxed_double_script->Run();
CHECK(result->ToObject()->HasRealIndexedProperty(2000));
Local<Script> key_count_check = Script::Compile(v8_str(
"key_count;"));
result = key_count_check->Run();
CHECK_EQ(v8_num(40013), result);
}
Handle<v8::Array> NonStrictArgsIndexedPropertyEnumerator(
const AccessorInfo& info) {
// Force the list of returned keys to be stored in a Arguments object.
Local<Script> indexed_property_names_script = Script::Compile(v8_str(
"function f(w,x) {"
" return arguments;"
"}"
"keys = f(0, 1, 2, 3);"
"keys;"));
Local<Value> result = indexed_property_names_script->Run();
return Local<v8::Array>(static_cast<v8::Array*>(::v8::Object::Cast(*result)));
}
static v8::Handle<Value> NonStrictIndexedPropertyGetter(
uint32_t index,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
if (index < 4) {
return v8::Handle<Value>(v8_num(index));
}
return v8::Handle<Value>();
}
// Make sure that the the interceptor code in the runtime properly handles
// merging property name lists for non-string arguments arrays.
THREADED_TEST(IndexedInterceptorNonStrictArgsWithIndexedAccessor) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(NonStrictIndexedPropertyGetter,
0,
0,
0,
NonStrictArgsIndexedPropertyEnumerator);
LocalContext context;
context->Global()->Set(v8_str("obj"), templ->NewInstance());
Local<Script> create_args_script =
Script::Compile(v8_str(
"var key_count = 0;"
"for (x in obj) {key_count++;} key_count;"));
Local<Value> result = create_args_script->Run();
CHECK_EQ(v8_num(4), result);
}
static v8::Handle<Value> IdentityIndexedPropertyGetter(
uint32_t index,
const AccessorInfo& info) {
return v8::Integer::NewFromUnsigned(index);
}
THREADED_TEST(IndexedInterceptorWithGetOwnPropertyDescriptor) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(IdentityIndexedPropertyGetter);
LocalContext context;
context->Global()->Set(v8_str("obj"), templ->NewInstance());
// Check fast object case.
const char* fast_case_code =
"Object.getOwnPropertyDescriptor(obj, 0).value.toString()";
ExpectString(fast_case_code, "0");
// Check slow case.
const char* slow_case_code =
"obj.x = 1; delete obj.x;"
"Object.getOwnPropertyDescriptor(obj, 1).value.toString()";
ExpectString(slow_case_code, "1");
}
THREADED_TEST(IndexedInterceptorWithNoSetter) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(IdentityIndexedPropertyGetter);
LocalContext context;
context->Global()->Set(v8_str("obj"), templ->NewInstance());
const char* code =
"try {"
" obj[0] = 239;"
" for (var i = 0; i < 100; i++) {"
" var v = obj[0];"
" if (v != 0) throw 'Wrong value ' + v + ' at iteration ' + i;"
" }"
" 'PASSED'"
"} catch(e) {"
" e"
"}";
ExpectString(code, "PASSED");
}
THREADED_TEST(IndexedInterceptorWithAccessorCheck) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(IdentityIndexedPropertyGetter);
LocalContext context;
Local<v8::Object> obj = templ->NewInstance();
obj->TurnOnAccessCheck();
context->Global()->Set(v8_str("obj"), obj);
const char* code =
"try {"
" for (var i = 0; i < 100; i++) {"
" var v = obj[0];"
" if (v != undefined) throw 'Wrong value ' + v + ' at iteration ' + i;"
" }"
" 'PASSED'"
"} catch(e) {"
" e"
"}";
ExpectString(code, "PASSED");
}
THREADED_TEST(IndexedInterceptorWithAccessorCheckSwitchedOn) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(IdentityIndexedPropertyGetter);
LocalContext context;
Local<v8::Object> obj = templ->NewInstance();
context->Global()->Set(v8_str("obj"), obj);
const char* code =
"try {"
" for (var i = 0; i < 100; i++) {"
" var expected = i;"
" if (i == 5) {"
" %EnableAccessChecks(obj);"
" expected = undefined;"
" }"
" var v = obj[i];"
" if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
" if (i == 5) %DisableAccessChecks(obj);"
" }"
" 'PASSED'"
"} catch(e) {"
" e"
"}";
ExpectString(code, "PASSED");
}
THREADED_TEST(IndexedInterceptorWithDifferentIndices) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(IdentityIndexedPropertyGetter);
LocalContext context;
Local<v8::Object> obj = templ->NewInstance();
context->Global()->Set(v8_str("obj"), obj);
const char* code =
"try {"
" for (var i = 0; i < 100; i++) {"
" var v = obj[i];"
" if (v != i) throw 'Wrong value ' + v + ' at iteration ' + i;"
" }"
" 'PASSED'"
"} catch(e) {"
" e"
"}";
ExpectString(code, "PASSED");
}
THREADED_TEST(IndexedInterceptorWithNegativeIndices) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(IdentityIndexedPropertyGetter);
LocalContext context;
Local<v8::Object> obj = templ->NewInstance();
context->Global()->Set(v8_str("obj"), obj);
const char* code =
"try {"
" for (var i = 0; i < 100; i++) {"
" var expected = i;"
" var key = i;"
" if (i == 25) {"
" key = -1;"
" expected = undefined;"
" }"
" if (i == 50) {"
" /* probe minimal Smi number on 32-bit platforms */"
" key = -(1 << 30);"
" expected = undefined;"
" }"
" if (i == 75) {"
" /* probe minimal Smi number on 64-bit platforms */"
" key = 1 << 31;"
" expected = undefined;"
" }"
" var v = obj[key];"
" if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
" }"
" 'PASSED'"
"} catch(e) {"
" e"
"}";
ExpectString(code, "PASSED");
}
THREADED_TEST(IndexedInterceptorWithNotSmiLookup) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(IdentityIndexedPropertyGetter);
LocalContext context;
Local<v8::Object> obj = templ->NewInstance();
context->Global()->Set(v8_str("obj"), obj);
const char* code =
"try {"
" for (var i = 0; i < 100; i++) {"
" var expected = i;"
" var key = i;"
" if (i == 50) {"
" key = 'foobar';"
" expected = undefined;"
" }"
" var v = obj[key];"
" if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
" }"
" 'PASSED'"
"} catch(e) {"
" e"
"}";
ExpectString(code, "PASSED");
}
THREADED_TEST(IndexedInterceptorGoingMegamorphic) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(IdentityIndexedPropertyGetter);
LocalContext context;
Local<v8::Object> obj = templ->NewInstance();
context->Global()->Set(v8_str("obj"), obj);
const char* code =
"var original = obj;"
"try {"
" for (var i = 0; i < 100; i++) {"
" var expected = i;"
" if (i == 50) {"
" obj = {50: 'foobar'};"
" expected = 'foobar';"
" }"
" var v = obj[i];"
" if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
" if (i == 50) obj = original;"
" }"
" 'PASSED'"
"} catch(e) {"
" e"
"}";
ExpectString(code, "PASSED");
}
THREADED_TEST(IndexedInterceptorReceiverTurningSmi) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(IdentityIndexedPropertyGetter);
LocalContext context;
Local<v8::Object> obj = templ->NewInstance();
context->Global()->Set(v8_str("obj"), obj);
const char* code =
"var original = obj;"
"try {"
" for (var i = 0; i < 100; i++) {"
" var expected = i;"
" if (i == 5) {"
" obj = 239;"
" expected = undefined;"
" }"
" var v = obj[i];"
" if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
" if (i == 5) obj = original;"
" }"
" 'PASSED'"
"} catch(e) {"
" e"
"}";
ExpectString(code, "PASSED");
}
THREADED_TEST(IndexedInterceptorOnProto) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(IdentityIndexedPropertyGetter);
LocalContext context;
Local<v8::Object> obj = templ->NewInstance();
context->Global()->Set(v8_str("obj"), obj);
const char* code =
"var o = {__proto__: obj};"
"try {"
" for (var i = 0; i < 100; i++) {"
" var v = o[i];"
" if (v != i) throw 'Wrong value ' + v + ' at iteration ' + i;"
" }"
" 'PASSED'"
"} catch(e) {"
" e"
"}";
ExpectString(code, "PASSED");
}
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 =
global0->Get(v8_str("Object")).As<v8::Object>();
v8::Handle<v8::Object> tostring0 =
object0->Get(v8_str("toString")).As<v8::Object>();
v8::Handle<v8::Object> proto0 =
tostring0->Get(v8_str("__proto__")).As<v8::Object>();
proto0->Set(v8_str("custom"), v8_num(1234));
LocalContext env1;
v8::Handle<v8::Object> global1 =
env1->Global();
v8::Handle<v8::Object> object1 =
global1->Get(v8_str("Object")).As<v8::Object>();
v8::Handle<v8::Object> tostring1 =
object1->Get(v8_str("toString")).As<v8::Object>();
v8::Handle<v8::Object> proto1 =
tostring1->Get(v8_str("__proto__")).As<v8::Object>();
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());
}
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(VoidLiteral) {
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);
ExpectBoolean("undefined == void 0", true);
ExpectBoolean("undetectable == void 0", true);
ExpectBoolean("null == void 0", true);
ExpectBoolean("undefined === void 0", true);
ExpectBoolean("undetectable === void 0", false);
ExpectBoolean("null === void 0", false);
ExpectBoolean("void 0 == undefined", true);
ExpectBoolean("void 0 == undetectable", true);
ExpectBoolean("void 0 == null", true);
ExpectBoolean("void 0 === undefined", true);
ExpectBoolean("void 0 === undetectable", false);
ExpectBoolean("void 0 === null", false);
ExpectString("(function() {"
" try {"
" return x === void 0;"
" } catch(e) {"
" return e.toString();"
" }"
"})()",
"ReferenceError: x is not defined");
ExpectString("(function() {"
" try {"
" return void 0 === x;"
" } catch(e) {"
" return e.toString();"
" }"
"})()",
"ReferenceError: x is not defined");
}
THREADED_TEST(ExtensibleOnUndetectable) {
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);
Local<String> source = v8_str("undetectable.x = 42;"
"undetectable.x");
Local<Script> script = Script::Compile(source);
CHECK_EQ(v8::Integer::New(42), script->Run());
ExpectBoolean("Object.isExtensible(undetectable)", true);
source = v8_str("Object.preventExtensions(undetectable);");
script = Script::Compile(source);
script->Run();
ExpectBoolean("Object.isExtensible(undetectable)", false);
source = v8_str("undetectable.y = 2000;");
script = Script::Compile(source);
script->Run();
ExpectBoolean("undetectable.y == undefined", 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);
}
TEST(UndetectableOptimized) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
LocalContext env;
Local<String> obj = String::NewUndetectable("foo");
env->Global()->Set(v8_str("undetectable"), obj);
env->Global()->Set(v8_str("detectable"), v8_str("bar"));
ExpectString(
"function testBranch() {"
" if (!%_IsUndetectableObject(undetectable)) throw 1;"
" if (%_IsUndetectableObject(detectable)) throw 2;"
"}\n"
"function testBool() {"
" var b1 = !%_IsUndetectableObject(undetectable);"
" var b2 = %_IsUndetectableObject(detectable);"
" if (b1) throw 3;"
" if (b2) throw 4;"
" return b1 == b2;"
"}\n"
"%OptimizeFunctionOnNextCall(testBranch);"
"%OptimizeFunctionOnNextCall(testBool);"
"for (var i = 0; i < 10; i++) {"
" testBranch();"
" testBool();"
"}\n"
"\"PASS\"",
"PASS");
}
template <typename T> static void USE(T) { }
// This test is not intended to be run, just type checked.
static inline 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));
}
THREADED_TEST(NullExtensions) {
v8::HandleScope handle_scope;
v8::RegisterExtension(new Extension("nulltest", NULL));
const char* extension_names[] = { "nulltest" };
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("1+3"))->Run();
CHECK_EQ(result, v8::Integer::New(4));
}
static const char* kEmbeddedExtensionSource =
"function Ret54321(){return 54321;}~~@@$"
"$%% THIS IS A SERIES OF NON-NULL-TERMINATED STRINGS.";
static const int kEmbeddedExtensionSourceValidLen = 34;
THREADED_TEST(ExtensionMissingSourceLength) {
v8::HandleScope handle_scope;
v8::RegisterExtension(new Extension("srclentest_fail",
kEmbeddedExtensionSource));
const char* extension_names[] = { "srclentest_fail" };
v8::ExtensionConfiguration extensions(1, extension_names);
v8::Handle<Context> context = Context::New(&extensions);
CHECK_EQ(0, *context);
}
THREADED_TEST(ExtensionWithSourceLength) {
for (int source_len = kEmbeddedExtensionSourceValidLen - 1;
source_len <= kEmbeddedExtensionSourceValidLen + 1; ++source_len) {
v8::HandleScope handle_scope;
i::ScopedVector<char> extension_name(32);
i::OS::SNPrintF(extension_name, "ext #%d", source_len);
v8::RegisterExtension(new Extension(extension_name.start(),
kEmbeddedExtensionSource, 0, 0,
source_len));
const char* extension_names[1] = { extension_name.start() };
v8::ExtensionConfiguration extensions(1, extension_names);
v8::Handle<Context> context = Context::New(&extensions);
if (source_len == kEmbeddedExtensionSourceValidLen) {
Context::Scope lock(context);
v8::Handle<Value> result = Script::Compile(v8_str("Ret54321()"))->Run();
CHECK_EQ(v8::Integer::New(54321), result);
} else {
// Anything but exactly the right length should fail to compile.
CHECK_EQ(0, *context);
}
}
}
static const char* kEvalExtensionSource1 =
"function UseEval1() {"
" var x = 42;"
" return eval('x');"
"}";
static const char* kEvalExtensionSource2 =
"(function() {"
" var x = 42;"
" function e() {"
" return eval('x');"
" }"
" this.UseEval2 = e;"
"})()";
THREADED_TEST(UseEvalFromExtension) {
v8::HandleScope handle_scope;
v8::RegisterExtension(new Extension("evaltest1", kEvalExtensionSource1));
v8::RegisterExtension(new Extension("evaltest2", kEvalExtensionSource2));
const char* extension_names[] = { "evaltest1", "evaltest2" };
v8::ExtensionConfiguration extensions(2, extension_names);
v8::Handle<Context> context = Context::New(&extensions);
Context::Scope lock(context);
v8::Handle<Value> result = Script::Compile(v8_str("UseEval1()"))->Run();
CHECK_EQ(result, v8::Integer::New(42));
result = Script::Compile(v8_str("UseEval2()"))->Run();
CHECK_EQ(result, v8::Integer::New(42));
}
static const char* kWithExtensionSource1 =
"function UseWith1() {"
" var x = 42;"
" with({x:87}) { return x; }"
"}";
static const char* kWithExtensionSource2 =
"(function() {"
" var x = 42;"
" function e() {"
" with ({x:87}) { return x; }"
" }"
" this.UseWith2 = e;"
"})()";
THREADED_TEST(UseWithFromExtension) {
v8::HandleScope handle_scope;
v8::RegisterExtension(new Extension("withtest1", kWithExtensionSource1));
v8::RegisterExtension(new Extension("withtest2", kWithExtensionSource2));
const char* extension_names[] = { "withtest1", "withtest2" };
v8::ExtensionConfiguration extensions(2, extension_names);
v8::Handle<Context> context = Context::New(&extensions);
Context::Scope lock(context);
v8::Handle<Value> result = Script::Compile(v8_str("UseWith1()"))->Run();
CHECK_EQ(result, v8::Integer::New(87));
result = Script::Compile(v8_str("UseWith2()"))->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 const char* kSyntaxErrorInExtensionSource =
"[";
// Test that a syntax error in an extension does not cause a fatal
// error but results in an empty context.
THREADED_TEST(SyntaxErrorExtensions) {
v8::HandleScope handle_scope;
v8::RegisterExtension(new Extension("syntaxerror",
kSyntaxErrorInExtensionSource));
const char* extension_names[] = { "syntaxerror" };
v8::ExtensionConfiguration extensions(1, extension_names);
v8::Handle<Context> context = Context::New(&extensions);
CHECK(context.IsEmpty());
}
static const char* kExceptionInExtensionSource =
"throw 42";
// Test that an exception when installing an extension does not cause
// a fatal error but results in an empty context.
THREADED_TEST(ExceptionExtensions) {
v8::HandleScope handle_scope;
v8::RegisterExtension(new Extension("exception",
kExceptionInExtensionSource));
const char* extension_names[] = { "exception" };
v8::ExtensionConfiguration extensions(1, extension_names);
v8::Handle<Context> context = Context::New(&extensions);
CHECK(context.IsEmpty());
}
static const char* kNativeCallInExtensionSource =
"function call_runtime_last_index_of(x) {"
" return %StringLastIndexOf(x, 'bob', 10);"
"}";
static const char* kNativeCallTest =
"call_runtime_last_index_of('bobbobboellebobboellebobbob');";
// Test that a native runtime calls are supported in extensions.
THREADED_TEST(NativeCallInExtensions) {
v8::HandleScope handle_scope;
v8::RegisterExtension(new Extension("nativecall",
kNativeCallInExtensionSource));
const char* extension_names[] = { "nativecall" };
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(kNativeCallTest))->Run();
CHECK_EQ(result, v8::Integer::New(3));
}
class NativeFunctionExtension : public Extension {
public:
NativeFunctionExtension(const char* name,
const char* source,
v8::InvocationCallback fun = &Echo)
: Extension(name, source),
function_(fun) { }
virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
v8::Handle<v8::String> name) {
return v8::FunctionTemplate::New(function_);
}
static v8::Handle<v8::Value> Echo(const v8::Arguments& args) {
if (args.Length() >= 1) return (args[0]);
return v8::Undefined();
}
private:
v8::InvocationCallback function_;
};
THREADED_TEST(NativeFunctionDeclaration) {
v8::HandleScope handle_scope;
const char* name = "nativedecl";
v8::RegisterExtension(new NativeFunctionExtension(name,
"native function foo();"));
const char* extension_names[] = { name };
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(42);"))->Run();
CHECK_EQ(result, v8::Integer::New(42));
}
THREADED_TEST(NativeFunctionDeclarationError) {
v8::HandleScope handle_scope;
const char* name = "nativedeclerr";
// Syntax error in extension code.
v8::RegisterExtension(new NativeFunctionExtension(name,
"native\nfunction foo();"));
const char* extension_names[] = { name };
v8::ExtensionConfiguration extensions(1, extension_names);
v8::Handle<Context> context(Context::New(&extensions));
CHECK(context.IsEmpty());
}
THREADED_TEST(NativeFunctionDeclarationErrorEscape) {
v8::HandleScope handle_scope;
const char* name = "nativedeclerresc";
// Syntax error in extension code - escape code in "native" means that
// it's not treated as a keyword.
v8::RegisterExtension(new NativeFunctionExtension(
name,
"nativ\\u0065 function foo();"));
const char* extension_names[] = { name };
v8::ExtensionConfiguration extensions(1, extension_names);
v8::Handle<Context> context(Context::New(&extensions));
CHECK(context.IsEmpty());
}
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();
if (args.IsConstructCall()) {
args.This()->Set(v8_str("data"), args.Data());
return v8::Null();
}
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());
}
THREADED_TEST(NativeFunctionConstructCall) {
v8::RegisterExtension(new FunctionExtension());
v8::HandleScope handle_scope;
static const char* exts[1] = { "functiontest" };
v8::ExtensionConfiguration config(1, exts);
LocalContext context(&config);
for (int i = 0; i < 10; i++) {
// Run a few times to ensure that allocation of objects doesn't
// change behavior of a constructor function.
CHECK_EQ(v8::Integer::New(8),
Script::Compile(v8_str("(new A()).data"))->Run());
CHECK_EQ(v8::Integer::New(7),
Script::Compile(v8_str("(new B()).data"))->Run());
CHECK_EQ(v8::Integer::New(6),
Script::Compile(v8_str("(new C()).data"))->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 < 30; 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;
script->Run();
CHECK(false); // Should not return.
}
static void MissingScriptInfoMessageListener(v8::Handle<v8::Message> message,
v8::Handle<Value> 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();
Whammy* whammy = new Whammy();
templ->SetNamedPropertyHandler(WhammyPropertyGetter,
0, 0, 0, 0,
v8::External::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());
delete whammy;
context.Dispose();
}
static void DisposeAndSetFlag(v8::Persistent<v8::Value> obj, void* data) {
obj.Dispose();
obj.Clear();
*(reinterpret_cast<bool*>(data)) = true;
}
THREADED_TEST(IndependentWeakHandle) {
v8::Persistent<Context> context = Context::New();
Context::Scope context_scope(context);
v8::Persistent<v8::Object> object_a, object_b;
{
v8::HandleScope handle_scope;
object_a = v8::Persistent<v8::Object>::New(v8::Object::New());
object_b = v8::Persistent<v8::Object>::New(v8::Object::New());
}
v8::Isolate* isolate = v8::Isolate::GetCurrent();
bool object_a_disposed = false;
bool object_b_disposed = false;
object_a.MakeWeak(&object_a_disposed, &DisposeAndSetFlag);
object_b.MakeWeak(&object_b_disposed, &DisposeAndSetFlag);
CHECK(!object_a.IsIndependent());
CHECK(!object_b.IsIndependent(isolate));
object_a.MarkIndependent();
object_b.MarkIndependent(isolate);
CHECK(object_a.IsIndependent());
CHECK(object_b.IsIndependent(isolate));
HEAP->PerformScavenge();
CHECK(object_a_disposed);
CHECK(object_b_disposed);
}
static void InvokeScavenge() {
HEAP->PerformScavenge();
}
static void InvokeMarkSweep() {
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
}
static void ForceScavenge(v8::Persistent<v8::Value> obj, void* data) {
obj.Dispose();
obj.Clear();
*(reinterpret_cast<bool*>(data)) = true;
InvokeScavenge();
}
static void ForceMarkSweep(v8::Persistent<v8::Value> obj, void* data) {
obj.Dispose();
obj.Clear();
*(reinterpret_cast<bool*>(data)) = true;
InvokeMarkSweep();
}
THREADED_TEST(GCFromWeakCallbacks) {
v8::Persistent<Context> context = Context::New();
Context::Scope context_scope(context);
static const int kNumberOfGCTypes = 2;
v8::WeakReferenceCallback gc_forcing_callback[kNumberOfGCTypes] =
{&ForceScavenge, &ForceMarkSweep};
typedef void (*GCInvoker)();
GCInvoker invoke_gc[kNumberOfGCTypes] = {&InvokeScavenge, &InvokeMarkSweep};
for (int outer_gc = 0; outer_gc < kNumberOfGCTypes; outer_gc++) {
for (int inner_gc = 0; inner_gc < kNumberOfGCTypes; inner_gc++) {
v8::Persistent<v8::Object> object;
{
v8::HandleScope handle_scope;
object = v8::Persistent<v8::Object>::New(v8::Object::New());
}
bool disposed = false;
object.MakeWeak(&disposed, gc_forcing_callback[inner_gc]);
object.MarkIndependent();
invoke_gc[outer_gc]();
CHECK(disposed);
}
}
}
static void RevivingCallback(v8::Persistent<v8::Value> obj, void* data) {
obj.ClearWeak();
*(reinterpret_cast<bool*>(data)) = true;
}
THREADED_TEST(IndependentHandleRevival) {
v8::Persistent<Context> context = Context::New();
Context::Scope context_scope(context);
v8::Persistent<v8::Object> object;
{
v8::HandleScope handle_scope;
object = v8::Persistent<v8::Object>::New(v8::Object::New());
object->Set(v8_str("x"), v8::Integer::New(1));
v8::Local<String> y_str = v8_str("y");
object->Set(y_str, y_str);
}
bool revived = false;
object.MakeWeak(&revived, &RevivingCallback);
object.MarkIndependent();
HEAP->PerformScavenge();
CHECK(revived);
HEAP->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
{
v8::HandleScope handle_scope;
v8::Local<String> y_str = v8_str("y");
CHECK_EQ(v8::Integer::New(1), object->Get(v8_str("x")));
CHECK(object->Get(y_str)->Equals(y_str));
}
}
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;
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
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 = context->Global()->Get(v8_str("f")).As<Function>();
v8_compile("f(1, 2, 3)")->Run();
}
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);
}
}
static int StrCmp16(uint16_t* a, uint16_t* b) {
while (true) {
if (*a == 0 && *b == 0) return 0;
if (*a != *b) return 0 + *a - *b;
a++;
b++;
}
}
static int StrNCmp16(uint16_t* a, uint16_t* b, int n) {
while (true) {
if (n-- == 0) return 0;
if (*a == 0 && *b == 0) return 0;
if (*a != *b) return 0 + *a - *b;
a++;
b++;
}
}
int GetUtf8Length(Handle<String> str) {
int len = str->Utf8Length();
if (len < 0) {
i::Handle<i::String> istr(v8::Utils::OpenHandle(*str));
i::FlattenString(istr);
len = str->Utf8Length();
}
return len;
}
THREADED_TEST(StringWrite) {
LocalContext context;
v8::HandleScope scope;
v8::Handle<String> str = v8_str("abcde");
// abc<Icelandic eth><Unicode snowman>.
v8::Handle<String> str2 = v8_str("abc\303\260\342\230\203");
v8::Handle<String> str3 = v8::String::New("abc\0def", 7);
const int kStride = 4; // Must match stride in for loops in JS below.
CompileRun(
"var left = '';"
"for (var i = 0; i < 0xd800; i += 4) {"
" left = left + String.fromCharCode(i);"
"}");
CompileRun(
"var right = '';"
"for (var i = 0; i < 0xd800; i += 4) {"
" right = String.fromCharCode(i) + right;"
"}");
v8::Handle<v8::Object> global = Context::GetCurrent()->Global();
Handle<String> left_tree = global->Get(v8_str("left")).As<String>();
Handle<String> right_tree = global->Get(v8_str("right")).As<String>();
CHECK_EQ(5, str2->Length());
CHECK_EQ(0xd800 / kStride, left_tree->Length());
CHECK_EQ(0xd800 / kStride, right_tree->Length());
char buf[100];
char utf8buf[0xd800 * 3];
uint16_t wbuf[100];
int len;
int charlen;
memset(utf8buf, 0x1, 1000);
len = str2->WriteUtf8(utf8buf, sizeof(utf8buf), &charlen);
CHECK_EQ(9, len);
CHECK_EQ(5, charlen);
CHECK_EQ(0, strcmp(utf8buf, "abc\303\260\342\230\203"));
memset(utf8buf, 0x1, 1000);
len = str2->WriteUtf8(utf8buf, 8, &charlen);
CHECK_EQ(8, len);
CHECK_EQ(5, charlen);
CHECK_EQ(0, strncmp(utf8buf, "abc\303\260\342\230\203\1", 9));
memset(utf8buf, 0x1, 1000);
len = str2->WriteUtf8(utf8buf, 7, &charlen);
CHECK_EQ(5, len);
CHECK_EQ(4, charlen);
CHECK_EQ(0, strncmp(utf8buf, "abc\303\260\1", 5));
memset(utf8buf, 0x1, 1000);
len = str2->WriteUtf8(utf8buf, 6, &charlen);
CHECK_EQ(5, len);
CHECK_EQ(4, charlen);
CHECK_EQ(0, strncmp(utf8buf, "abc\303\260\1", 5));
memset(utf8buf, 0x1, 1000);
len = str2->WriteUtf8(utf8buf, 5, &charlen);
CHECK_EQ(5, len);
CHECK_EQ(4, charlen);
CHECK_EQ(0, strncmp(utf8buf, "abc\303\260\1", 5));
memset(utf8buf, 0x1, 1000);
len = str2->WriteUtf8(utf8buf, 4, &charlen);
CHECK_EQ(3, len);
CHECK_EQ(3, charlen);
CHECK_EQ(0, strncmp(utf8buf, "abc\1", 4));
memset(utf8buf, 0x1, 1000);
len = str2->WriteUtf8(utf8buf, 3, &charlen);
CHECK_EQ(3, len);
CHECK_EQ(3, charlen);
CHECK_EQ(0, strncmp(utf8buf, "abc\1", 4));
memset(utf8buf, 0x1, 1000);
len = str2->WriteUtf8(utf8buf, 2, &charlen);
CHECK_EQ(2, len);
CHECK_EQ(2, charlen);
CHECK_EQ(0, strncmp(utf8buf, "ab\1", 3));
memset(utf8buf, 0x1, sizeof(utf8buf));
len = GetUtf8Length(left_tree);
int utf8_expected =
(0x80 + (0x800 - 0x80) * 2 + (0xd800 - 0x800) * 3) / kStride;
CHECK_EQ(utf8_expected, len);
len = left_tree->WriteUtf8(utf8buf, utf8_expected, &charlen);
CHECK_EQ(utf8_expected, len);
CHECK_EQ(0xd800 / kStride, charlen);
CHECK_EQ(0xed, static_cast<unsigned char>(utf8buf[utf8_expected - 3]));
CHECK_EQ(0x9f, static_cast<unsigned char>(utf8buf[utf8_expected - 2]));
CHECK_EQ(0xc0 - kStride,
static_cast<unsigned char>(utf8buf[utf8_expected - 1]));
CHECK_EQ(1, utf8buf[utf8_expected]);
memset(utf8buf, 0x1, sizeof(utf8buf));
len = GetUtf8Length(right_tree);
CHECK_EQ(utf8_expected, len);
len = right_tree->WriteUtf8(utf8buf, utf8_expected, &charlen);
CHECK_EQ(utf8_expected, len);
CHECK_EQ(0xd800 / kStride, charlen);
CHECK_EQ(0xed, static_cast<unsigned char>(utf8buf[0]));
CHECK_EQ(0x9f, static_cast<unsigned char>(utf8buf[1]));
CHECK_EQ(0xc0 - kStride, static_cast<unsigned char>(utf8buf[2]));
CHECK_EQ(1, utf8buf[utf8_expected]);
memset(buf, 0x1, sizeof(buf));
memset(wbuf, 0x1, sizeof(wbuf));
len = str->WriteAscii(buf);
CHECK_EQ(5, len);
len = str->Write(wbuf);
CHECK_EQ(5, len);
CHECK_EQ(0, strcmp("abcde", buf));
uint16_t answer1[] = {'a', 'b', 'c', 'd', 'e', '\0'};
CHECK_EQ(0, StrCmp16(answer1, wbuf));
memset(buf, 0x1, sizeof(buf));
memset(wbuf, 0x1, sizeof(wbuf));
len = str->WriteAscii(buf, 0, 4);
CHECK_EQ(4, len);
len = str->Write(wbuf, 0, 4);
CHECK_EQ(4, len);
CHECK_EQ(0, strncmp("abcd\1", buf, 5));
uint16_t answer2[] = {'a', 'b', 'c', 'd', 0x101};
CHECK_EQ(0, StrNCmp16(answer2, wbuf, 5));
memset(buf, 0x1, sizeof(buf));
memset(wbuf, 0x1, sizeof(wbuf));
len = str->WriteAscii(buf, 0, 5);
CHECK_EQ(5, len);
len = str->Write(wbuf, 0, 5);
CHECK_EQ(5, len);
CHECK_EQ(0, strncmp("abcde\1", buf, 6));
uint16_t answer3[] = {'a', 'b', 'c', 'd', 'e', 0x101};
CHECK_EQ(0, StrNCmp16(answer3, wbuf, 6));
memset(buf, 0x1, sizeof(buf));
memset(wbuf, 0x1, sizeof(wbuf));
len = str->WriteAscii(buf, 0, 6);
CHECK_EQ(5, len);
len = str->Write(wbuf, 0, 6);
CHECK_EQ(5, len);
CHECK_EQ(0, strcmp("abcde", buf));
uint16_t answer4[] = {'a', 'b', 'c', 'd', 'e', '\0'};
CHECK_EQ(0, StrCmp16(answer4, wbuf));
memset(buf, 0x1, sizeof(buf));
memset(wbuf, 0x1, sizeof(wbuf));
len = str->WriteAscii(buf, 4, -1);
CHECK_EQ(1, len);
len = str->Write(wbuf, 4, -1);
CHECK_EQ(1, len);
CHECK_EQ(0, strcmp("e", buf));
uint16_t answer5[] = {'e', '\0'};
CHECK_EQ(0, StrCmp16(answer5, wbuf));
memset(buf, 0x1, sizeof(buf));
memset(wbuf, 0x1, sizeof(wbuf));
len = str->WriteAscii(buf, 4, 6);
CHECK_EQ(1, len);
len = str->Write(wbuf, 4, 6);
CHECK_EQ(1, len);
CHECK_EQ(0, strcmp("e", buf));
CHECK_EQ(0, StrCmp16(answer5, wbuf));
memset(buf, 0x1, sizeof(buf));
memset(wbuf, 0x1, sizeof(wbuf));
len = str->WriteAscii(buf, 4, 1);
CHECK_EQ(1, len);
len = str->Write(wbuf, 4, 1);
CHECK_EQ(1, len);
CHECK_EQ(0, strncmp("e\1", buf, 2));
uint16_t answer6[] = {'e', 0x101};
CHECK_EQ(0, StrNCmp16(answer6, wbuf, 2));
memset(buf, 0x1, sizeof(buf));
memset(wbuf, 0x1, sizeof(wbuf));
len = str->WriteAscii(buf, 3, 1);
CHECK_EQ(1, len);
len = str->Write(wbuf, 3, 1);
CHECK_EQ(1, len);
CHECK_EQ(0, strncmp("d\1", buf, 2));
uint16_t answer7[] = {'d', 0x101};
CHECK_EQ(0, StrNCmp16(answer7, wbuf, 2));
memset(wbuf, 0x1, sizeof(wbuf));
wbuf[5] = 'X';
len = str->Write(wbuf, 0, 6, String::NO_NULL_TERMINATION);
CHECK_EQ(5, len);
CHECK_EQ('X', wbuf[5]);
uint16_t answer8a[] = {'a', 'b', 'c', 'd', 'e'};
uint16_t answer8b[] = {'a', 'b', 'c', 'd', 'e', '\0'};
CHECK_EQ(0, StrNCmp16(answer8a, wbuf, 5));
CHECK_NE(0, StrCmp16(answer8b, wbuf));
wbuf[5] = '\0';
CHECK_EQ(0, StrCmp16(answer8b, wbuf));
memset(buf, 0x1, sizeof(buf));
buf[5] = 'X';
len = str->WriteAscii(buf, 0, 6, String::NO_NULL_TERMINATION);
CHECK_EQ(5, len);
CHECK_EQ('X', buf[5]);
CHECK_EQ(0, strncmp("abcde", buf, 5));
CHECK_NE(0, strcmp("abcde", buf));
buf[5] = '\0';
CHECK_EQ(0, strcmp("abcde", buf));
memset(utf8buf, 0x1, sizeof(utf8buf));
utf8buf[8] = 'X';
len = str2->WriteUtf8(utf8buf, sizeof(utf8buf), &charlen,
String::NO_NULL_TERMINATION);
CHECK_EQ(8, len);
CHECK_EQ('X', utf8buf[8]);
CHECK_EQ(5, charlen);
CHECK_EQ(0, strncmp(utf8buf, "abc\303\260\342\230\203", 8));
CHECK_NE(0, strcmp(utf8buf, "abc\303\260\342\230\203"));
utf8buf[8] = '\0';
CHECK_EQ(0, strcmp(utf8buf, "abc\303\260\342\230\203"));
memset(utf8buf, 0x1, sizeof(utf8buf));
utf8buf[5] = 'X';
len = str->WriteUtf8(utf8buf, sizeof(utf8buf), &charlen,
String::NO_NULL_TERMINATION);
CHECK_EQ(5, len);
CHECK_EQ('X', utf8buf[5]); // Test that the sixth character is untouched.
CHECK_EQ(5, charlen);
utf8buf[5] = '\0';
CHECK_EQ(0, strcmp(utf8buf, "abcde"));
memset(buf, 0x1, sizeof(buf));
len = str3->WriteAscii(buf);
CHECK_EQ(7, len);
CHECK_EQ(0, strcmp("abc def", buf));
memset(buf, 0x1, sizeof(buf));
len = str3->WriteAscii(buf, 0, -1, String::PRESERVE_ASCII_NULL);
CHECK_EQ(7, len);
CHECK_EQ(0, strcmp("abc", buf));
CHECK_EQ(0, buf[3]);
CHECK_EQ(0, strcmp("def", buf + 4));
CHECK_EQ(0, str->WriteAscii(NULL, 0, 0, String::NO_NULL_TERMINATION));
CHECK_EQ(0, str->WriteUtf8(NULL, 0, 0, String::NO_NULL_TERMINATION));
CHECK_EQ(0, str->Write(NULL, 0, 0, String::NO_NULL_TERMINATION));
}
static void Utf16Helper(
LocalContext& context,
const char* name,
const char* lengths_name,
int len) {
Local<v8::Array> a =
Local<v8::Array>::Cast(context->Global()->Get(v8_str(name)));
Local<v8::Array> alens =
Local<v8::Array>::Cast(context->Global()->Get(v8_str(lengths_name)));
for (int i = 0; i < len; i++) {
Local<v8::String> string =
Local<v8::String>::Cast(a->Get(i));
Local<v8::Number> expected_len =
Local<v8::Number>::Cast(alens->Get(i));
#ifndef ENABLE_LATIN_1
CHECK_EQ(expected_len->Value() != string->Length(),
string->MayContainNonAscii());
#endif
int length = GetUtf8Length(string);
CHECK_EQ(static_cast<int>(expected_len->Value()), length);
}
}
static uint16_t StringGet(Handle<String> str, int index) {
i::Handle<i::String> istring =
v8::Utils::OpenHandle(String::Cast(*str));
return istring->Get(index);
}
static void WriteUtf8Helper(
LocalContext& context,
const char* name,
const char* lengths_name,
int len) {
Local<v8::Array> b =
Local<v8::Array>::Cast(context->Global()->Get(v8_str(name)));
Local<v8::Array> alens =
Local<v8::Array>::Cast(context->Global()->Get(v8_str(lengths_name)));
char buffer[1000];
char buffer2[1000];
for (int i = 0; i < len; i++) {
Local<v8::String> string =
Local<v8::String>::Cast(b->Get(i));
Local<v8::Number> expected_len =
Local<v8::Number>::Cast(alens->Get(i));
int utf8_length = static_cast<int>(expected_len->Value());
for (int j = utf8_length + 1; j >= 0; j--) {
memset(reinterpret_cast<void*>(&buffer), 42, sizeof(buffer));
memset(reinterpret_cast<void*>(&buffer2), 42, sizeof(buffer2));
int nchars;
int utf8_written =
string->WriteUtf8(buffer, j, &nchars, String::NO_OPTIONS);
int utf8_written2 =
string->WriteUtf8(buffer2, j, &nchars, String::NO_NULL_TERMINATION);
CHECK_GE(utf8_length + 1, utf8_written);
CHECK_GE(utf8_length, utf8_written2);
for (int k = 0; k < utf8_written2; k++) {
CHECK_EQ(buffer[k], buffer2[k]);
}
CHECK(nchars * 3 >= utf8_written - 1);
CHECK(nchars <= utf8_written);
if (j == utf8_length + 1) {
CHECK_EQ(utf8_written2, utf8_length);
CHECK_EQ(utf8_written2 + 1, utf8_written);
}
CHECK_EQ(buffer[utf8_written], 42);
if (j > utf8_length) {
if (utf8_written != 0) CHECK_EQ(buffer[utf8_written - 1], 0);
if (utf8_written > 1) CHECK_NE(buffer[utf8_written - 2], 42);
Handle<String> roundtrip = v8_str(buffer);
CHECK(roundtrip->Equals(string));
} else {
if (utf8_written != 0) CHECK_NE(buffer[utf8_written - 1], 42);
}
if (utf8_written2 != 0) CHECK_NE(buffer[utf8_written - 1], 42);
if (nchars >= 2) {
uint16_t trail = StringGet(string, nchars - 1);
uint16_t lead = StringGet(string, nchars - 2);
if (((lead & 0xfc00) == 0xd800) &&
((trail & 0xfc00) == 0xdc00)) {
unsigned char u1 = buffer2[utf8_written2 - 4];
unsigned char u2 = buffer2[utf8_written2 - 3];
unsigned char u3 = buffer2[utf8_written2 - 2];
unsigned char u4 = buffer2[utf8_written2 - 1];
CHECK_EQ((u1 & 0xf8), 0xf0);
CHECK_EQ((u2 & 0xc0), 0x80);
CHECK_EQ((u3 & 0xc0), 0x80);
CHECK_EQ((u4 & 0xc0), 0x80);
uint32_t c = 0x10000 + ((lead & 0x3ff) << 10) + (trail & 0x3ff);
CHECK_EQ((u4 & 0x3f), (c & 0x3f));
CHECK_EQ((u3 & 0x3f), ((c >> 6) & 0x3f));
CHECK_EQ((u2 & 0x3f), ((c >> 12) & 0x3f));
CHECK_EQ((u1 & 0x3), c >> 18);
}
}
}
}
}
THREADED_TEST(Utf16) {
LocalContext context;
v8::HandleScope scope;
CompileRun(
"var pad = '01234567890123456789';"
"var p = [];"
"var plens = [20, 3, 3];"
"p.push('01234567890123456789');"
"var lead = 0xd800;"
"var trail = 0xdc00;"
"p.push(String.fromCharCode(0xd800));"
"p.push(String.fromCharCode(0xdc00));"
"var a = [];"
"var b = [];"
"var c = [];"
"var alens = [];"
"for (var i = 0; i < 3; i++) {"
" p[1] = String.fromCharCode(lead++);"
" for (var j = 0; j < 3; j++) {"
" p[2] = String.fromCharCode(trail++);"
" a.push(p[i] + p[j]);"
" b.push(p[i] + p[j]);"
" c.push(p[i] + p[j]);"
" alens.push(plens[i] + plens[j]);"
" }"
"}"
"alens[5] -= 2;" // Here the surrogate pairs match up.
"var a2 = [];"
"var b2 = [];"
"var c2 = [];"
"var a2lens = [];"
"for (var m = 0; m < 9; m++) {"
" for (var n = 0; n < 9; n++) {"
" a2.push(a[m] + a[n]);"
" b2.push(b[m] + b[n]);"
" var newc = 'x' + c[m] + c[n] + 'y';"
" c2.push(newc.substring(1, newc.length - 1));"
" var utf = alens[m] + alens[n];" // And here.
// The 'n's that start with 0xdc.. are 6-8
// The 'm's that end with 0xd8.. are 1, 4 and 7
" if ((m % 3) == 1 && n >= 6) utf -= 2;"
" a2lens.push(utf);"
" }"
"}");
Utf16Helper(context, "a", "alens", 9);
Utf16Helper(context, "a2", "a2lens", 81);
WriteUtf8Helper(context, "b", "alens", 9);
WriteUtf8Helper(context, "b2", "a2lens", 81);
WriteUtf8Helper(context, "c2", "a2lens", 81);
}
static bool SameSymbol(Handle<String> s1, Handle<String> s2) {
i::Handle<i::String> is1(v8::Utils::OpenHandle(*s1));
i::Handle<i::String> is2(v8::Utils::OpenHandle(*s2));
return *is1 == *is2;
}
static void SameSymbolHelper(const char* a, const char* b) {
Handle<String> symbol1 = v8::String::NewSymbol(a);
Handle<String> symbol2 = v8::String::NewSymbol(b);
CHECK(SameSymbol(symbol1, symbol2));
}
THREADED_TEST(Utf16Symbol) {
LocalContext context;
v8::HandleScope scope;
Handle<String> symbol1 = v8::String::NewSymbol("abc");
Handle<String> symbol2 = v8::String::NewSymbol("abc");
CHECK(SameSymbol(symbol1, symbol2));
SameSymbolHelper("\360\220\220\205", // 4 byte encoding.
"\355\240\201\355\260\205"); // 2 3-byte surrogates.
SameSymbolHelper("\355\240\201\355\260\206", // 2 3-byte surrogates.
"\360\220\220\206"); // 4 byte encoding.
SameSymbolHelper("x\360\220\220\205", // 4 byte encoding.
"x\355\240\201\355\260\205"); // 2 3-byte surrogates.
SameSymbolHelper("x\355\240\201\355\260\206", // 2 3-byte surrogates.
"x\360\220\220\206"); // 4 byte encoding.
CompileRun(
"var sym0 = 'benedictus';"
"var sym0b = 'S\303\270ren';"
"var sym1 = '\355\240\201\355\260\207';"
"var sym2 = '\360\220\220\210';"
"var sym3 = 'x\355\240\201\355\260\207';"
"var sym4 = 'x\360\220\220\210';"
"if (sym1.length != 2) throw sym1;"
"if (sym1.charCodeAt(1) != 0xdc07) throw sym1.charCodeAt(1);"
"if (sym2.length != 2) throw sym2;"
"if (sym2.charCodeAt(1) != 0xdc08) throw sym2.charCodeAt(2);"
"if (sym3.length != 3) throw sym3;"
"if (sym3.charCodeAt(2) != 0xdc07) throw sym1.charCodeAt(2);"
"if (sym4.length != 3) throw sym4;"
"if (sym4.charCodeAt(2) != 0xdc08) throw sym2.charCodeAt(2);");
Handle<String> sym0 = v8::String::NewSymbol("benedictus");
Handle<String> sym0b = v8::String::NewSymbol("S\303\270ren");
Handle<String> sym1 = v8::String::NewSymbol("\355\240\201\355\260\207");
Handle<String> sym2 = v8::String::NewSymbol("\360\220\220\210");
Handle<String> sym3 = v8::String::NewSymbol("x\355\240\201\355\260\207");
Handle<String> sym4 = v8::String::NewSymbol("x\360\220\220\210");
v8::Local<v8::Object> global = context->Global();
Local<Value> s0 = global->Get(v8_str("sym0"));
Local<Value> s0b = global->Get(v8_str("sym0b"));
Local<Value> s1 = global->Get(v8_str("sym1"));
Local<Value> s2 = global->Get(v8_str("sym2"));
Local<Value> s3 = global->Get(v8_str("sym3"));
Local<Value> s4 = global->Get(v8_str("sym4"));
CHECK(SameSymbol(sym0, Handle<String>(String::Cast(*s0))));
CHECK(SameSymbol(sym0b, Handle<String>(String::Cast(*s0b))));
CHECK(SameSymbol(sym1, Handle<String>(String::Cast(*s1))));
CHECK(SameSymbol(sym2, Handle<String>(String::Cast(*s2))));
CHECK(SameSymbol(sym3, Handle<String>(String::Cast(*s3))));
CHECK(SameSymbol(sym4, Handle<String>(String::Cast(*s4))));
}
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());
CHECK(range_error.As<v8::Object>()->Get(message)->Equals(foo));
v8::Handle<Value> reference_error = v8::Exception::ReferenceError(foo);
CHECK(reference_error->IsObject());
CHECK(reference_error.As<v8::Object>()->Get(message)->Equals(foo));
v8::Handle<Value> syntax_error = v8::Exception::SyntaxError(foo);
CHECK(syntax_error->IsObject());
CHECK(syntax_error.As<v8::Object>()->Get(message)->Equals(foo));
v8::Handle<Value> type_error = v8::Exception::TypeError(foo);
CHECK(type_error->IsObject());
CHECK(type_error.As<v8::Object>()->Get(message)->Equals(foo));
v8::Handle<Value> error = v8::Exception::Error(foo);
CHECK(error->IsObject());
CHECK(error.As<v8::Object>()->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);
}
static const char* script_resource_name = "ExceptionInNativeScript.js";
static void ExceptionInNativeScriptTestListener(v8::Handle<v8::Message> message,
v8::Handle<Value>) {
v8::Handle<v8::Value> name_val = message->GetScriptResourceName();
CHECK(!name_val.IsEmpty() && name_val->IsString());
v8::String::AsciiValue name(message->GetScriptResourceName());
CHECK_EQ(script_resource_name, *name);
CHECK_EQ(3, message->GetLineNumber());
v8::String::AsciiValue source_line(message->GetSourceLine());
CHECK_EQ(" new o.foo();", *source_line);
}
TEST(ExceptionInNativeScript) {
v8::HandleScope scope;
LocalContext env;
v8::V8::AddMessageListener(ExceptionInNativeScriptTestListener);
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() {\n"
" var o = {};\n"
" new o.foo();\n"
"};"), v8::String::New(script_resource_name))->Run();
Local<Value> trouble = global->Get(v8_str("trouble"));
CHECK(trouble->IsFunction());
Function::Cast(*trouble)->Call(global, 0, NULL);
v8::V8::RemoveMessageListeners(ExceptionInNativeScriptTestListener);
}
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 env2 and add a reference to it in env1.
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<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 env2's global, and reuse the global object of env2
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->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();
}
TEST(DetachAndReattachGlobal) {
v8::HandleScope scope;
LocalContext env1;
// Create second environment.
v8::Persistent<Context> env2 = Context::New();
Local<Value> foo = v8_str("foo");
// Set same security token for env1 and env2.
env1->SetSecurityToken(foo);
env2->SetSecurityToken(foo);
// Create a property on the global object in env2.
{
v8::Context::Scope scope(env2);
env2->Global()->Set(v8_str("p"), v8::Integer::New(42));
}
// Create a reference to env2 global from env1 global.
env1->Global()->Set(v8_str("other"), env2->Global());
// Check that we have access to other.p in env2 from env1.
Local<Value> result = CompileRun("other.p");
CHECK(result->IsInt32());
CHECK_EQ(42, result->Int32Value());
// Hold on to global from env2 and detach global from env2.
Local<v8::Object> global2 = env2->Global();
env2->DetachGlobal();
// Check that the global has been detached. No other.p property can
// be found.
result = CompileRun("other.p");
CHECK(result->IsUndefined());
// Reuse global2 for env3.
v8::Persistent<Context> env3 =
Context::New(0, v8::Handle<v8::ObjectTemplate>(), global2);
CHECK_EQ(global2, env3->Global());
// Start by using the same security token for env3 as for env1 and env2.
env3->SetSecurityToken(foo);
// Create a property on the global object in env3.
{
v8::Context::Scope scope(env3);
env3->Global()->Set(v8_str("p"), v8::Integer::New(24));
}
// Check that other.p is now the property in env3 and that we have access.
result = CompileRun("other.p");
CHECK(result->IsInt32());
CHECK_EQ(24, result->Int32Value());
// Change security token for env3 to something different from env1 and env2.
env3->SetSecurityToken(v8_str("bar"));
// Check that we do not have access to other.p in env1. |other| is now
// the global object for env3 which has a different security token,
// so access should be blocked.
result = CompileRun("other.p");
CHECK(result->IsUndefined());
// Detach the global for env3 and reattach it to env2.
env3->DetachGlobal();
env2->ReattachGlobal(global2);
// Check that we have access to other.p again in env1. |other| is now
// the global object for env2 which has the same security token as env1.
result = CompileRun("other.p");
CHECK(result->IsInt32());
CHECK_EQ(42, result->Int32Value());
env2.Dispose();
env3.Dispose();
}
static bool allowed_access_type[v8::ACCESS_KEYS + 1] = { false };
static bool NamedAccessBlocker(Local<v8::Object> global,
Local<Value> name,
v8::AccessType type,
Local<Value> data) {
return Context::GetCurrent()->Global()->Equals(global) ||
allowed_access_type[type];
}
static bool IndexedAccessBlocker(Local<v8::Object> global,
uint32_t key,
v8::AccessType type,
Local<Value> data) {
return Context::GetCurrent()->Global()->Equals(global) ||
allowed_access_type[type];
}
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.
}
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();
// Define a property with JS getter and setter.
CompileRun(
"function getter() { return 'getter'; };\n"
"function setter() { return 'setter'; }\n"
"Object.defineProperty(this, 'js_accessor_p', {get:getter, set:setter})");
Local<Value> getter = global0->Get(v8_str("getter"));
Local<Value> setter = global0->Get(v8_str("setter"));
// And define normal element.
global0->Set(239, v8_str("239"));
// Define an element with JS getter and setter.
CompileRun(
"function el_getter() { return 'el_getter'; };\n"
"function el_setter() { return 'el_setter'; };\n"
"Object.defineProperty(this, '42', {get: el_getter, set: el_setter});");
Local<Value> el_getter = global0->Get(v8_str("el_getter"));
Local<Value> el_setter = global0->Get(v8_str("el_setter"));
v8::HandleScope scope1;
v8::Persistent<Context> context1 = Context::New();
context1->Enter();
v8::Handle<v8::Object> global1 = context1->Global();
global1->Set(v8_str("other"), global0);
// Access blocked property.
CompileRun("other.blocked_prop = 1");
ExpectUndefined("other.blocked_prop");
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'blocked_prop')");
ExpectFalse("propertyIsEnumerable.call(other, 'blocked_prop')");
// Enable ACCESS_HAS
allowed_access_type[v8::ACCESS_HAS] = true;
ExpectUndefined("other.blocked_prop");
// ... and now we can get the descriptor...
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'blocked_prop').value");
// ... and enumerate the property.
ExpectTrue("propertyIsEnumerable.call(other, 'blocked_prop')");
allowed_access_type[v8::ACCESS_HAS] = false;
// Access blocked element.
CompileRun("other[239] = 1");
ExpectUndefined("other[239]");
ExpectUndefined("Object.getOwnPropertyDescriptor(other, '239')");
ExpectFalse("propertyIsEnumerable.call(other, '239')");
// Enable ACCESS_HAS
allowed_access_type[v8::ACCESS_HAS] = true;
ExpectUndefined("other[239]");
// ... and now we can get the descriptor...
ExpectUndefined("Object.getOwnPropertyDescriptor(other, '239').value");
// ... and enumerate the property.
ExpectTrue("propertyIsEnumerable.call(other, '239')");
allowed_access_type[v8::ACCESS_HAS] = false;
// Access a property with JS accessor.
CompileRun("other.js_accessor_p = 2");
ExpectUndefined("other.js_accessor_p");
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p')");
// Enable ACCESS_HAS.
allowed_access_type[v8::ACCESS_HAS] = true;
ExpectUndefined("other.js_accessor_p");
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').get");
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').set");
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').value");
allowed_access_type[v8::ACCESS_HAS] = false;
// Enable both ACCESS_HAS and ACCESS_GET.
allowed_access_type[v8::ACCESS_HAS] = true;
allowed_access_type[v8::ACCESS_GET] = true;
ExpectString("other.js_accessor_p", "getter");
ExpectObject(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').get", getter);
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').set");
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').value");
allowed_access_type[v8::ACCESS_GET] = false;
allowed_access_type[v8::ACCESS_HAS] = false;
// Enable both ACCESS_HAS and ACCESS_SET.
allowed_access_type[v8::ACCESS_HAS] = true;
allowed_access_type[v8::ACCESS_SET] = true;
ExpectUndefined("other.js_accessor_p");
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').get");
ExpectObject(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').set", setter);
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').value");
allowed_access_type[v8::ACCESS_SET] = false;
allowed_access_type[v8::ACCESS_HAS] = false;
// Enable both ACCESS_HAS, ACCESS_GET and ACCESS_SET.
allowed_access_type[v8::ACCESS_HAS] = true;
allowed_access_type[v8::ACCESS_GET] = true;
allowed_access_type[v8::ACCESS_SET] = true;
ExpectString("other.js_accessor_p", "getter");
ExpectObject(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').get", getter);
ExpectObject(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').set", setter);
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'js_accessor_p').value");
allowed_access_type[v8::ACCESS_SET] = false;
allowed_access_type[v8::ACCESS_GET] = false;
allowed_access_type[v8::ACCESS_HAS] = false;
// Access an element with JS accessor.
CompileRun("other[42] = 2");
ExpectUndefined("other[42]");
ExpectUndefined("Object.getOwnPropertyDescriptor(other, '42')");
// Enable ACCESS_HAS.
allowed_access_type[v8::ACCESS_HAS] = true;
ExpectUndefined("other[42]");
ExpectUndefined("Object.getOwnPropertyDescriptor(other, '42').get");
ExpectUndefined("Object.getOwnPropertyDescriptor(other, '42').set");
ExpectUndefined("Object.getOwnPropertyDescriptor(other, '42').value");
allowed_access_type[v8::ACCESS_HAS] = false;
// Enable both ACCESS_HAS and ACCESS_GET.
allowed_access_type[v8::ACCESS_HAS] = true;
allowed_access_type[v8::ACCESS_GET] = true;
ExpectString("other[42]", "el_getter");
ExpectObject("Object.getOwnPropertyDescriptor(other, '42').get", el_getter);
ExpectUndefined("Object.getOwnPropertyDescriptor(other, '42').set");
ExpectUndefined("Object.getOwnPropertyDescriptor(other, '42').value");
allowed_access_type[v8::ACCESS_GET] = false;
allowed_access_type[v8::ACCESS_HAS] = false;
// Enable both ACCESS_HAS and ACCESS_SET.
allowed_access_type[v8::ACCESS_HAS] = true;
allowed_access_type[v8::ACCESS_SET] = true;
ExpectUndefined("other[42]");
ExpectUndefined("Object.getOwnPropertyDescriptor(other, '42').get");
ExpectObject("Object.getOwnPropertyDescriptor(other, '42').set", el_setter);
ExpectUndefined("Object.getOwnPropertyDescriptor(other, '42').value");
allowed_access_type[v8::ACCESS_SET] = false;
allowed_access_type[v8::ACCESS_HAS] = false;
// Enable both ACCESS_HAS, ACCESS_GET and ACCESS_SET.
allowed_access_type[v8::ACCESS_HAS] = true;
allowed_access_type[v8::ACCESS_GET] = true;
allowed_access_type[v8::ACCESS_SET] = true;
ExpectString("other[42]", "el_getter");
ExpectObject("Object.getOwnPropertyDescriptor(other, '42').get", el_getter);
ExpectObject("Object.getOwnPropertyDescriptor(other, '42').set", el_setter);
ExpectUndefined("Object.getOwnPropertyDescriptor(other, '42').value");
allowed_access_type[v8::ACCESS_SET] = false;
allowed_access_type[v8::ACCESS_GET] = false;
allowed_access_type[v8::ACCESS_HAS] = false;
v8::Handle<Value> value;
// Access accessible property
value = CompileRun("other.accessible_prop = 3");
CHECK(value->IsNumber());
CHECK_EQ(3, value->Int32Value());
CHECK_EQ(3, g_echo_value);
value = CompileRun("other.accessible_prop");
CHECK(value->IsNumber());
CHECK_EQ(3, value->Int32Value());
value = CompileRun(
"Object.getOwnPropertyDescriptor(other, 'accessible_prop').value");
CHECK(value->IsNumber());
CHECK_EQ(3, value->Int32Value());
value = CompileRun("propertyIsEnumerable.call(other, 'accessible_prop')");
CHECK(value->IsTrue());
// Enumeration doesn't enumerate accessors from inaccessible objects in
// the prototype chain even if the accessors are in themselves accessible.
value =
CompileRun("(function(){var obj = {'__proto__':other};"
"for (var p in obj)"
" if (p == 'accessible_prop' || p == 'blocked_prop') {"
" return false;"
" }"
"return true;})()");
CHECK(value->IsTrue());
context1->Exit();
context0->Exit();
context1.Dispose();
context0.Dispose();
}
TEST(AccessControlES5) {
v8::HandleScope handle_scope;
v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
global_template->SetAccessCheckCallbacks(NamedAccessBlocker,
IndexedAccessBlocker);
// Add accessible accessor.
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::Persistent<Context> context1 = Context::New();
context1->Enter();
v8::Handle<v8::Object> global1 = context1->Global();
global1->Set(v8_str("other"), global0);
// Regression test for issue 1154.
ExpectTrue("Object.keys(other).indexOf('blocked_prop') == -1");
ExpectUndefined("other.blocked_prop");
// Regression test for issue 1027.
CompileRun("Object.defineProperty(\n"
" other, 'blocked_prop', {configurable: false})");
ExpectUndefined("other.blocked_prop");
ExpectUndefined(
"Object.getOwnPropertyDescriptor(other, 'blocked_prop')");
// Regression test for issue 1171.
ExpectTrue("Object.isExtensible(other)");
CompileRun("Object.preventExtensions(other)");
ExpectTrue("Object.isExtensible(other)");
// Object.seal and Object.freeze.
CompileRun("Object.freeze(other)");
ExpectTrue("Object.isExtensible(other)");
CompileRun("Object.seal(other)");
ExpectTrue("Object.isExtensible(other)");
// Regression test for issue 1250.
// Make sure that we can set the accessible accessors value using normal
// assignment.
CompileRun("other.accessible_prop = 42");
CHECK_EQ(42, g_echo_value);
v8::Handle<Value> value;
// We follow Safari in ignoring assignments to host object accessors.
CompileRun("Object.defineProperty(other, 'accessible_prop', {value: -1})");
value = CompileRun("other.accessible_prop == 42");
CHECK(value->IsTrue());
}
static bool GetOwnPropertyNamesNamedBlocker(Local<v8::Object> global,
Local<Value> name,
v8::AccessType type,
Local<Value> data) {
return false;
}
static bool GetOwnPropertyNamesIndexedBlocker(Local<v8::Object> global,
uint32_t key,
v8::AccessType type,
Local<Value> data) {
return false;
}
THREADED_TEST(AccessControlGetOwnPropertyNames) {
v8::HandleScope handle_scope;
v8::Handle<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New();
obj_template->Set(v8_str("x"), v8::Integer::New(42));
obj_template->SetAccessCheckCallbacks(GetOwnPropertyNamesNamedBlocker,
GetOwnPropertyNamesIndexedBlocker);
// Create an environment
v8::Persistent<Context> context0 = Context::New(NULL, obj_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);
global1->Set(v8_str("object"), obj_template->NewInstance());
v8::Handle<Value> value;
// Attempt to get the property names of the other global object and
// of an object that requires access checks. Accessing the other
// global object should be blocked by access checks on the global
// proxy object. Accessing the object that requires access checks
// is blocked by the access checks on the object itself.
value = CompileRun("Object.getOwnPropertyNames(other).length == 0");
CHECK(value->IsTrue());
value = CompileRun("Object.getOwnPropertyNames(object).length == 0");
CHECK(value->IsTrue());
context1->Exit();
context0->Exit();
context1.Dispose();
context0.Dispose();
}
static v8::Handle<v8::Array> NamedPropertyEnumerator(const AccessorInfo& info) {
v8::Handle<v8::Array> result = v8::Array::New(1);
result->Set(0, v8_str("x"));
return result;
}
THREADED_TEST(GetOwnPropertyNamesWithInterceptor) {
v8::HandleScope handle_scope;
v8::Handle<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New();
obj_template->Set(v8_str("x"), v8::Integer::New(42));
obj_template->SetNamedPropertyHandler(NULL, NULL, NULL, NULL,
NamedPropertyEnumerator);
LocalContext context;
v8::Handle<v8::Object> global = context->Global();
global->Set(v8_str("object"), obj_template->NewInstance());
v8::Handle<Value> value =
CompileRun("Object.getOwnPropertyNames(object).join(',')");
CHECK_EQ(v8_str("x"), value);
}
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 = name.As<String>()->WriteAscii(buf);
CHECK_EQ(4, len);
uint16_t buf2[100];
memset(buf, 0x1, sizeof(buf));
len = name.As<String>()->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));
// The script to check how Crankshaft compiles missing global function
// invocations. function g is not defined and should throw on call.
const char* script =
"function wrapper(call) {"
" var x = 0, y = 1;"
" for (var i = 0; i < 1000; i++) {"
" x += i * 100;"
" y += i * 100;"
" }"
" if (call) g();"
"}"
"for (var i = 0; i < 17; i++) wrapper(false);"
"var thrown = 0;"
"try { wrapper(true); } catch (e) { thrown = 1; };"
"thrown";
{
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());
value = Script::Compile(v8_str(script))->Run();
CHECK_EQ(1, 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());
value = Script::Compile(v8_str(script))->Run();
CHECK_EQ(1, value->Int32Value());
}
}
THREADED_TEST(CallKnownGlobalReceiver) {
v8::HandleScope handle_scope;
Local<Value> global_object;
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
Local<ObjectTemplate> instance_template = t->InstanceTemplate();
// The script to check that we leave global object not
// global object proxy on stack when we deoptimize from inside
// arguments evaluation.
// To provoke error we need to both force deoptimization
// from arguments evaluation and to force CallIC to take
// CallIC_Miss code path that can't cope with global proxy.
const char* script =
"function bar(x, y) { try { } finally { } }"
"function baz(x) { try { } finally { } }"
"function bom(x) { try { } finally { } }"
"function foo(x) { bar([x], bom(2)); }"
"for (var i = 0; i < 10000; i++) foo(1);"
"foo";
Local<Value> foo;
{
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();
foo = Script::Compile(v8_str(script))->Run();
}
{
// Create new environment reusing the global object.
LocalContext env(NULL, instance_template, global_object);
env->Global()->Set(v8_str("foo"), foo);
Script::Compile(v8_str("foo()"))->Run();
}
}
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();
proto->Set(v8_str("f"),
v8::FunctionTemplate::New(ShadowFunctionCallback, Local<Value>()));
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("this.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 = 43"))->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(proto.As<v8::Object>()->Get(v8_str("z"))->IsUndefined());
}
THREADED_TEST(HiddenPrototypeSet) {
v8::HandleScope handle_scope;
LocalContext context;
Local<v8::FunctionTemplate> ot = v8::FunctionTemplate::New();
Local<v8::FunctionTemplate> ht = v8::FunctionTemplate::New();
ht->SetHiddenPrototype(true);
Local<v8::FunctionTemplate> pt = v8::FunctionTemplate::New();
ht->InstanceTemplate()->Set(v8_str("x"), v8_num(0));
Local<v8::Object> o = ot->GetFunction()->NewInstance();
Local<v8::Object> h = ht->GetFunction()->NewInstance();
Local<v8::Object> p = pt->GetFunction()->NewInstance();
o->Set(v8_str("__proto__"), h);
h->Set(v8_str("__proto__"), p);
// Setting a property that exists on the hidden prototype goes there.
o->Set(v8_str("x"), v8_num(7));
CHECK_EQ(7, o->Get(v8_str("x"))->Int32Value());
CHECK_EQ(7, h->Get(v8_str("x"))->Int32Value());
CHECK(p->Get(v8_str("x"))->IsUndefined());
// Setting a new property should not be forwarded to the hidden prototype.
o->Set(v8_str("y"), v8_num(6));
CHECK_EQ(6, o->Get(v8_str("y"))->Int32Value());
CHECK(h->Get(v8_str("y"))->IsUndefined());
CHECK(p->Get(v8_str("y"))->IsUndefined());
// Setting a property that only exists on a prototype of the hidden prototype
// is treated normally again.
p->Set(v8_str("z"), v8_num(8));
CHECK_EQ(8, o->Get(v8_str("z"))->Int32Value());
CHECK_EQ(8, h->Get(v8_str("z"))->Int32Value());
CHECK_EQ(8, p->Get(v8_str("z"))->Int32Value());
o->Set(v8_str("z"), v8_num(9));
CHECK_EQ(9, o->Get(v8_str("z"))->Int32Value());
CHECK_EQ(8, h->Get(v8_str("z"))->Int32Value());
CHECK_EQ(8, p->Get(v8_str("z"))->Int32Value());
}
// Regression test for issue 2457.
THREADED_TEST(HiddenPrototypeIdentityHash) {
v8::HandleScope handle_scope;
LocalContext context;
Handle<FunctionTemplate> t = FunctionTemplate::New();
t->SetHiddenPrototype(true);
t->InstanceTemplate()->Set(v8_str("foo"), v8_num(75));
Handle<Object> p = t->GetFunction()->NewInstance();
Handle<Object> o = Object::New();
o->SetPrototype(p);
int hash = o->GetIdentityHash();
USE(hash);
o->Set(v8_str("foo"), v8_num(42));
ASSERT_EQ(hash, o->GetIdentityHash());
}
THREADED_TEST(SetPrototype) {
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 does not skip hidden prototypes.
CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value());
CHECK(o0->SetPrototype(o1));
CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value());
CHECK_EQ(1, o0->Get(v8_str("y"))->Int32Value());
CHECK(o1->SetPrototype(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());
CHECK(o2->SetPrototype(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_EQ(proto.As<v8::Object>(), o3);
// However, Object::GetPrototype ignores hidden prototype.
Local<Value> proto0 = o0->GetPrototype();
CHECK(proto0->IsObject());
CHECK_EQ(proto0.As<v8::Object>(), o1);
Local<Value> proto1 = o1->GetPrototype();
CHECK(proto1->IsObject());
CHECK_EQ(proto1.As<v8::Object>(), o2);
Local<Value> proto2 = o2->GetPrototype();
CHECK(proto2->IsObject());
CHECK_EQ(proto2.As<v8::Object>(), o3);
}
// Getting property names of an object with a prototype chain that
// triggers dictionary elements in GetLocalPropertyNames() shouldn't
// crash the runtime.
THREADED_TEST(Regress91517) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope handle_scope;
LocalContext context;
Local<v8::FunctionTemplate> t1 = v8::FunctionTemplate::New();
t1->SetHiddenPrototype(true);
t1->InstanceTemplate()->Set(v8_str("foo"), v8_num(1));
Local<v8::FunctionTemplate> t2 = v8::FunctionTemplate::New();
t2->SetHiddenPrototype(true);
t2->InstanceTemplate()->Set(v8_str("fuz1"), v8_num(2));
t2->InstanceTemplate()->Set(v8_str("objects"), v8::Object::New());
t2->InstanceTemplate()->Set(v8_str("fuz2"), v8_num(2));
Local<v8::FunctionTemplate> t3 = v8::FunctionTemplate::New();
t3->SetHiddenPrototype(true);
t3->InstanceTemplate()->Set(v8_str("boo"), v8_num(3));
Local<v8::FunctionTemplate> t4 = v8::FunctionTemplate::New();
t4->InstanceTemplate()->Set(v8_str("baz"), v8_num(4));
// Force dictionary-based properties.
i::ScopedVector<char> name_buf(1024);
for (int i = 1; i <= 1000; i++) {
i::OS::SNPrintF(name_buf, "sdf%d", i);
t2->InstanceTemplate()->Set(v8_str(name_buf.start()), v8_num(2));
}
Local<v8::Object> o1 = t1->GetFunction()->NewInstance();
Local<v8::Object> o2 = t2->GetFunction()->NewInstance();
Local<v8::Object> o3 = t3->GetFunction()->NewInstance();
Local<v8::Object> o4 = t4->GetFunction()->NewInstance();
// Create prototype chain of hidden prototypes.
CHECK(o4->SetPrototype(o3));
CHECK(o3->SetPrototype(o2));
CHECK(o2->SetPrototype(o1));
// Call the runtime version of GetLocalPropertyNames() on the natively
// created object through JavaScript.
context->Global()->Set(v8_str("obj"), o4);
CompileRun("var names = %GetLocalPropertyNames(obj);");
ExpectInt32("names.length", 1006);
ExpectTrue("names.indexOf(\"baz\") >= 0");
ExpectTrue("names.indexOf(\"boo\") >= 0");
ExpectTrue("names.indexOf(\"foo\") >= 0");
ExpectTrue("names.indexOf(\"fuz1\") >= 0");
ExpectTrue("names.indexOf(\"fuz2\") >= 0");
ExpectFalse("names[1005] == undefined");
}
THREADED_TEST(FunctionReadOnlyPrototype) {
v8::HandleScope handle_scope;
LocalContext context;
Local<v8::FunctionTemplate> t1 = v8::FunctionTemplate::New();
t1->PrototypeTemplate()->Set(v8_str("x"), v8::Integer::New(42));
t1->ReadOnlyPrototype();
context->Global()->Set(v8_str("func1"), t1->GetFunction());
// Configured value of ReadOnly flag.
CHECK(CompileRun(
"(function() {"
" descriptor = Object.getOwnPropertyDescriptor(func1, 'prototype');"
" return (descriptor['writable'] == false);"
"})()")->BooleanValue());
CHECK_EQ(42, CompileRun("func1.prototype.x")->Int32Value());
CHECK_EQ(42,
CompileRun("func1.prototype = {}; func1.prototype.x")->Int32Value());
Local<v8::FunctionTemplate> t2 = v8::FunctionTemplate::New();
t2->PrototypeTemplate()->Set(v8_str("x"), v8::Integer::New(42));
context->Global()->Set(v8_str("func2"), t2->GetFunction());
// Default value of ReadOnly flag.
CHECK(CompileRun(
"(function() {"
" descriptor = Object.getOwnPropertyDescriptor(func2, 'prototype');"
" return (descriptor['writable'] == true);"
"})()")->BooleanValue());
CHECK_EQ(42, CompileRun("func2.prototype.x")->Int32Value());
}
THREADED_TEST(SetPrototypeThrows) {
v8::HandleScope handle_scope;
LocalContext context;
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
Local<v8::Object> o0 = t->GetFunction()->NewInstance();
Local<v8::Object> o1 = t->GetFunction()->NewInstance();
CHECK(o0->SetPrototype(o1));
// If setting the prototype leads to the cycle, SetPrototype should
// return false and keep VM in sane state.
v8::TryCatch try_catch;
CHECK(!o1->SetPrototype(o0));
CHECK(!try_catch.HasCaught());
ASSERT(!i::Isolate::Current()->has_pending_exception());
CHECK_EQ(42, CompileRun("function f() { return 42; }; f()")->Int32Value());
}
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));
CHECK(obj->IsJSObject());
Local<Value> value = CompileRun("(new Fun()).constructor === Fun");
CHECK(value->BooleanValue());
}
static Handle<Value> ConstructorCallback(const Arguments& args) {
ApiTestFuzzer::Fuzz();
Local<Object> This;
if (args.IsConstructCall()) {
Local<Object> Holder = args.Holder();
This = Object::New();
Local<Value> proto = Holder->GetPrototype();
if (proto->IsObject()) {
This->SetPrototype(proto);
}
} else {
This = args.This();
}
This->Set(v8_str("a"), args[0]);
return This;
}
static Handle<Value> FakeConstructorCallback(const Arguments& args) {
ApiTestFuzzer::Fuzz();
return args[0];
}
THREADED_TEST(ConstructorForObject) {
v8::HandleScope handle_scope;
LocalContext context;
{ Local<ObjectTemplate> instance_template = ObjectTemplate::New();
instance_template->SetCallAsFunctionHandler(ConstructorCallback);
Local<Object> instance = instance_template->NewInstance();
context->Global()->Set(v8_str("obj"), instance);
v8::TryCatch try_catch;
Local<Value> value;
CHECK(!try_catch.HasCaught());
// Call the Object's constructor with a 32-bit signed integer.
value = CompileRun("(function() { var o = new obj(28); return o.a; })()");
CHECK(!try_catch.HasCaught());
CHECK(value->IsInt32());
CHECK_EQ(28, value->Int32Value());
Local<Value> args1[] = { v8_num(28) };
Local<Value> value_obj1 = instance->CallAsConstructor(1, args1);
CHECK(value_obj1->IsObject());
Local<Object> object1 = Local<Object>::Cast(value_obj1);
value = object1->Get(v8_str("a"));
CHECK(value->IsInt32());
CHECK(!try_catch.HasCaught());
CHECK_EQ(28, value->Int32Value());
// Call the Object's constructor with a String.
value = CompileRun(
"(function() { var o = new obj('tipli'); return o.a; })()");
CHECK(!try_catch.HasCaught());
CHECK(value->IsString());
String::AsciiValue string_value1(value->ToString());
CHECK_EQ("tipli", *string_value1);
Local<Value> args2[] = { v8_str("tipli") };
Local<Value> value_obj2 = instance->CallAsConstructor(1, args2);
CHECK(value_obj2->IsObject());
Local<Object> object2 = Local<Object>::Cast(value_obj2);
value = object2->Get(v8_str("a"));
CHECK(!try_catch.HasCaught());
CHECK(value->IsString());
String::AsciiValue string_value2(value->ToString());
CHECK_EQ("tipli", *string_value2);
// Call the Object's constructor with a Boolean.
value = CompileRun("(function() { var o = new obj(true); return o.a; })()");
CHECK(!try_catch.HasCaught());
CHECK(value->IsBoolean());
CHECK_EQ(true, value->BooleanValue());
Handle<Value> args3[] = { v8::True() };
Local<Value> value_obj3 = instance->CallAsConstructor(1, args3);
CHECK(value_obj3->IsObject());
Local<Object> object3 = Local<Object>::Cast(value_obj3);
value = object3->Get(v8_str("a"));
CHECK(!try_catch.HasCaught());
CHECK(value->IsBoolean());
CHECK_EQ(true, value->BooleanValue());
// Call the Object's constructor with undefined.
Handle<Value> args4[] = { v8::Undefined() };
Local<Value> value_obj4 = instance->CallAsConstructor(1, args4);
CHECK(value_obj4->IsObject());
Local<Object> object4 = Local<Object>::Cast(value_obj4);
value = object4->Get(v8_str("a"));
CHECK(!try_catch.HasCaught());
CHECK(value->IsUndefined());
// Call the Object's constructor with null.
Handle<Value> args5[] = { v8::Null() };
Local<Value> value_obj5 = instance->CallAsConstructor(1, args5);
CHECK(value_obj5->IsObject());
Local<Object> object5 = Local<Object>::Cast(value_obj5);
value = object5->Get(v8_str("a"));
CHECK(!try_catch.HasCaught());
CHECK(value->IsNull());
}
// Check exception handling when there is no constructor set for the Object.
{ Local<ObjectTemplate> instance_template = ObjectTemplate::New();
Local<Object> instance = instance_template->NewInstance();
context->Global()->Set(v8_str("obj2"), instance);
v8::TryCatch try_catch;
Local<Value> value;
CHECK(!try_catch.HasCaught());
value = CompileRun("new obj2(28)");
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value1(try_catch.Exception());
CHECK_EQ("TypeError: object is not a function", *exception_value1);
try_catch.Reset();
Local<Value> args[] = { v8_num(29) };
value = instance->CallAsConstructor(1, args);
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value2(try_catch.Exception());
CHECK_EQ("TypeError: #<Object> is not a function", *exception_value2);
try_catch.Reset();
}
// Check the case when constructor throws exception.
{ Local<ObjectTemplate> instance_template = ObjectTemplate::New();
instance_template->SetCallAsFunctionHandler(ThrowValue);
Local<Object> instance = instance_template->NewInstance();
context->Global()->Set(v8_str("obj3"), instance);
v8::TryCatch try_catch;
Local<Value> value;
CHECK(!try_catch.HasCaught());
value = CompileRun("new obj3(22)");
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value1(try_catch.Exception());
CHECK_EQ("22", *exception_value1);
try_catch.Reset();
Local<Value> args[] = { v8_num(23) };
value = instance->CallAsConstructor(1, args);
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value2(try_catch.Exception());
CHECK_EQ("23", *exception_value2);
try_catch.Reset();
}
// Check whether constructor returns with an object or non-object.
{ Local<FunctionTemplate> function_template =
FunctionTemplate::New(FakeConstructorCallback);
Local<Function> function = function_template->GetFunction();
Local<Object> instance1 = function;
context->Global()->Set(v8_str("obj4"), instance1);
v8::TryCatch try_catch;
Local<Value> value;
CHECK(!try_catch.HasCaught());
CHECK(instance1->IsObject());
CHECK(instance1->IsFunction());
value = CompileRun("new obj4(28)");
CHECK(!try_catch.HasCaught());
CHECK(value->IsObject());
Local<Value> args1[] = { v8_num(28) };
value = instance1->CallAsConstructor(1, args1);
CHECK(!try_catch.HasCaught());
CHECK(value->IsObject());
Local<ObjectTemplate> instance_template = ObjectTemplate::New();
instance_template->SetCallAsFunctionHandler(FakeConstructorCallback);
Local<Object> instance2 = instance_template->NewInstance();
context->Global()->Set(v8_str("obj5"), instance2);
CHECK(!try_catch.HasCaught());
CHECK(instance2->IsObject());
CHECK(!instance2->IsFunction());
value = CompileRun("new obj5(28)");
CHECK(!try_catch.HasCaught());
CHECK(!value->IsObject());
Local<Value> args2[] = { v8_num(28) };
value = instance2->CallAsConstructor(1, args2);
CHECK(!try_catch.HasCaught());
CHECK(!value->IsObject());
}
}
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('#<Fun>') == -1) return 3;"
" return 0;"
" }"
" return 4;"
"}"
"test();");
CHECK_EQ(0, value->Int32Value());
}
THREADED_TEST(EvalAliasedDynamic) {
v8::HandleScope scope;
LocalContext current;
// 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(this);"
"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'); }"
"}"
"result4 = f(this)"));
script->Run();
CHECK(!try_catch.HasCaught());
CHECK_EQ(2, current->Global()->Get(v8_str("result4"))->Int32Value());
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);
// Set up 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());
}
// Test that calling eval in a context which has been detached from
// its global throws an exception. This behavior is consistent with
// other JavaScript implementations.
THREADED_TEST(EvalInDetachedGlobal) {
v8::HandleScope scope;
v8::Persistent<Context> context0 = Context::New();
v8::Persistent<Context> context1 = Context::New();
// Set up function in context0 that uses eval from context0.
context0->Enter();
v8::Handle<v8::Value> fun =
CompileRun("var x = 42;"
"(function() {"
" var e = eval;"
" return function(s) { return e(s); }"
"})()");
context0->Exit();
// Put the function into context1 and call it before and after
// detaching the global. Before detaching, the call succeeds and
// after detaching and exception is thrown.
context1->Enter();
context1->Global()->Set(v8_str("fun"), fun);
v8::Handle<v8::Value> x_value = CompileRun("fun('x')");
CHECK_EQ(42, x_value->Int32Value());
context0->DetachGlobal();
v8::TryCatch catcher;
x_value = CompileRun("fun('x')");
CHECK(x_value.IsEmpty());
CHECK(catcher.HasCaught());
context1->Exit();
context1.Dispose();
context0.Dispose();
}
THREADED_TEST(CrossLazyLoad) {
v8::HandleScope scope;
LocalContext other;
LocalContext current;
Local<String> token = v8_str("<security token>");
other->SetSecurityToken(token);
current->SetSecurityToken(token);
// Set up 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();
if (args.IsConstructCall()) {
if (args[0]->IsInt32()) {
return v8_num(-args[0]->Int32Value());
}
}
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.
value = CompileRun("new obj(43)");
CHECK(!try_catch.HasCaught());
CHECK_EQ(-43, value->Int32Value());
// Check that the call-as-function handler can be called through
// the API.
v8::Handle<Value> args[] = { v8_num(28) };
value = instance->CallAsFunction(instance, 1, args);
CHECK(!try_catch.HasCaught());
CHECK_EQ(28, value->Int32Value());
}
{ Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
Local<ObjectTemplate> instance_template(t->InstanceTemplate());
USE(instance_template);
Local<v8::Object> instance = t->GetFunction()->NewInstance();
context->Global()->Set(v8_str("obj2"), instance);
v8::TryCatch try_catch;
Local<Value> value;
CHECK(!try_catch.HasCaught());
// Call an object without call-as-function handler through the JS
value = CompileRun("obj2(28)");
CHECK(value.IsEmpty());
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value1(try_catch.Exception());
CHECK_EQ("TypeError: Property 'obj2' of object #<Object> is not a function",
*exception_value1);
try_catch.Reset();
// Call an object without call-as-function handler through the API
value = CompileRun("obj2(28)");
v8::Handle<Value> args[] = { v8_num(28) };
value = instance->CallAsFunction(instance, 1, args);
CHECK(value.IsEmpty());
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value2(try_catch.Exception());
CHECK_EQ("TypeError: [object Object] is not a function", *exception_value2);
try_catch.Reset();
}
{ Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
Local<ObjectTemplate> instance_template = t->InstanceTemplate();
instance_template->SetCallAsFunctionHandler(ThrowValue);
Local<v8::Object> instance = t->GetFunction()->NewInstance();
context->Global()->Set(v8_str("obj3"), instance);
v8::TryCatch try_catch;
Local<Value> value;
CHECK(!try_catch.HasCaught());
// Catch the exception which is thrown by call-as-function handler
value = CompileRun("obj3(22)");
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value1(try_catch.Exception());
CHECK_EQ("22", *exception_value1);
try_catch.Reset();
v8::Handle<Value> args[] = { v8_num(23) };
value = instance->CallAsFunction(instance, 1, args);
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value2(try_catch.Exception());
CHECK_EQ("23", *exception_value2);
try_catch.Reset();
}
}
// Check whether a non-function object is callable.
THREADED_TEST(CallableObject) {
v8::HandleScope scope;
LocalContext context;
{ Local<ObjectTemplate> instance_template = ObjectTemplate::New();
instance_template->SetCallAsFunctionHandler(call_as_function);
Local<Object> instance = instance_template->NewInstance();
v8::TryCatch try_catch;
CHECK(instance->IsCallable());
CHECK(!try_catch.HasCaught());
}
{ Local<ObjectTemplate> instance_template = ObjectTemplate::New();
Local<Object> instance = instance_template->NewInstance();
v8::TryCatch try_catch;
CHECK(!instance->IsCallable());
CHECK(!try_catch.HasCaught());
}
{ Local<FunctionTemplate> function_template =
FunctionTemplate::New(call_as_function);
Local<Function> function = function_template->GetFunction();
Local<Object> instance = function;
v8::TryCatch try_catch;
CHECK(instance->IsCallable());
CHECK(!try_catch.HasCaught());
}
{ Local<FunctionTemplate> function_template = FunctionTemplate::New();
Local<Function> function = function_template->GetFunction();
Local<Object> instance = function;
v8::TryCatch try_catch;
CHECK(instance->IsCallable());
CHECK(!try_catch.HasCaught());
}
}
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();
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
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());
}
typedef v8::Handle<Value> (*NamedPropertyGetter)(Local<String> property,
const AccessorInfo& info);
static void CheckInterceptorLoadIC(NamedPropertyGetter getter,
const char* source,
int expected) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(getter, 0, 0, 0, 0, v8_str("data"));
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
v8::Handle<Value> value = CompileRun(source);
CHECK_EQ(expected, value->Int32Value());
}
static v8::Handle<Value> InterceptorLoadICGetter(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
v8::Isolate* isolate = v8::Isolate::GetCurrent();
CHECK_EQ(isolate, info.GetIsolate());
CHECK_EQ(v8_str("data"), info.Data());
CHECK_EQ(v8_str("x"), name);
return v8::Integer::New(42);
}
// This test should hit the load IC for the interceptor case.
THREADED_TEST(InterceptorLoadIC) {
CheckInterceptorLoadIC(InterceptorLoadICGetter,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" result = o.x;"
"}",
42);
}
// Below go several tests which verify that JITing for various
// configurations of interceptor and explicit fields works fine
// (those cases are special cased to get better performance).
static v8::Handle<Value> InterceptorLoadXICGetter(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
return v8_str("x")->Equals(name)
? v8::Integer::New(42) : v8::Handle<v8::Value>();
}
THREADED_TEST(InterceptorLoadICWithFieldOnHolder) {
CheckInterceptorLoadIC(InterceptorLoadXICGetter,
"var result = 0;"
"o.y = 239;"
"for (var i = 0; i < 1000; i++) {"
" result = o.y;"
"}",
239);
}
THREADED_TEST(InterceptorLoadICWithSubstitutedProto) {
CheckInterceptorLoadIC(InterceptorLoadXICGetter,
"var result = 0;"
"o.__proto__ = { 'y': 239 };"
"for (var i = 0; i < 1000; i++) {"
" result = o.y + o.x;"
"}",
239 + 42);
}
THREADED_TEST(InterceptorLoadICWithPropertyOnProto) {
CheckInterceptorLoadIC(InterceptorLoadXICGetter,
"var result = 0;"
"o.__proto__.y = 239;"
"for (var i = 0; i < 1000; i++) {"
" result = o.y + o.x;"
"}",
239 + 42);
}
THREADED_TEST(InterceptorLoadICUndefined) {
CheckInterceptorLoadIC(InterceptorLoadXICGetter,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" result = (o.y == undefined) ? 239 : 42;"
"}",
239);
}
THREADED_TEST(InterceptorLoadICWithOverride) {
CheckInterceptorLoadIC(InterceptorLoadXICGetter,
"fst = new Object(); fst.__proto__ = o;"
"snd = new Object(); snd.__proto__ = fst;"
"var result1 = 0;"
"for (var i = 0; i < 1000; i++) {"
" result1 = snd.x;"
"}"
"fst.x = 239;"
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" result = snd.x;"
"}"
"result + result1",
239 + 42);
}
// Test the case when we stored field into
// a stub, but interceptor produced value on its own.
THREADED_TEST(InterceptorLoadICFieldNotNeeded) {
CheckInterceptorLoadIC(InterceptorLoadXICGetter,
"proto = new Object();"
"o.__proto__ = proto;"
"proto.x = 239;"
"for (var i = 0; i < 1000; i++) {"
" o.x;"
// Now it should be ICed and keep a reference to x defined on proto
"}"
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" result += o.x;"
"}"
"result;",
42 * 1000);
}
// Test the case when we stored field into
// a stub, but it got invalidated later on.
THREADED_TEST(InterceptorLoadICInvalidatedField) {
CheckInterceptorLoadIC(InterceptorLoadXICGetter,
"proto1 = new Object();"
"proto2 = new Object();"
"o.__proto__ = proto1;"
"proto1.__proto__ = proto2;"
"proto2.y = 239;"
"for (var i = 0; i < 1000; i++) {"
" o.y;"
// Now it should be ICed and keep a reference to y defined on proto2
"}"
"proto1.y = 42;"
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" result += o.y;"
"}"
"result;",
42 * 1000);
}
static int interceptor_load_not_handled_calls = 0;
static v8::Handle<Value> InterceptorLoadNotHandled(Local<String> name,
const AccessorInfo& info) {
++interceptor_load_not_handled_calls;
return v8::Handle<v8::Value>();
}
// Test how post-interceptor lookups are done in the non-cacheable
// case: the interceptor should not be invoked during this lookup.
THREADED_TEST(InterceptorLoadICPostInterceptor) {
interceptor_load_not_handled_calls = 0;
CheckInterceptorLoadIC(InterceptorLoadNotHandled,
"receiver = new Object();"
"receiver.__proto__ = o;"
"proto = new Object();"
"/* Make proto a slow-case object. */"
"for (var i = 0; i < 1000; i++) {"
" proto[\"xxxxxxxx\" + i] = [];"
"}"
"proto.x = 17;"
"o.__proto__ = proto;"
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" result += receiver.x;"
"}"
"result;",
17 * 1000);
CHECK_EQ(1000, interceptor_load_not_handled_calls);
}
// Test the case when we stored field into
// a stub, but it got invalidated later on due to override on
// global object which is between interceptor and fields' holders.
THREADED_TEST(InterceptorLoadICInvalidatedFieldViaGlobal) {
CheckInterceptorLoadIC(InterceptorLoadXICGetter,
"o.__proto__ = this;" // set a global to be a proto of o.
"this.__proto__.y = 239;"
"for (var i = 0; i < 10; i++) {"
" if (o.y != 239) throw 'oops: ' + o.y;"
// Now it should be ICed and keep a reference to y defined on field_holder.
"}"
"this.y = 42;" // Assign on a global.
"var result = 0;"
"for (var i = 0; i < 10; i++) {"
" result += o.y;"
"}"
"result;",
42 * 10);
}
static void SetOnThis(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
info.This()->ForceSet(name, value);
}
THREADED_TEST(InterceptorLoadICWithCallbackOnHolder) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorLoadXICGetter);
templ->SetAccessor(v8_str("y"), Return239);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
// Check the case when receiver and interceptor's holder
// are the same objects.
v8::Handle<Value> value = CompileRun(
"var result = 0;"
"for (var i = 0; i < 7; i++) {"
" result = o.y;"
"}");
CHECK_EQ(239, value->Int32Value());
// Check the case when interceptor's holder is in proto chain
// of receiver.
value = CompileRun(
"r = { __proto__: o };"
"var result = 0;"
"for (var i = 0; i < 7; i++) {"
" result = r.y;"
"}");
CHECK_EQ(239, value->Int32Value());
}
THREADED_TEST(InterceptorLoadICWithCallbackOnProto) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ_o = ObjectTemplate::New();
templ_o->SetNamedPropertyHandler(InterceptorLoadXICGetter);
v8::Handle<v8::ObjectTemplate> templ_p = ObjectTemplate::New();
templ_p->SetAccessor(v8_str("y"), Return239);
LocalContext context;
context->Global()->Set(v8_str("o"), templ_o->NewInstance());
context->Global()->Set(v8_str("p"), templ_p->NewInstance());
// Check the case when receiver and interceptor's holder
// are the same objects.
v8::Handle<Value> value = CompileRun(
"o.__proto__ = p;"
"var result = 0;"
"for (var i = 0; i < 7; i++) {"
" result = o.x + o.y;"
"}");
CHECK_EQ(239 + 42, value->Int32Value());
// Check the case when interceptor's holder is in proto chain
// of receiver.
value = CompileRun(
"r = { __proto__: o };"
"var result = 0;"
"for (var i = 0; i < 7; i++) {"
" result = r.x + r.y;"
"}");
CHECK_EQ(239 + 42, value->Int32Value());
}
THREADED_TEST(InterceptorLoadICForCallbackWithOverride) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorLoadXICGetter);
templ->SetAccessor(v8_str("y"), Return239);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
v8::Handle<Value> value = CompileRun(
"fst = new Object(); fst.__proto__ = o;"
"snd = new Object(); snd.__proto__ = fst;"
"var result1 = 0;"
"for (var i = 0; i < 7; i++) {"
" result1 = snd.x;"
"}"
"fst.x = 239;"
"var result = 0;"
"for (var i = 0; i < 7; i++) {"
" result = snd.x;"
"}"
"result + result1");
CHECK_EQ(239 + 42, value->Int32Value());
}
// Test the case when we stored callback into
// a stub, but interceptor produced value on its own.
THREADED_TEST(InterceptorLoadICCallbackNotNeeded) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ_o = ObjectTemplate::New();
templ_o->SetNamedPropertyHandler(InterceptorLoadXICGetter);
v8::Handle<v8::ObjectTemplate> templ_p = ObjectTemplate::New();
templ_p->SetAccessor(v8_str("y"), Return239);
LocalContext context;
context->Global()->Set(v8_str("o"), templ_o->NewInstance());
context->Global()->Set(v8_str("p"), templ_p->NewInstance());
v8::Handle<Value> value = CompileRun(
"o.__proto__ = p;"
"for (var i = 0; i < 7; i++) {"
" o.x;"
// Now it should be ICed and keep a reference to x defined on p
"}"
"var result = 0;"
"for (var i = 0; i < 7; i++) {"
" result += o.x;"
"}"
"result");
CHECK_EQ(42 * 7, value->Int32Value());
}
// Test the case when we stored callback into
// a stub, but it got invalidated later on.
THREADED_TEST(InterceptorLoadICInvalidatedCallback) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ_o = ObjectTemplate::New();
templ_o->SetNamedPropertyHandler(InterceptorLoadXICGetter);
v8::Handle<v8::ObjectTemplate> templ_p = ObjectTemplate::New();
templ_p->SetAccessor(v8_str("y"), Return239, SetOnThis);
LocalContext context;
context->Global()->Set(v8_str("o"), templ_o->NewInstance());
context->Global()->Set(v8_str("p"), templ_p->NewInstance());
v8::Handle<Value> value = CompileRun(
"inbetween = new Object();"
"o.__proto__ = inbetween;"
"inbetween.__proto__ = p;"
"for (var i = 0; i < 10; i++) {"
" o.y;"
// Now it should be ICed and keep a reference to y defined on p
"}"
"inbetween.y = 42;"
"var result = 0;"
"for (var i = 0; i < 10; i++) {"
" result += o.y;"
"}"
"result");
CHECK_EQ(42 * 10, value->Int32Value());
}
// Test the case when we stored callback into
// a stub, but it got invalidated later on due to override on
// global object which is between interceptor and callbacks' holders.
THREADED_TEST(InterceptorLoadICInvalidatedCallbackViaGlobal) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ_o = ObjectTemplate::New();
templ_o->SetNamedPropertyHandler(InterceptorLoadXICGetter);
v8::Handle<v8::ObjectTemplate> templ_p = ObjectTemplate::New();
templ_p->SetAccessor(v8_str("y"), Return239, SetOnThis);
LocalContext context;
context->Global()->Set(v8_str("o"), templ_o->NewInstance());
context->Global()->Set(v8_str("p"), templ_p->NewInstance());
v8::Handle<Value> value = CompileRun(
"o.__proto__ = this;"
"this.__proto__ = p;"
"for (var i = 0; i < 10; i++) {"
" if (o.y != 239) throw 'oops: ' + o.y;"
// Now it should be ICed and keep a reference to y defined on p
"}"
"this.y = 42;"
"var result = 0;"
"for (var i = 0; i < 10; i++) {"
" result += o.y;"
"}"
"result");
CHECK_EQ(42 * 10, value->Int32Value());
}
static v8::Handle<Value> InterceptorLoadICGetter0(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(v8_str("x")->Equals(name));
return v8::Integer::New(0);
}
THREADED_TEST(InterceptorReturningZero) {
CheckInterceptorLoadIC(InterceptorLoadICGetter0,
"o.x == undefined ? 1 : 0",
0);
}
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,
0, 0, 0, v8_str("data"));
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
CompileRun(
"for (var i = 0; i < 1000; i++) {"
" o.x = 42;"
"}");
}
THREADED_TEST(InterceptorStoreICWithNoSetter) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorLoadXICGetter);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
v8::Handle<Value> value = CompileRun(
"for (var i = 0; i < 1000; i++) {"
" o.y = 239;"
"}"
"42 + o.y");
CHECK_EQ(239 + 42, value->Int32Value());
}
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());
}
// This test checks that if interceptor doesn't provide
// a value, we can fetch regular value.
THREADED_TEST(InterceptorCallICSeesOthers) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(NoBlockGetterX);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
v8::Handle<Value> value = CompileRun(
"o.x = function f(x) { return x + 1; };"
"var result = 0;"
"for (var i = 0; i < 7; i++) {"
" result = o.x(41);"
"}");
CHECK_EQ(42, value->Int32Value());
}
static v8::Handle<Value> call_ic_function4;
static v8::Handle<Value> InterceptorCallICGetter4(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
CHECK(v8_str("x")->Equals(name));
return call_ic_function4;
}
// This test checks that if interceptor provides a function,
// even if we cached shadowed variant, interceptor's function
// is invoked
THREADED_TEST(InterceptorCallICCacheableNotNeeded) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorCallICGetter4);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
call_ic_function4 =
v8_compile("function f(x) { return x - 1; }; f")->Run();
v8::Handle<Value> value = CompileRun(
"o.__proto__.x = function(x) { return x + 1; };"
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" result = o.x(42);"
"}");
CHECK_EQ(41, value->Int32Value());
}
// Test the case when we stored cacheable lookup into
// a stub, but it got invalidated later on
THREADED_TEST(InterceptorCallICInvalidatedCacheable) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(NoBlockGetterX);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
v8::Handle<Value> value = CompileRun(
"proto1 = new Object();"
"proto2 = new Object();"
"o.__proto__ = proto1;"
"proto1.__proto__ = proto2;"
"proto2.y = function(x) { return x + 1; };"
// Invoke it many times to compile a stub
"for (var i = 0; i < 7; i++) {"
" o.y(42);"
"}"
"proto1.y = function(x) { return x - 1; };"
"var result = 0;"
"for (var i = 0; i < 7; i++) {"
" result += o.y(42);"
"}");
CHECK_EQ(41 * 7, value->Int32Value());
}
// This test checks that if interceptor doesn't provide a function,
// cached constant function is used
THREADED_TEST(InterceptorCallICConstantFunctionUsed) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(NoBlockGetterX);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
v8::Handle<Value> value = CompileRun(
"function inc(x) { return x + 1; };"
"inc(1);"
"o.x = inc;"
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" result = o.x(42);"
"}");
CHECK_EQ(43, value->Int32Value());
}
static v8::Handle<Value> call_ic_function5;
static v8::Handle<Value> InterceptorCallICGetter5(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
if (v8_str("x")->Equals(name))
return call_ic_function5;
else
return Local<Value>();
}
// This test checks that if interceptor provides a function,
// even if we cached constant function, interceptor's function
// is invoked
THREADED_TEST(InterceptorCallICConstantFunctionNotNeeded) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorCallICGetter5);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
call_ic_function5 =
v8_compile("function f(x) { return x - 1; }; f")->Run();
v8::Handle<Value> value = CompileRun(
"function inc(x) { return x + 1; };"
"inc(1);"
"o.x = inc;"
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" result = o.x(42);"
"}");
CHECK_EQ(41, value->Int32Value());
}
static v8::Handle<Value> call_ic_function6;
static v8::Handle<Value> InterceptorCallICGetter6(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
if (v8_str("x")->Equals(name))
return call_ic_function6;
else
return Local<Value>();
}
// Same test as above, except the code is wrapped in a function
// to test the optimized compiler.
THREADED_TEST(InterceptorCallICConstantFunctionNotNeededWrapped) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorCallICGetter6);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
call_ic_function6 =
v8_compile("function f(x) { return x - 1; }; f")->Run();
v8::Handle<Value> value = CompileRun(
"function inc(x) { return x + 1; };"
"inc(1);"
"o.x = inc;"
"function test() {"
" var result = 0;"
" for (var i = 0; i < 1000; i++) {"
" result = o.x(42);"
" }"
" return result;"
"};"
"test();"
"test();"
"test();"
"%OptimizeFunctionOnNextCall(test);"
"test()");
CHECK_EQ(41, value->Int32Value());
}
// Test the case when we stored constant function into
// a stub, but it got invalidated later on
THREADED_TEST(InterceptorCallICInvalidatedConstantFunction) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(NoBlockGetterX);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
v8::Handle<Value> value = CompileRun(
"function inc(x) { return x + 1; };"
"inc(1);"
"proto1 = new Object();"
"proto2 = new Object();"
"o.__proto__ = proto1;"
"proto1.__proto__ = proto2;"
"proto2.y = inc;"
// Invoke it many times to compile a stub
"for (var i = 0; i < 7; i++) {"
" o.y(42);"
"}"
"proto1.y = function(x) { return x - 1; };"
"var result = 0;"
"for (var i = 0; i < 7; i++) {"
" result += o.y(42);"
"}");
CHECK_EQ(41 * 7, value->Int32Value());
}
// Test the case when we stored constant function into
// a stub, but it got invalidated later on due to override on
// global object which is between interceptor and constant function' holders.
THREADED_TEST(InterceptorCallICInvalidatedConstantFunctionViaGlobal) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(NoBlockGetterX);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
v8::Handle<Value> value = CompileRun(
"function inc(x) { return x + 1; };"
"inc(1);"
"o.__proto__ = this;"
"this.__proto__.y = inc;"
// Invoke it many times to compile a stub
"for (var i = 0; i < 7; i++) {"
" if (o.y(42) != 43) throw 'oops: ' + o.y(42);"
"}"
"this.y = function(x) { return x - 1; };"
"var result = 0;"
"for (var i = 0; i < 7; i++) {"
" result += o.y(42);"
"}");
CHECK_EQ(41 * 7, value->Int32Value());
}
// Test the case when actual function to call sits on global object.
THREADED_TEST(InterceptorCallICCachedFromGlobal) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ_o = ObjectTemplate::New();
templ_o->SetNamedPropertyHandler(NoBlockGetterX);
LocalContext context;
context->Global()->Set(v8_str("o"), templ_o->NewInstance());
v8::Handle<Value> value = CompileRun(
"try {"
" o.__proto__ = this;"
" for (var i = 0; i < 10; i++) {"
" var v = o.parseFloat('239');"
" if (v != 239) throw v;"
// Now it should be ICed and keep a reference to parseFloat.
" }"
" var result = 0;"
" for (var i = 0; i < 10; i++) {"
" result += o.parseFloat('239');"
" }"
" result"
"} catch(e) {"
" e"
"};");
CHECK_EQ(239 * 10, value->Int32Value());
}
static v8::Handle<Value> InterceptorCallICFastApi(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
int* call_count =
reinterpret_cast<int*>(v8::External::Cast(*info.Data())->Value());
++(*call_count);
if ((*call_count) % 20 == 0) {
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
}
return v8::Handle<Value>();
}
static v8::Handle<Value> FastApiCallback_TrivialSignature(
const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
v8::Isolate* isolate = v8::Isolate::GetCurrent();
CHECK_EQ(isolate, args.GetIsolate());
CHECK_EQ(args.This(), args.Holder());
CHECK(args.Data()->Equals(v8_str("method_data")));
return v8::Integer::New(args[0]->Int32Value() + 1);
}
static v8::Handle<Value> FastApiCallback_SimpleSignature(
const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
v8::Isolate* isolate = v8::Isolate::GetCurrent();
CHECK_EQ(isolate, args.GetIsolate());
CHECK_EQ(args.This()->GetPrototype(), args.Holder());
CHECK(args.Data()->Equals(v8_str("method_data")));
// Note, we're using HasRealNamedProperty instead of Has to avoid
// invoking the interceptor again.
CHECK(args.Holder()->HasRealNamedProperty(v8_str("foo")));
return v8::Integer::New(args[0]->Int32Value() + 1);
}
// Helper to maximize the odds of object moving.
static void GenerateSomeGarbage() {
CompileRun(
"var garbage;"
"for (var i = 0; i < 1000; i++) {"
" garbage = [1/i, \"garbage\" + i, garbage, {foo: garbage}];"
"}"
"garbage = undefined;");
}
v8::Handle<v8::Value> DirectApiCallback(const v8::Arguments& args) {
static int count = 0;
if (count++ % 3 == 0) {
HEAP->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
// This should move the stub
GenerateSomeGarbage(); // This should ensure the old stub memory is flushed
}
return v8::Handle<v8::Value>();
}
THREADED_TEST(CallICFastApi_DirectCall_GCMoveStub) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<v8::ObjectTemplate> nativeobject_templ = v8::ObjectTemplate::New();
nativeobject_templ->Set("callback",
v8::FunctionTemplate::New(DirectApiCallback));
v8::Local<v8::Object> nativeobject_obj = nativeobject_templ->NewInstance();
context->Global()->Set(v8_str("nativeobject"), nativeobject_obj);
// call the api function multiple times to ensure direct call stub creation.
CompileRun(
"function f() {"
" for (var i = 1; i <= 30; i++) {"
" nativeobject.callback();"
" }"
"}"
"f();");
}
v8::Handle<v8::Value> ThrowingDirectApiCallback(const v8::Arguments& args) {
return v8::ThrowException(v8_str("g"));
}
THREADED_TEST(CallICFastApi_DirectCall_Throw) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<v8::ObjectTemplate> nativeobject_templ = v8::ObjectTemplate::New();
nativeobject_templ->Set("callback",
v8::FunctionTemplate::New(ThrowingDirectApiCallback));
v8::Local<v8::Object> nativeobject_obj = nativeobject_templ->NewInstance();
context->Global()->Set(v8_str("nativeobject"), nativeobject_obj);
// call the api function multiple times to ensure direct call stub creation.
v8::Handle<Value> result = CompileRun(
"var result = '';"
"function f() {"
" for (var i = 1; i <= 5; i++) {"
" try { nativeobject.callback(); } catch (e) { result += e; }"
" }"
"}"
"f(); result;");
CHECK_EQ(v8_str("ggggg"), result);
}
v8::Handle<v8::Value> DirectGetterCallback(Local<String> name,
const v8::AccessorInfo& info) {
if (++p_getter_count % 3 == 0) {
HEAP->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
GenerateSomeGarbage();
}
return v8::Handle<v8::Value>();
}
THREADED_TEST(LoadICFastApi_DirectCall_GCMoveStub) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<v8::ObjectTemplate> obj = v8::ObjectTemplate::New();
obj->SetAccessor(v8_str("p1"), DirectGetterCallback);
context->Global()->Set(v8_str("o1"), obj->NewInstance());
p_getter_count = 0;
CompileRun(
"function f() {"
" for (var i = 0; i < 30; i++) o1.p1;"
"}"
"f();");
CHECK_EQ(30, p_getter_count);
}
v8::Handle<v8::Value> ThrowingDirectGetterCallback(
Local<String> name, const v8::AccessorInfo& info) {
return v8::ThrowException(v8_str("g"));
}
THREADED_TEST(LoadICFastApi_DirectCall_Throw) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<v8::ObjectTemplate> obj = v8::ObjectTemplate::New();
obj->SetAccessor(v8_str("p1"), ThrowingDirectGetterCallback);
context->Global()->Set(v8_str("o1"), obj->NewInstance());
v8::Handle<Value> result = CompileRun(
"var result = '';"
"for (var i = 0; i < 5; i++) {"
" try { o1.p1; } catch (e) { result += e; }"
"}"
"result;");
CHECK_EQ(v8_str("ggggg"), result);
}
THREADED_TEST(InterceptorCallICFastApi_TrivialSignature) {
int interceptor_call_count = 0;
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> method_templ =
v8::FunctionTemplate::New(FastApiCallback_TrivialSignature,
v8_str("method_data"),
v8::Handle<v8::Signature>());
v8::Handle<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
proto_templ->Set(v8_str("method"), method_templ);
v8::Handle<v8::ObjectTemplate> templ = fun_templ->InstanceTemplate();
templ->SetNamedPropertyHandler(InterceptorCallICFastApi,
NULL, NULL, NULL, NULL,
v8::External::New(&interceptor_call_count));
LocalContext context;
v8::Handle<v8::Function> fun = fun_templ->GetFunction();
GenerateSomeGarbage();
context->Global()->Set(v8_str("o"), fun->NewInstance());
CompileRun(
"var result = 0;"
"for (var i = 0; i < 100; i++) {"
" result = o.method(41);"
"}");
CHECK_EQ(42, context->Global()->Get(v8_str("result"))->Int32Value());
CHECK_EQ(100, interceptor_call_count);
}
THREADED_TEST(InterceptorCallICFastApi_SimpleSignature) {
int interceptor_call_count = 0;
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> method_templ =
v8::FunctionTemplate::New(FastApiCallback_SimpleSignature,
v8_str("method_data"),
v8::Signature::New(fun_templ));
v8::Handle<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
proto_templ->Set(v8_str("method"), method_templ);
fun_templ->SetHiddenPrototype(true);
v8::Handle<v8::ObjectTemplate> templ = fun_templ->InstanceTemplate();
templ->SetNamedPropertyHandler(InterceptorCallICFastApi,
NULL, NULL, NULL, NULL,
v8::External::New(&interceptor_call_count));
LocalContext context;
v8::Handle<v8::Function> fun = fun_templ->GetFunction();
GenerateSomeGarbage();
context->Global()->Set(v8_str("o"), fun->NewInstance());
CompileRun(
"o.foo = 17;"
"var receiver = {};"
"receiver.__proto__ = o;"
"var result = 0;"
"for (var i = 0; i < 100; i++) {"
" result = receiver.method(41);"
"}");
CHECK_EQ(42, context->Global()->Get(v8_str("result"))->Int32Value());
CHECK_EQ(100, interceptor_call_count);
}
THREADED_TEST(InterceptorCallICFastApi_SimpleSignature_Miss1) {
int interceptor_call_count = 0;
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> method_templ =
v8::FunctionTemplate::New(FastApiCallback_SimpleSignature,
v8_str("method_data"),
v8::Signature::New(fun_templ));
v8::Handle<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
proto_templ->Set(v8_str("method"), method_templ);
fun_templ->SetHiddenPrototype(true);
v8::Handle<v8::ObjectTemplate> templ = fun_templ->InstanceTemplate();
templ->SetNamedPropertyHandler(InterceptorCallICFastApi,
NULL, NULL, NULL, NULL,
v8::External::New(&interceptor_call_count));
LocalContext context;
v8::Handle<v8::Function> fun = fun_templ->GetFunction();
GenerateSomeGarbage();
context->Global()->Set(v8_str("o"), fun->NewInstance());
CompileRun(
"o.foo = 17;"
"var receiver = {};"
"receiver.__proto__ = o;"
"var result = 0;"
"var saved_result = 0;"
"for (var i = 0; i < 100; i++) {"
" result = receiver.method(41);"
" if (i == 50) {"
" saved_result = result;"
" receiver = {method: function(x) { return x - 1 }};"
" }"
"}");
CHECK_EQ(40, context->Global()->Get(v8_str("result"))->Int32Value());
CHECK_EQ(42, context->Global()->Get(v8_str("saved_result"))->Int32Value());
CHECK_GE(interceptor_call_count, 50);
}
THREADED_TEST(InterceptorCallICFastApi_SimpleSignature_Miss2) {
int interceptor_call_count = 0;
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> method_templ =
v8::FunctionTemplate::New(FastApiCallback_SimpleSignature,
v8_str("method_data"),
v8::Signature::New(fun_templ));
v8::Handle<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
proto_templ->Set(v8_str("method"), method_templ);
fun_templ->SetHiddenPrototype(true);
v8::Handle<v8::ObjectTemplate> templ = fun_templ->InstanceTemplate();
templ->SetNamedPropertyHandler(InterceptorCallICFastApi,
NULL, NULL, NULL, NULL,
v8::External::New(&interceptor_call_count));
LocalContext context;
v8::Handle<v8::Function> fun = fun_templ->GetFunction();
GenerateSomeGarbage();
context->Global()->Set(v8_str("o"), fun->NewInstance());
CompileRun(
"o.foo = 17;"
"var receiver = {};"
"receiver.__proto__ = o;"
"var result = 0;"
"var saved_result = 0;"
"for (var i = 0; i < 100; i++) {"
" result = receiver.method(41);"
" if (i == 50) {"
" saved_result = result;"
" o.method = function(x) { return x - 1 };"
" }"
"}");
CHECK_EQ(40, context->Global()->Get(v8_str("result"))->Int32Value());
CHECK_EQ(42, context->Global()->Get(v8_str("saved_result"))->Int32Value());
CHECK_GE(interceptor_call_count, 50);
}
THREADED_TEST(InterceptorCallICFastApi_SimpleSignature_Miss3) {
int interceptor_call_count = 0;
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> method_templ =
v8::FunctionTemplate::New(FastApiCallback_SimpleSignature,
v8_str("method_data"),
v8::Signature::New(fun_templ));
v8::Handle<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
proto_templ->Set(v8_str("method"), method_templ);
fun_templ->SetHiddenPrototype(true);
v8::Handle<v8::ObjectTemplate> templ = fun_templ->InstanceTemplate();
templ->SetNamedPropertyHandler(InterceptorCallICFastApi,
NULL, NULL, NULL, NULL,
v8::External::New(&interceptor_call_count));
LocalContext context;
v8::Handle<v8::Function> fun = fun_templ->GetFunction();
GenerateSomeGarbage();
context->Global()->Set(v8_str("o"), fun->NewInstance());
v8::TryCatch try_catch;
CompileRun(
"o.foo = 17;"
"var receiver = {};"
"receiver.__proto__ = o;"
"var result = 0;"
"var saved_result = 0;"
"for (var i = 0; i < 100; i++) {"
" result = receiver.method(41);"
" if (i == 50) {"
" saved_result = result;"
" receiver = 333;"
" }"
"}");
CHECK(try_catch.HasCaught());
CHECK_EQ(v8_str("TypeError: Object 333 has no method 'method'"),
try_catch.Exception()->ToString());
CHECK_EQ(42, context->Global()->Get(v8_str("saved_result"))->Int32Value());
CHECK_GE(interceptor_call_count, 50);
}
THREADED_TEST(InterceptorCallICFastApi_SimpleSignature_TypeError) {
int interceptor_call_count = 0;
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> method_templ =
v8::FunctionTemplate::New(FastApiCallback_SimpleSignature,
v8_str("method_data"),
v8::Signature::New(fun_templ));
v8::Handle<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
proto_templ->Set(v8_str("method"), method_templ);
fun_templ->SetHiddenPrototype(true);
v8::Handle<v8::ObjectTemplate> templ = fun_templ->InstanceTemplate();
templ->SetNamedPropertyHandler(InterceptorCallICFastApi,
NULL, NULL, NULL, NULL,
v8::External::New(&interceptor_call_count));
LocalContext context;
v8::Handle<v8::Function> fun = fun_templ->GetFunction();
GenerateSomeGarbage();
context->Global()->Set(v8_str("o"), fun->NewInstance());
v8::TryCatch try_catch;
CompileRun(
"o.foo = 17;"
"var receiver = {};"
"receiver.__proto__ = o;"
"var result = 0;"
"var saved_result = 0;"
"for (var i = 0; i < 100; i++) {"
" result = receiver.method(41);"
" if (i == 50) {"
" saved_result = result;"
" receiver = {method: receiver.method};"
" }"
"}");
CHECK(try_catch.HasCaught());
CHECK_EQ(v8_str("TypeError: Illegal invocation"),
try_catch.Exception()->ToString());
CHECK_EQ(42, context->Global()->Get(v8_str("saved_result"))->Int32Value());
CHECK_GE(interceptor_call_count, 50);
}
THREADED_TEST(CallICFastApi_TrivialSignature) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> method_templ =
v8::FunctionTemplate::New(FastApiCallback_TrivialSignature,
v8_str("method_data"),
v8::Handle<v8::Signature>());
v8::Handle<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
proto_templ->Set(v8_str("method"), method_templ);
v8::Handle<v8::ObjectTemplate> templ(fun_templ->InstanceTemplate());
USE(templ);
LocalContext context;
v8::Handle<v8::Function> fun = fun_templ->GetFunction();
GenerateSomeGarbage();
context->Global()->Set(v8_str("o"), fun->NewInstance());
CompileRun(
"var result = 0;"
"for (var i = 0; i < 100; i++) {"
" result = o.method(41);"
"}");
CHECK_EQ(42, context->Global()->Get(v8_str("result"))->Int32Value());
}
THREADED_TEST(CallICFastApi_SimpleSignature) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> method_templ =
v8::FunctionTemplate::New(FastApiCallback_SimpleSignature,
v8_str("method_data"),
v8::Signature::New(fun_templ));
v8::Handle<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
proto_templ->Set(v8_str("method"), method_templ);
fun_templ->SetHiddenPrototype(true);
v8::Handle<v8::ObjectTemplate> templ(fun_templ->InstanceTemplate());
CHECK(!templ.IsEmpty());
LocalContext context;
v8::Handle<v8::Function> fun = fun_templ->GetFunction();
GenerateSomeGarbage();
context->Global()->Set(v8_str("o"), fun->NewInstance());
CompileRun(
"o.foo = 17;"
"var receiver = {};"
"receiver.__proto__ = o;"
"var result = 0;"
"for (var i = 0; i < 100; i++) {"
" result = receiver.method(41);"
"}");
CHECK_EQ(42, context->Global()->Get(v8_str("result"))->Int32Value());
}
THREADED_TEST(CallICFastApi_SimpleSignature_Miss1) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> method_templ =
v8::FunctionTemplate::New(FastApiCallback_SimpleSignature,
v8_str("method_data"),
v8::Signature::New(fun_templ));
v8::Handle<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
proto_templ->Set(v8_str("method"), method_templ);
fun_templ->SetHiddenPrototype(true);
v8::Handle<v8::ObjectTemplate> templ(fun_templ->InstanceTemplate());
CHECK(!templ.IsEmpty());
LocalContext context;
v8::Handle<v8::Function> fun = fun_templ->GetFunction();
GenerateSomeGarbage();
context->Global()->Set(v8_str("o"), fun->NewInstance());
CompileRun(
"o.foo = 17;"
"var receiver = {};"
"receiver.__proto__ = o;"
"var result = 0;"
"var saved_result = 0;"
"for (var i = 0; i < 100; i++) {"
" result = receiver.method(41);"
" if (i == 50) {"
" saved_result = result;"
" receiver = {method: function(x) { return x - 1 }};"
" }"
"}");
CHECK_EQ(40, context->Global()->Get(v8_str("result"))->Int32Value());
CHECK_EQ(42, context->Global()->Get(v8_str("saved_result"))->Int32Value());
}
THREADED_TEST(CallICFastApi_SimpleSignature_Miss2) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> method_templ =
v8::FunctionTemplate::New(FastApiCallback_SimpleSignature,
v8_str("method_data"),
v8::Signature::New(fun_templ));
v8::Handle<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
proto_templ->Set(v8_str("method"), method_templ);
fun_templ->SetHiddenPrototype(true);
v8::Handle<v8::ObjectTemplate> templ(fun_templ->InstanceTemplate());
CHECK(!templ.IsEmpty());
LocalContext context;
v8::Handle<v8::Function> fun = fun_templ->GetFunction();
GenerateSomeGarbage();
context->Global()->Set(v8_str("o"), fun->NewInstance());
v8::TryCatch try_catch;
CompileRun(
"o.foo = 17;"
"var receiver = {};"
"receiver.__proto__ = o;"
"var result = 0;"
"var saved_result = 0;"
"for (var i = 0; i < 100; i++) {"
" result = receiver.method(41);"
" if (i == 50) {"
" saved_result = result;"
" receiver = 333;"
" }"
"}");
CHECK(try_catch.HasCaught());
CHECK_EQ(v8_str("TypeError: Object 333 has no method 'method'"),
try_catch.Exception()->ToString());
CHECK_EQ(42, context->Global()->Get(v8_str("saved_result"))->Int32Value());
}
THREADED_TEST(CallICFastApi_SimpleSignature_TypeError) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New();
v8::Handle<v8::FunctionTemplate> method_templ =
v8::FunctionTemplate::New(FastApiCallback_SimpleSignature,
v8_str("method_data"),
v8::Signature::New(fun_templ));
v8::Handle<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
proto_templ->Set(v8_str("method"), method_templ);
fun_templ->SetHiddenPrototype(true);
v8::Handle<v8::ObjectTemplate> templ(fun_templ->InstanceTemplate());
CHECK(!templ.IsEmpty());
LocalContext context;
v8::Handle<v8::Function> fun = fun_templ->GetFunction();
GenerateSomeGarbage();
context->Global()->Set(v8_str("o"), fun->NewInstance());
v8::TryCatch try_catch;
CompileRun(
"o.foo = 17;"
"var receiver = {};"
"receiver.__proto__ = o;"
"var result = 0;"
"var saved_result = 0;"
"for (var i = 0; i < 100; i++) {"
" result = receiver.method(41);"
" if (i == 50) {"
" saved_result = result;"
" receiver = Object.create(receiver);"
" }"
"}");
CHECK(try_catch.HasCaught());
CHECK_EQ(v8_str("TypeError: Illegal invocation"),
try_catch.Exception()->ToString());
CHECK_EQ(42, context->Global()->Get(v8_str("saved_result"))->Int32Value());
}
v8::Handle<Value> keyed_call_ic_function;
static v8::Handle<Value> InterceptorKeyedCallICGetter(
Local<String> name, const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
if (v8_str("x")->Equals(name)) {
return keyed_call_ic_function;
}
return v8::Handle<Value>();
}
// Test the case when we stored cacheable lookup into
// a stub, but the function name changed (to another cacheable function).
THREADED_TEST(InterceptorKeyedCallICKeyChange1) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(NoBlockGetterX);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
CompileRun(
"proto = new Object();"
"proto.y = function(x) { return x + 1; };"
"proto.z = function(x) { return x - 1; };"
"o.__proto__ = proto;"
"var result = 0;"
"var method = 'y';"
"for (var i = 0; i < 10; i++) {"
" if (i == 5) { method = 'z'; };"
" result += o[method](41);"
"}");
CHECK_EQ(42*5 + 40*5, context->Global()->Get(v8_str("result"))->Int32Value());
}
// Test the case when we stored cacheable lookup into
// a stub, but the function name changed (and the new function is present
// both before and after the interceptor in the prototype chain).
THREADED_TEST(InterceptorKeyedCallICKeyChange2) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(InterceptorKeyedCallICGetter);
LocalContext context;
context->Global()->Set(v8_str("proto1"), templ->NewInstance());
keyed_call_ic_function =
v8_compile("function f(x) { return x - 1; }; f")->Run();
CompileRun(
"o = new Object();"
"proto2 = new Object();"
"o.y = function(x) { return x + 1; };"
"proto2.y = function(x) { return x + 2; };"
"o.__proto__ = proto1;"
"proto1.__proto__ = proto2;"
"var result = 0;"
"var method = 'x';"
"for (var i = 0; i < 10; i++) {"
" if (i == 5) { method = 'y'; };"
" result += o[method](41);"
"}");
CHECK_EQ(42*5 + 40*5, context->Global()->Get(v8_str("result"))->Int32Value());
}
// Same as InterceptorKeyedCallICKeyChange1 only the cacheable function sit
// on the global object.
THREADED_TEST(InterceptorKeyedCallICKeyChangeOnGlobal) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(NoBlockGetterX);
LocalContext context;
context->Global()->Set(v8_str("o"), templ->NewInstance());
CompileRun(
"function inc(x) { return x + 1; };"
"inc(1);"
"function dec(x) { return x - 1; };"
"dec(1);"
"o.__proto__ = this;"
"this.__proto__.x = inc;"
"this.__proto__.y = dec;"
"var result = 0;"
"var method = 'x';"
"for (var i = 0; i < 10; i++) {"
" if (i == 5) { method = 'y'; };"
" result += o[method](41);"
"}");
CHECK_EQ(42*5 + 40*5, context->Global()->Get(v8_str("result"))->Int32Value());
}
// Test the case when actual function to call sits on global object.
THREADED_TEST(InterceptorKeyedCallICFromGlobal) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ_o = ObjectTemplate::New();
templ_o->SetNamedPropertyHandler(NoBlockGetterX);
LocalContext context;
context->Global()->Set(v8_str("o"), templ_o->NewInstance());
CompileRun(
"function len(x) { return x.length; };"
"o.__proto__ = this;"
"var m = 'parseFloat';"
"var result = 0;"
"for (var i = 0; i < 10; i++) {"
" if (i == 5) {"
" m = 'len';"
" saved_result = result;"
" };"
" result = o[m]('239');"
"}");
CHECK_EQ(3, context->Global()->Get(v8_str("result"))->Int32Value());
CHECK_EQ(239, context->Global()->Get(v8_str("saved_result"))->Int32Value());
}
// Test the map transition before the interceptor.
THREADED_TEST(InterceptorKeyedCallICMapChangeBefore) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ_o = ObjectTemplate::New();
templ_o->SetNamedPropertyHandler(NoBlockGetterX);
LocalContext context;
context->Global()->Set(v8_str("proto"), templ_o->NewInstance());
CompileRun(
"var o = new Object();"
"o.__proto__ = proto;"
"o.method = function(x) { return x + 1; };"
"var m = 'method';"
"var result = 0;"
"for (var i = 0; i < 10; i++) {"
" if (i == 5) { o.method = function(x) { return x - 1; }; };"
" result += o[m](41);"
"}");
CHECK_EQ(42*5 + 40*5, context->Global()->Get(v8_str("result"))->Int32Value());
}
// Test the map transition after the interceptor.
THREADED_TEST(InterceptorKeyedCallICMapChangeAfter) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ_o = ObjectTemplate::New();
templ_o->SetNamedPropertyHandler(NoBlockGetterX);
LocalContext context;
context->Global()->Set(v8_str("o"), templ_o->NewInstance());
CompileRun(
"var proto = new Object();"
"o.__proto__ = proto;"
"proto.method = function(x) { return x + 1; };"
"var m = 'method';"
"var result = 0;"
"for (var i = 0; i < 10; i++) {"
" if (i == 5) { proto.method = function(x) { return x - 1; }; };"
" result += o[m](41);"
"}");
CHECK_EQ(42*5 + 40*5, context->Global()->Get(v8_str("result"))->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());
}
THREADED_TEST(NamedPropertyHandlerGetterAttributes) {
v8::HandleScope scope;
v8::Handle<v8::FunctionTemplate> templ = v8::FunctionTemplate::New();
templ->InstanceTemplate()->SetNamedPropertyHandler(InterceptorLoadXICGetter);
LocalContext env;
env->Global()->Set(v8_str("obj"),
templ->GetFunction()->NewInstance());
ExpectTrue("obj.x === 42");
ExpectTrue("!obj.propertyIsEnumerable('x')");
}
static Handle<Value> ThrowingGetter(Local<String> name,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
ThrowException(Handle<Value>());
return Undefined();
}
THREADED_TEST(VariousGetPropertiesAndThrowingCallbacks) {
HandleScope scope;
LocalContext context;
Local<FunctionTemplate> templ = FunctionTemplate::New();
Local<ObjectTemplate> instance_templ = templ->InstanceTemplate();
instance_templ->SetAccessor(v8_str("f"), ThrowingGetter);
Local<Object> instance = templ->GetFunction()->NewInstance();
Local<Object> another = Object::New();
another->SetPrototype(instance);
Local<Object> with_js_getter = CompileRun(
"o = {};\n"
"o.__defineGetter__('f', function() { throw undefined; });\n"
"o\n").As<Object>();
CHECK(!with_js_getter.IsEmpty());
TryCatch try_catch;
Local<Value> result = instance->GetRealNamedProperty(v8_str("f"));
CHECK(try_catch.HasCaught());
try_catch.Reset();
CHECK(result.IsEmpty());
result = another->GetRealNamedProperty(v8_str("f"));
CHECK(try_catch.HasCaught());
try_catch.Reset();
CHECK(result.IsEmpty());
result = another->GetRealNamedPropertyInPrototypeChain(v8_str("f"));
CHECK(try_catch.HasCaught());
try_catch.Reset();
CHECK(result.IsEmpty());
result = another->Get(v8_str("f"));
CHECK(try_catch.HasCaught());
try_catch.Reset();
CHECK(result.IsEmpty());
result = with_js_getter->GetRealNamedProperty(v8_str("f"));
CHECK(try_catch.HasCaught());
try_catch.Reset();
CHECK(result.IsEmpty());
result = with_js_getter->Get(v8_str("f"));
CHECK(try_catch.HasCaught());
try_catch.Reset();
CHECK(result.IsEmpty());
}
static Handle<Value> ThrowingCallbackWithTryCatch(const Arguments& args) {
TryCatch try_catch;
// Verboseness is important: it triggers message delivery which can call into
// external code.
try_catch.SetVerbose(true);
CompileRun("throw 'from JS';");
CHECK(try_catch.HasCaught());
CHECK(!i::Isolate::Current()->has_pending_exception());
CHECK(!i::Isolate::Current()->has_scheduled_exception());
return Undefined();
}
static int call_depth;
static void WithTryCatch(Handle<Message> message, Handle<Value> data) {
TryCatch try_catch;
}
static void ThrowFromJS(Handle<Message> message, Handle<Value> data) {
if (--call_depth) CompileRun("throw 'ThrowInJS';");
}
static void ThrowViaApi(Handle<Message> message, Handle<Value> data) {
if (--call_depth) ThrowException(v8_str("ThrowViaApi"));
}
static void WebKitLike(Handle<Message> message, Handle<Value> data) {
Handle<String> errorMessageString = message->Get();
CHECK(!errorMessageString.IsEmpty());
message->GetStackTrace();
message->GetScriptResourceName();
}
THREADED_TEST(ExceptionsDoNotPropagatePastTryCatch) {
HandleScope scope;
LocalContext context;
Local<Function> func =
FunctionTemplate::New(ThrowingCallbackWithTryCatch)->GetFunction();
context->Global()->Set(v8_str("func"), func);
MessageCallback callbacks[] =
{ NULL, WebKitLike, ThrowViaApi, ThrowFromJS, WithTryCatch };
for (unsigned i = 0; i < sizeof(callbacks)/sizeof(callbacks[0]); i++) {
MessageCallback callback = callbacks[i];
if (callback != NULL) {
V8::AddMessageListener(callback);
}
// Some small number to control number of times message handler should
// throw an exception.
call_depth = 5;
ExpectFalse(
"var thrown = false;\n"
"try { func(); } catch(e) { thrown = true; }\n"
"thrown\n");
if (callback != NULL) {
V8::RemoveMessageListeners(callback);
}
}
}
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) {
i::FLAG_es5_readonly = true;
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 that 'h' cannot be shadowed.
value = v8_compile("o.h = 3; o.h")->Run();
CHECK_EQ(1, value->Int32Value());
// Check that 'i' 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();
return v8::Boolean::New(args.IsConstructCall());
}
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 = object.As<v8::Object>()->ObjectProtoToString();
CHECK(value->IsString() && value->Equals(v8_str("[object Object]")));
}
THREADED_TEST(ObjectGetConstructorName) {
v8::HandleScope scope;
LocalContext context;
v8_compile("function Parent() {};"
"function Child() {};"
"Child.prototype = new Parent();"
"var outer = { inner: function() { } };"
"var p = new Parent();"
"var c = new Child();"
"var x = new outer.inner();")->Run();
Local<v8::Value> p = context->Global()->Get(v8_str("p"));
CHECK(p->IsObject() && p->ToObject()->GetConstructorName()->Equals(
v8_str("Parent")));
Local<v8::Value> c = context->Global()->Get(v8_str("c"));
CHECK(c->IsObject() && c->ToObject()->GetConstructorName()->Equals(
v8_str("Child")));
Local<v8::Value> x = context->Global()->Get(v8_str("x"));
CHECK(x->IsObject() && x->ToObject()->GetConstructorName()->Equals(
v8_str("outer.inner")));
}
bool ApiTestFuzzer::fuzzing_ = false;
i::Semaphore* ApiTestFuzzer::all_tests_done_=
i::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();
const char* test_name = RegisterThreadedTest::nth(current_)->name();
if (test_position == current_) {
if (kLogThreading)
printf("Stay with %s\n", test_name);
return false;
}
if (kLogThreading) {
printf("Switch from %s to %s\n",
test_name,
RegisterThreadedTest::nth(test_position)->name());
}
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(CcTest::default_isolate());
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 count = RegisterThreadedTest::count();
int start = count * part / (LAST_PART + 1);
int end = (count * (part + 1) / (LAST_PART + 1)) - 1;
active_tests_ = tests_being_run_ = end - start + 1;
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(CcTest::default_isolate());
// 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();
}
TEST(Threading3) {
ApiTestFuzzer::SetUp(ApiTestFuzzer::THIRD_PART);
ApiTestFuzzer::RunAllTests();
ApiTestFuzzer::TearDown();
}
TEST(Threading4) {
ApiTestFuzzer::SetUp(ApiTestFuzzer::FOURTH_PART);
ApiTestFuzzer::RunAllTests();
ApiTestFuzzer::TearDown();
}
void ApiTestFuzzer::CallTest() {
if (kLogThreading)
printf("Start test %d\n", test_number_);
CallTestNumber(test_number_);
if (kLogThreading)
printf("End test %d\n", test_number_);
}
static v8::Handle<Value> ThrowInJS(const v8::Arguments& args) {
CHECK(v8::Locker::IsLocked(CcTest::default_isolate()));
ApiTestFuzzer::Fuzz();
v8::Unlocker unlocker(CcTest::default_isolate());
const char* code = "throw 7;";
{
v8::Locker nested_locker(CcTest::default_isolate());
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(CcTest::default_isolate()));
ApiTestFuzzer::Fuzz();
v8::Unlocker unlocker(CcTest::default_isolate());
const char* code = "throw 7;";
{
v8::Locker nested_locker(CcTest::default_isolate());
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(CcTest::default_isolate());
CHECK(v8::Locker::IsLocked(CcTest::default_isolate()));
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(CcTest::default_isolate());
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(CcTest::default_isolate());
{
v8::Locker locker2(CcTest::default_isolate());
CHECK(v8::Locker::IsLocked(CcTest::default_isolate()));
}
}
static v8::Handle<Value> UnlockForAMoment(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
v8::Unlocker unlocker(CcTest::default_isolate());
return v8::Undefined();
}
THREADED_TEST(LockUnlockLock) {
{
v8::Locker locker(CcTest::default_isolate());
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(CcTest::default_isolate());
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 GetGlobalObjectsCount() {
i::Isolate::Current()->heap()->EnsureHeapIsIterable();
int count = 0;
i::HeapIterator it;
for (i::HeapObject* object = it.next(); object != NULL; object = it.next())
if (object->IsJSGlobalObject()) count++;
return count;
}
static void CheckSurvivingGlobalObjectsCount(int expected) {
// 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.
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
HEAP->CollectAllGarbage(i::Heap::kMakeHeapIterableMask);
int count = GetGlobalObjectsCount();
#ifdef DEBUG
if (count != expected) HEAP->TracePathToGlobal();
#endif
CHECK_EQ(expected, count);
}
TEST(DontLeakGlobalObjects) {
// Regression test for issues 1139850 and 1174891.
v8::V8::Initialize();
for (int i = 0; i < 5; i++) {
{ v8::HandleScope scope;
LocalContext context;
}
v8::V8::ContextDisposedNotification();
CheckSurvivingGlobalObjectsCount(0);
{ v8::HandleScope scope;
LocalContext context;
v8_compile("Date")->Run();
}
v8::V8::ContextDisposedNotification();
CheckSurvivingGlobalObjectsCount(0);
{ v8::HandleScope scope;
LocalContext context;
v8_compile("/aaa/")->Run();
}
v8::V8::ContextDisposedNotification();
CheckSurvivingGlobalObjectsCount(0);
{ v8::HandleScope scope;
const char* extension_list[] = { "v8/gc" };
v8::ExtensionConfiguration extensions(1, extension_list);
LocalContext context(&extensions);
v8_compile("gc();")->Run();
}
v8::V8::ContextDisposedNotification();
CheckSurvivingGlobalObjectsCount(0);
}
}
v8::Persistent<v8::Object> some_object;
v8::Persistent<v8::Object> bad_handle;
void NewPersistentHandleCallback(v8::Persistent<v8::Value> handle, void*) {
v8::HandleScope scope;
bad_handle = v8::Persistent<v8::Object>::New(some_object);
handle.Dispose();
}
THREADED_TEST(NewPersistentHandleFromWeakCallback) {
LocalContext context;
v8::Persistent<v8::Object> handle1, handle2;
{
v8::HandleScope scope;
some_object = v8::Persistent<v8::Object>::New(v8::Object::New());
handle1 = v8::Persistent<v8::Object>::New(v8::Object::New());
handle2 = v8::Persistent<v8::Object>::New(v8::Object::New());
}
// Note: order is implementation dependent alas: currently
// global handle nodes are processed by PostGarbageCollectionProcessing
// in reverse allocation order, so if second allocated handle is deleted,
// weak callback of the first handle would be able to 'reallocate' it.
handle1.MakeWeak(NULL, NewPersistentHandleCallback);
handle2.Dispose();
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
}
v8::Persistent<v8::Object> to_be_disposed;
void DisposeAndForceGcCallback(v8::Persistent<v8::Value> handle, void*) {
to_be_disposed.Dispose();
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
handle.Dispose();
}
THREADED_TEST(DoNotUseDeletedNodesInSecondLevelGc) {
LocalContext context;
v8::Persistent<v8::Object> handle1, handle2;
{
v8::HandleScope scope;
handle1 = v8::Persistent<v8::Object>::New(v8::Object::New());
handle2 = v8::Persistent<v8::Object>::New(v8::Object::New());
}
handle1.MakeWeak(NULL, DisposeAndForceGcCallback);
to_be_disposed = handle2;
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
}
void DisposingCallback(v8::Persistent<v8::Value> handle, void*) {
handle.Dispose();
}
void HandleCreatingCallback(v8::Persistent<v8::Value> handle, void*) {
v8::HandleScope scope;
v8::Persistent<v8::Object>::New(v8::Object::New());
handle.Dispose();
}
THREADED_TEST(NoGlobalHandlesOrphaningDueToWeakCallback) {
LocalContext context;
v8::Persistent<v8::Object> handle1, handle2, handle3;
{
v8::HandleScope scope;
handle3 = v8::Persistent<v8::Object>::New(v8::Object::New());
handle2 = v8::Persistent<v8::Object>::New(v8::Object::New());
handle1 = v8::Persistent<v8::Object>::New(v8::Object::New());
}
handle2.MakeWeak(NULL, DisposingCallback);
handle3.MakeWeak(NULL, HandleCreatingCallback);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
}
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());
CHECK(!str.IsEmpty());
env->Exit();
env.Dispose();
}
static i::Handle<i::JSFunction>* foo_ptr = NULL;
static int foo_count = 0;
static i::Handle<i::JSFunction>* bar_ptr = NULL;
static int bar_count = 0;
static void entry_hook(uintptr_t function,
uintptr_t return_addr_location) {
i::Code* code = i::Code::GetCodeFromTargetAddress(
reinterpret_cast<i::Address>(function));
CHECK(code != NULL);
if (bar_ptr != NULL && code == (*bar_ptr)->code())
++bar_count;
if (foo_ptr != NULL && code == (*foo_ptr)->code())
++foo_count;
// TODO(siggi): Verify return_addr_location.
// This can be done by capturing JitCodeEvents, but requires an ordered
// collection.
}
static void RunLoopInNewEnv() {
bar_ptr = NULL;
foo_ptr = NULL;
v8::HandleScope outer;
v8::Persistent<Context> env = Context::New();
env->Enter();
const char* script =
"function bar() {"
" var sum = 0;"
" for (i = 0; i < 100; ++i)"
" sum = foo(i);"
" return sum;"
"}"
"function foo(i) { return i * i; }";
CompileRun(script);
i::Handle<i::JSFunction> bar =
i::Handle<i::JSFunction>::cast(
v8::Utils::OpenHandle(*env->Global()->Get(v8_str("bar"))));
ASSERT(*bar);
i::Handle<i::JSFunction> foo =
i::Handle<i::JSFunction>::cast(
v8::Utils::OpenHandle(*env->Global()->Get(v8_str("foo"))));
ASSERT(*foo);
bar_ptr = &bar;
foo_ptr = &foo;
v8::Handle<v8::Value> value = CompileRun("bar();");
CHECK(value->IsNumber());
CHECK_EQ(9801.0, v8::Number::Cast(*value)->Value());
// Test the optimized codegen path.
value = CompileRun("%OptimizeFunctionOnNextCall(foo);"
"bar();");
CHECK(value->IsNumber());
CHECK_EQ(9801.0, v8::Number::Cast(*value)->Value());
env->Exit();
}
TEST(SetFunctionEntryHook) {
i::FLAG_allow_natives_syntax = true;
i::FLAG_use_inlining = false;
// Test setting and resetting the entry hook.
// Nulling it should always succeed.
CHECK(v8::V8::SetFunctionEntryHook(NULL));
CHECK(v8::V8::SetFunctionEntryHook(entry_hook));
// Setting a hook while one's active should fail.
CHECK_EQ(false, v8::V8::SetFunctionEntryHook(entry_hook));
CHECK(v8::V8::SetFunctionEntryHook(NULL));
// Reset the entry count to zero and set the entry hook.
bar_count = 0;
foo_count = 0;
CHECK(v8::V8::SetFunctionEntryHook(entry_hook));
RunLoopInNewEnv();
CHECK_EQ(2, bar_count);
CHECK_EQ(200, foo_count);
// Clear the entry hook and count.
bar_count = 0;
foo_count = 0;
v8::V8::SetFunctionEntryHook(NULL);
// Clear the compilation cache to make sure we don't reuse the
// functions from the previous invocation.
v8::internal::Isolate::Current()->compilation_cache()->Clear();
// Verify that entry hooking is now disabled.
RunLoopInNewEnv();
CHECK_EQ(0u, bar_count);
CHECK_EQ(0u, foo_count);
}
static i::HashMap* code_map = NULL;
static int saw_bar = 0;
static int move_events = 0;
static bool FunctionNameIs(const char* expected,
const v8::JitCodeEvent* event) {
// Log lines for functions are of the general form:
// "LazyCompile:<type><function_name>", where the type is one of
// "*", "~" or "".
static const char kPreamble[] = "LazyCompile:";
static size_t kPreambleLen = sizeof(kPreamble) - 1;
if (event->name.len < sizeof(kPreamble) - 1 ||
strncmp(kPreamble, event->name.str, kPreambleLen) != 0) {
return false;
}
const char* tail = event->name.str + kPreambleLen;
size_t tail_len = event->name.len - kPreambleLen;
size_t expected_len = strlen(expected);
if (tail_len == expected_len + 1) {
if (*tail == '*' || *tail == '~') {
--tail_len;
++tail;
} else {
return false;
}
}
if (tail_len != expected_len)
return false;
return strncmp(tail, expected, expected_len) == 0;
}
static void event_handler(const v8::JitCodeEvent* event) {
CHECK(event != NULL);
CHECK(code_map != NULL);
switch (event->type) {
case v8::JitCodeEvent::CODE_ADDED: {
CHECK(event->code_start != NULL);
CHECK_NE(0, static_cast<int>(event->code_len));
CHECK(event->name.str != NULL);
i::HashMap::Entry* entry =
code_map->Lookup(event->code_start,
i::ComputePointerHash(event->code_start),
true);
entry->value = reinterpret_cast<void*>(event->code_len);
if (FunctionNameIs("bar", event)) {
++saw_bar;
}
}
break;
case v8::JitCodeEvent::CODE_MOVED: {
uint32_t hash = i::ComputePointerHash(event->code_start);
// We would like to never see code move that we haven't seen before,
// but the code creation event does not happen until the line endings
// have been calculated (this is so that we can report the line in the
// script at which the function source is found, see
// Compiler::RecordFunctionCompilation) and the line endings
// calculations can cause a GC, which can move the newly created code
// before its existence can be logged.
i::HashMap::Entry* entry =
code_map->Lookup(event->code_start, hash, false);
if (entry != NULL) {
++move_events;
CHECK_EQ(reinterpret_cast<void*>(event->code_len), entry->value);
code_map->Remove(event->code_start, hash);
entry = code_map->Lookup(event->new_code_start,
i::ComputePointerHash(event->new_code_start),
true);
CHECK(entry != NULL);
entry->value = reinterpret_cast<void*>(event->code_len);
}
}
break;
case v8::JitCodeEvent::CODE_REMOVED:
// Object/code removal events are currently not dispatched from the GC.
CHECK(false);
break;
default:
// Impossible event.
CHECK(false);
break;
}
}
static bool MatchPointers(void* key1, void* key2) {
return key1 == key2;
}
TEST(SetJitCodeEventHandler) {
const char* script =
"function bar() {"
" var sum = 0;"
" for (i = 0; i < 100; ++i)"
" sum = foo(i);"
" return sum;"
"}"
"function foo(i) { return i * i; };"
"bar();";
// Run this test in a new isolate to make sure we don't
// have remnants of state from other code.
v8::Isolate* isolate = v8::Isolate::New();
isolate->Enter();
{
i::HashMap code(MatchPointers);
code_map = &code;
saw_bar = 0;
move_events = 0;
i::FLAG_stress_compaction = true;
V8::SetJitCodeEventHandler(v8::kJitCodeEventDefault, event_handler);
v8::HandleScope scope;
// Generate new code objects sparsely distributed across several
// different fragmented code-space pages.
const int kIterations = 10;
for (int i = 0; i < kIterations; ++i) {
LocalContext env;
v8::Handle<v8::Script> compiled_script;
{
i::AlwaysAllocateScope always_allocate;
SimulateFullSpace(HEAP->code_space());
compiled_script = v8_compile(script);
}
compiled_script->Run();
// Clear the compilation cache to get more wastage.
ISOLATE->compilation_cache()->Clear();
}
// Force code movement.
HEAP->CollectAllAvailableGarbage("TestSetJitCodeEventHandler");
CHECK_LE(kIterations, saw_bar);
CHECK_NE(0, move_events);
code_map = NULL;
V8::SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL);
}
isolate->Exit();
isolate->Dispose();
// Do this in a new isolate.
isolate = v8::Isolate::New();
isolate->Enter();
// Verify that we get callbacks for existing code objects when we
// request enumeration of existing code.
{
v8::HandleScope scope;
LocalContext env;
CompileRun(script);
// Now get code through initial iteration.
i::HashMap code(MatchPointers);
code_map = &code;
V8::SetJitCodeEventHandler(v8::kJitCodeEventEnumExisting, event_handler);
V8::SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL);
code_map = NULL;
// We expect that we got some events. Note that if we could get code removal
// notifications, we could compare two collections, one created by listening
// from the time of creation of an isolate, and the other by subscribing
// with EnumExisting.
CHECK_NE(0, code.occupancy());
}
isolate->Exit();
isolate->Dispose();
}
static int64_t cast(intptr_t x) { return static_cast<int64_t>(x); }
THREADED_TEST(ExternalAllocatedMemory) {
v8::HandleScope outer;
v8::Persistent<Context> env(Context::New());
CHECK(!env.IsEmpty());
const intptr_t kSize = 1024*1024;
CHECK_EQ(cast(v8::V8::AdjustAmountOfExternalAllocatedMemory(kSize)),
cast(kSize));
CHECK_EQ(cast(v8::V8::AdjustAmountOfExternalAllocatedMemory(-kSize)),
cast(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, date.As<v8::Date>()->NumberValue());
}
void CheckProperties(v8::Handle<v8::Value> val, int elmc, const char* elmv[]) {
v8::Handle<v8::Object> obj = val.As<v8::Object>();
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);
}
}
void CheckOwnProperties(v8::Handle<v8::Value> val,
int elmc,
const char* elmv[]) {
v8::Handle<v8::Object> obj = val.As<v8::Object>();
v8::Handle<v8::Array> props = obj->GetOwnPropertyNames();
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 = obj.As<v8::Array>();
CHECK_EQ(4, elms->Length());
int elmc0 = 0;
const char** elmv0 = NULL;
CheckProperties(elms->Get(v8::Integer::New(0)), elmc0, elmv0);
CheckOwnProperties(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);
CheckOwnProperties(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);
CheckOwnProperties(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);
int elmc4 = 2;
const char* elmv4[] = {"w", "z"};
CheckOwnProperties(elms->Get(v8::Integer::New(3)), elmc4, elmv4);
}
THREADED_TEST(PropertyEnumeration2) {
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 = obj.As<v8::Array>();
CHECK_EQ(4, elms->Length());
int elmc0 = 0;
const char** elmv0 = NULL;
CheckProperties(elms->Get(v8::Integer::New(0)), elmc0, elmv0);
v8::Handle<v8::Value> val = elms->Get(v8::Integer::New(0));
v8::Handle<v8::Array> props = val.As<v8::Object>()->GetPropertyNames();
CHECK_EQ(0, props->Length());
for (uint32_t i = 0; i < props->Length(); i++) {
printf("p[%d]\n", i);
}
}
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(!context0.IsEmpty());
CHECK(!constructor->access_check_info()->IsUndefined());
}
THREADED_TEST(TurnOnAccessCheck) {
v8::HandleScope handle_scope;
// Create an environment with access check to the global object disabled by
// default.
v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
global_template->SetAccessCheckCallbacks(NamedGetAccessBlocker,
IndexedGetAccessBlocker,
v8::Handle<v8::Value>(),
false);
v8::Persistent<Context> context = Context::New(NULL, global_template);
Context::Scope context_scope(context);
// Set up a property and a number of functions.
context->Global()->Set(v8_str("a"), v8_num(1));
CompileRun("function f1() {return a;}"
"function f2() {return a;}"
"function g1() {return h();}"
"function g2() {return h();}"
"function h() {return 1;}");
Local<Function> f1 =
Local<Function>::Cast(context->Global()->Get(v8_str("f1")));
Local<Function> f2 =
Local<Function>::Cast(context->Global()->Get(v8_str("f2")));
Local<Function> g1 =
Local<Function>::Cast(context->Global()->Get(v8_str("g1")));
Local<Function> g2 =
Local<Function>::Cast(context->Global()->Get(v8_str("g2")));
Local<Function> h =
Local<Function>::Cast(context->Global()->Get(v8_str("h")));
// Get the global object.
v8::Handle<v8::Object> global = context->Global();
// Call f1 one time and f2 a number of times. This will ensure that f1 still
// uses the runtime system to retreive property a whereas f2 uses global load
// inline cache.
CHECK(f1->Call(global, 0, NULL)->Equals(v8_num(1)));
for (int i = 0; i < 4; i++) {
CHECK(f2->Call(global, 0, NULL)->Equals(v8_num(1)));
}
// Same for g1 and g2.
CHECK(g1->Call(global, 0, NULL)->Equals(v8_num(1)));
for (int i = 0; i < 4; i++) {
CHECK(g2->Call(global, 0, NULL)->Equals(v8_num(1)));
}
// Detach the global and turn on access check.
context->DetachGlobal();
context->Global()->TurnOnAccessCheck();
// Failing access check to property get results in undefined.
CHECK(f1->Call(global, 0, NULL)->IsUndefined());
CHECK(f2->Call(global, 0, NULL)->IsUndefined());
// Failing access check to function call results in exception.
CHECK(g1->Call(global, 0, NULL).IsEmpty());
CHECK(g2->Call(global, 0, NULL).IsEmpty());
// No failing access check when just returning a constant.
CHECK(h->Call(global, 0, NULL)->Equals(v8_num(1)));
}
static const char* kPropertyA = "a";
static const char* kPropertyH = "h";
static bool NamedGetAccessBlockAandH(Local<v8::Object> obj,
Local<Value> name,
v8::AccessType type,
Local<Value> data) {
if (!name->IsString()) return false;
i::Handle<i::String> name_handle =
v8::Utils::OpenHandle(String::Cast(*name));
return !name_handle->IsUtf8EqualTo(i::CStrVector(kPropertyA))
&& !name_handle->IsUtf8EqualTo(i::CStrVector(kPropertyH));
}
THREADED_TEST(TurnOnAccessCheckAndRecompile) {
v8::HandleScope handle_scope;
// Create an environment with access check to the global object disabled by
// default. When the registered access checker will block access to properties
// a and h.
v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
global_template->SetAccessCheckCallbacks(NamedGetAccessBlockAandH,
IndexedGetAccessBlocker,
v8::Handle<v8::Value>(),
false);
v8::Persistent<Context> context = Context::New(NULL, global_template);
Context::Scope context_scope(context);
// Set up a property and a number of functions.
context->Global()->Set(v8_str("a"), v8_num(1));
static const char* source = "function f1() {return a;}"
"function f2() {return a;}"
"function g1() {return h();}"
"function g2() {return h();}"
"function h() {return 1;}";
CompileRun(source);
Local<Function> f1;
Local<Function> f2;
Local<Function> g1;
Local<Function> g2;
Local<Function> h;
f1 = Local<Function>::Cast(context->Global()->Get(v8_str("f1")));
f2 = Local<Function>::Cast(context->Global()->Get(v8_str("f2")));
g1 = Local<Function>::Cast(context->Global()->Get(v8_str("g1")));
g2 = Local<Function>::Cast(context->Global()->Get(v8_str("g2")));
h = Local<Function>::Cast(context->Global()->Get(v8_str("h")));
// Get the global object.
v8::Handle<v8::Object> global = context->Global();
// Call f1 one time and f2 a number of times. This will ensure that f1 still
// uses the runtime system to retreive property a whereas f2 uses global load
// inline cache.
CHECK(f1->Call(global, 0, NULL)->Equals(v8_num(1)));
for (int i = 0; i < 4; i++) {
CHECK(f2->Call(global, 0, NULL)->Equals(v8_num(1)));
}
// Same for g1 and g2.
CHECK(g1->Call(global, 0, NULL)->Equals(v8_num(1)));
for (int i = 0; i < 4; i++) {
CHECK(g2->Call(global, 0, NULL)->Equals(v8_num(1)));
}
// Detach the global and turn on access check now blocking access to property
// a and function h.
context->DetachGlobal();
context->Global()->TurnOnAccessCheck();
// Failing access check to property get results in undefined.
CHECK(f1->Call(global, 0, NULL)->IsUndefined());
CHECK(f2->Call(global, 0, NULL)->IsUndefined());
// Failing access check to function call results in exception.
CHECK(g1->Call(global, 0, NULL).IsEmpty());
CHECK(g2->Call(global, 0, NULL).IsEmpty());
// No failing access check when just returning a constant.
CHECK(h->Call(global, 0, NULL)->Equals(v8_num(1)));
// Now compile the source again. And get the newly compiled functions, except
// for h for which access is blocked.
CompileRun(source);
f1 = Local<Function>::Cast(context->Global()->Get(v8_str("f1")));
f2 = Local<Function>::Cast(context->Global()->Get(v8_str("f2")));
g1 = Local<Function>::Cast(context->Global()->Get(v8_str("g1")));
g2 = Local<Function>::Cast(context->Global()->Get(v8_str("g2")));
CHECK(context->Global()->Get(v8_str("h"))->IsUndefined());
// Failing access check to property get results in undefined.
CHECK(f1->Call(global, 0, NULL)->IsUndefined());
CHECK(f2->Call(global, 0, NULL)->IsUndefined());
// Failing access check to function call results in exception.
CHECK(g1->Call(global, 0, NULL).IsEmpty());
CHECK(g2->Call(global, 0, NULL).IsEmpty());
}
// 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, i::StrLength(script));
CHECK_NE(sd->Length(), 0);
CHECK_NE(sd->Data(), NULL);
CHECK(!sd->HasError());
delete sd;
}
TEST(PreCompileWithError) {
v8::V8::Initialize();
const char* script = "function foo(a) { return 1 * * 2; }";
v8::ScriptData* sd =
v8::ScriptData::PreCompile(script, i::StrLength(script));
CHECK(sd->HasError());
delete sd;
}
TEST(Regress31661) {
v8::V8::Initialize();
const char* script = " The Definintive Guide";
v8::ScriptData* sd =
v8::ScriptData::PreCompile(script, i::StrLength(script));
CHECK(sd->HasError());
delete sd;
}
// Tests that ScriptData can be serialized and deserialized.
TEST(PreCompileSerialization) {
v8::V8::Initialize();
const char* script = "function foo(a) { return a+1; }";
v8::ScriptData* sd =
v8::ScriptData::PreCompile(script, i::StrLength(script));
// Serialize.
int serialized_data_length = sd->Length();
char* serialized_data = i::NewArray<char>(serialized_data_length);
memcpy(serialized_data, sd->Data(), serialized_data_length);
// Deserialize.
v8::ScriptData* deserialized_sd =
v8::ScriptData::New(serialized_data, serialized_data_length);
// Verify that the original is the same as the deserialized.
CHECK_EQ(sd->Length(), deserialized_sd->Length());
CHECK_EQ(0, memcmp(sd->Data(), deserialized_sd->Data(), sd->Length()));
CHECK_EQ(sd->HasError(), deserialized_sd->HasError());
delete sd;
delete deserialized_sd;
}
// Attempts to deserialize bad data.
TEST(PreCompileDeserializationError) {
v8::V8::Initialize();
const char* data = "DONT CARE";
int invalid_size = 3;
v8::ScriptData* sd = v8::ScriptData::New(data, invalid_size);
CHECK_EQ(0, sd->Length());
delete sd;
}
// Attempts to deserialize bad data.
TEST(PreCompileInvalidPreparseDataError) {
v8::V8::Initialize();
v8::HandleScope scope;
LocalContext context;
const char* script = "function foo(){ return 5;}\n"
"function bar(){ return 6 + 7;} foo();";
v8::ScriptData* sd =
v8::ScriptData::PreCompile(script, i::StrLength(script));
CHECK(!sd->HasError());
// ScriptDataImpl private implementation details
const int kHeaderSize = i::PreparseDataConstants::kHeaderSize;
const int kFunctionEntrySize = i::FunctionEntry::kSize;
const int kFunctionEntryStartOffset = 0;
const int kFunctionEntryEndOffset = 1;
unsigned* sd_data =
reinterpret_cast<unsigned*>(const_cast<char*>(sd->Data()));
// Overwrite function bar's end position with 0.
sd_data[kHeaderSize + 1 * kFunctionEntrySize + kFunctionEntryEndOffset] = 0;
v8::TryCatch try_catch;
Local<String> source = String::New(script);
Local<Script> compiled_script = Script::New(source, NULL, sd);
CHECK(try_catch.HasCaught());
String::AsciiValue exception_value(try_catch.Message()->Get());
CHECK_EQ("Uncaught SyntaxError: Invalid preparser data for function bar",
*exception_value);
try_catch.Reset();
// Overwrite function bar's start position with 200. The function entry
// will not be found when searching for it by position and we should fall
// back on eager compilation.
sd = v8::ScriptData::PreCompile(script, i::StrLength(script));
sd_data = reinterpret_cast<unsigned*>(const_cast<char*>(sd->Data()));
sd_data[kHeaderSize + 1 * kFunctionEntrySize + kFunctionEntryStartOffset] =
200;
compiled_script = Script::New(source, NULL, sd);
CHECK(!try_catch.HasCaught());
delete sd;
}
// Verifies that the Handle<String> and const char* versions of the API produce
// the same results (at least for one trivial case).
TEST(PreCompileAPIVariationsAreSame) {
v8::V8::Initialize();
v8::HandleScope scope;
const char* cstring = "function foo(a) { return a+1; }";
v8::ScriptData* sd_from_cstring =
v8::ScriptData::PreCompile(cstring, i::StrLength(cstring));
TestAsciiResource* resource = new TestAsciiResource(cstring);
v8::ScriptData* sd_from_external_string = v8::ScriptData::PreCompile(
v8::String::NewExternal(resource));
v8::ScriptData* sd_from_string = v8::ScriptData::PreCompile(
v8::String::New(cstring));
CHECK_EQ(sd_from_cstring->Length(), sd_from_external_string->Length());
CHECK_EQ(0, memcmp(sd_from_cstring->Data(),
sd_from_external_string->Data(),
sd_from_cstring->Length()));
CHECK_EQ(sd_from_cstring->Length(), sd_from_string->Length());
CHECK_EQ(0, memcmp(sd_from_cstring->Data(),
sd_from_string->Data(),
sd_from_cstring->Length()));
delete sd_from_cstring;
delete sd_from_external_string;
delete sd_from_string;
}
// 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(CcTest::default_isolate());
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)
: Thread("GCThread"), test_(test) {}
virtual void Run() {
test_->CollectGarbage();
}
private:
RegExpInterruptTest* test_;
};
void CollectGarbage() {
block_->Wait();
while (gc_during_regexp_ < kRequiredGCs) {
{
v8::Locker lock(CcTest::default_isolate());
// TODO(lrn): Perhaps create some garbage before collecting.
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
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(CcTest::default_isolate());
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(CcTest::default_isolate());
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)
: Thread("GCThread"), test_(test) {}
virtual void Run() {
test_->CollectGarbage();
}
private:
ApplyInterruptTest* test_;
};
void CollectGarbage() {
block_->Wait();
while (gc_during_apply_ < kRequiredGCs) {
{
v8::Locker lock(CcTest::default_isolate());
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
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(CcTest::default_isolate());
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 = val.As<v8::Object>();
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->IsOneByteRepresentation()) {
// Check old map is not symbol or long.
CHECK(string->map() == HEAP->external_ascii_string_map());
// Morph external string to be TwoByte string.
string->set_map(HEAP->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() == HEAP->external_string_map());
// Morph external string to be ASCII string.
string->set_map(HEAP->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) {
char utf_buffer[129];
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, i::StrLength(c_string)));
UC16VectorResource uc16_resource(
i::Vector<const uint16_t>(two_byte_string,
i::StrLength(c_string)));
Local<String> lhs(v8::Utils::ToLocal(
FACTORY->NewExternalStringFromAscii(&ascii_resource)));
Local<String> rhs(v8::Utils::ToLocal(
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);");
#ifndef ENABLE_LATIN_1
CHECK(!lhs->MayContainNonAscii());
CHECK(!rhs->MayContainNonAscii());
#endif
MorphAString(*v8::Utils::OpenHandle(*lhs), &ascii_resource, &uc16_resource);
MorphAString(*v8::Utils::OpenHandle(*rhs), &ascii_resource, &uc16_resource);
// This should UTF-8 without flattening, since everything is ASCII.
Handle<String> cons = v8_compile("cons")->Run().As<String>();
CHECK_EQ(128, cons->Utf8Length());
int nchars = -1;
CHECK_EQ(129, cons->WriteUtf8(utf_buffer, -1, &nchars));
CHECK_EQ(128, nchars);
CHECK_EQ(0, strcmp(
utf_buffer,
"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"));
// 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")));
}
i::DeleteArray(two_byte_string);
}
TEST(CompileExternalTwoByteSource) {
v8::HandleScope scope;
LocalContext context;
// This is a very short list of sources, which currently is to check for a
// regression caused by r2703.
const char* ascii_sources[] = {
"0.5",
"-0.5", // This mainly testes PushBack in the Scanner.
"--0.5", // This mainly testes PushBack in the Scanner.
NULL
};
// Compile the sources as external two byte strings.
for (int i = 0; ascii_sources[i] != NULL; i++) {
uint16_t* two_byte_string = AsciiToTwoByteString(ascii_sources[i]);
UC16VectorResource uc16_resource(
i::Vector<const uint16_t>(two_byte_string,
i::StrLength(ascii_sources[i])));
v8::Local<v8::String> source = v8::String::NewExternal(&uc16_resource);
v8::Script::Compile(source);
i::DeleteArray(two_byte_string);
}
}
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_ = FACTORY->NewExternalStringFromAscii(&ascii_resource_);
// Inject the input as a global variable.
i::Handle<i::String> input_name =
FACTORY->NewStringFromAscii(i::Vector<const char>("input", 5));
i::Isolate::Current()->native_context()->global_object()->SetProperty(
*input_name,
*input_,
NONE,
i::kNonStrictMode)->ToObjectChecked();
MorphThread morph_thread(this);
morph_thread.Start();
v8::Locker::StartPreemption(1);
LongRunningRegExp();
{
v8::Unlocker unlock(CcTest::default_isolate());
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)
: Thread("MorphThread"), test_(test) {}
virtual void Run() {
test_->MorphString();
}
private:
RegExpStringModificationTest* test_;
};
void MorphString() {
block_->Wait();
while (morphs_during_regexp_ < kRequiredModifications &&
morphs_ < kMaxModifications) {
{
v8::Locker lock(CcTest::default_isolate());
// 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_;
{
v8::HandleScope scope;
// 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(CcTest::default_isolate());
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 cannot set a property on the global object if there
// is a read-only property in the prototype chain.
TEST(ReadOnlyPropertyInGlobalProto) {
i::FLAG_es5_readonly = true;
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_EQ(v8::Integer::New(0), res);
// Check with 'eval'.
res = CompileRun("function f() { eval('1'); y = 43; return y; }; f()");
CHECK_EQ(v8::Integer::New(0), res);
// Check with 'with'.
res = CompileRun("function f() { with (this) { y = 44 }; return y; }; f()");
CHECK_EQ(v8::Integer::New(0), 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);
}
THREADED_TEST(ForceDelete) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = v8::ObjectTemplate::New();
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::DontDelete);
CHECK_EQ(4, global->Get(simple_property)->Int32Value());
// This should fail because the property is dont-delete.
CHECK(!global->Delete(simple_property));
CHECK_EQ(4, global->Get(simple_property)->Int32Value());
// This should succeed even though the property is dont-delete.
CHECK(global->ForceDelete(simple_property));
CHECK(global->Get(simple_property)->IsUndefined());
}
static int force_delete_interceptor_count = 0;
static bool pass_on_delete = false;
static v8::Handle<v8::Boolean> ForceDeleteDeleter(
v8::Local<v8::String> name,
const v8::AccessorInfo& info) {
force_delete_interceptor_count++;
if (pass_on_delete) {
return v8::Handle<v8::Boolean>();
} else {
return v8::True();
}
}
THREADED_TEST(ForceDeleteWithInterceptor) {
force_delete_interceptor_count = 0;
pass_on_delete = false;
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = v8::ObjectTemplate::New();
templ->SetNamedPropertyHandler(0, 0, 0, ForceDeleteDeleter);
LocalContext context(NULL, templ);
v8::Handle<v8::Object> global = context->Global();
v8::Handle<v8::String> some_property = v8::String::New("a");
global->Set(some_property, v8::Integer::New(42), v8::DontDelete);
// Deleting a property should get intercepted and nothing should
// happen.
CHECK_EQ(0, force_delete_interceptor_count);
CHECK(global->Delete(some_property));
CHECK_EQ(1, force_delete_interceptor_count);
CHECK_EQ(42, global->Get(some_property)->Int32Value());
// Deleting the property when the interceptor returns an empty
// handle should not delete the property since it is DontDelete.
pass_on_delete = true;
CHECK(!global->Delete(some_property));
CHECK_EQ(2, force_delete_interceptor_count);
CHECK_EQ(42, global->Get(some_property)->Int32Value());
// Forcing the property to be deleted should delete the value
// without calling the interceptor.
CHECK(global->ForceDelete(some_property));
CHECK(global->Get(some_property)->IsUndefined());
CHECK_EQ(2, force_delete_interceptor_count);
}
// Make sure that forcing a delete invalidates any IC stubs, so we
// don't read the hole value.
THREADED_TEST(ForceDeleteIC) {
v8::HandleScope scope;
LocalContext context;
// Create a DontDelete variable on the global object.
CompileRun("this.__proto__ = { foo: 'horse' };"
"var foo = 'fish';"
"function f() { return foo.length; }");
// Initialize the IC for foo in f.
CompileRun("for (var i = 0; i < 4; i++) f();");
// Make sure the value of foo is correct before the deletion.
CHECK_EQ(4, CompileRun("f()")->Int32Value());
// Force the deletion of foo.
CHECK(context->Global()->ForceDelete(v8_str("foo")));
// Make sure the value for foo is read from the prototype, and that
// we don't get in trouble with reading the deleted cell value
// sentinel.
CHECK_EQ(5, CompileRun("f()")->Int32Value());
}
TEST(InlinedFunctionAcrossContexts) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope outer_scope;
v8::Persistent<v8::Context> ctx1 = v8::Context::New();
v8::Persistent<v8::Context> ctx2 = v8::Context::New();
ctx1->Enter();
{
v8::HandleScope inner_scope;
CompileRun("var G = 42; function foo() { return G; }");
v8::Local<v8::Value> foo = ctx1->Global()->Get(v8_str("foo"));
ctx2->Enter();
ctx2->Global()->Set(v8_str("o"), foo);
v8::Local<v8::Value> res = CompileRun(
"function f() { return o(); }"
"for (var i = 0; i < 10; ++i) f();"
"%OptimizeFunctionOnNextCall(f);"
"f();");
CHECK_EQ(42, res->Int32Value());
ctx2->Exit();
v8::Handle<v8::String> G_property = v8::String::New("G");
CHECK(ctx1->Global()->ForceDelete(G_property));
ctx2->Enter();
ExpectString(
"(function() {"
" try {"
" return f();"
" } catch(e) {"
" return e.toString();"
" }"
" })()",
"ReferenceError: G is not defined");
ctx2->Exit();
ctx1->Exit();
ctx1.Dispose();
}
ctx2.Dispose();
}
v8::Persistent<Context> calling_context0;
v8::Persistent<Context> calling_context1;
v8::Persistent<Context> calling_context2;
// Check that the call to the callback is initiated in
// calling_context2, the directly calling context is calling_context1
// and the callback itself is in calling_context0.
static v8::Handle<Value> GetCallingContextCallback(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz();
CHECK(Context::GetCurrent() == calling_context0);
CHECK(Context::GetCalling() == calling_context1);
CHECK(Context::GetEntered() == calling_context2);
return v8::Integer::New(42);
}
THREADED_TEST(GetCallingContext) {
v8::HandleScope scope;
calling_context0 = Context::New();
calling_context1 = Context::New();
calling_context2 = Context::New();
// Allow cross-domain access.
Local<String> token = v8_str("<security token>");
calling_context0->SetSecurityToken(token);
calling_context1->SetSecurityToken(token);
calling_context2->SetSecurityToken(token);
// Create an object with a C++ callback in context0.
calling_context0->Enter();
Local<v8::FunctionTemplate> callback_templ =
v8::FunctionTemplate::New(GetCallingContextCallback);
calling_context0->Global()->Set(v8_str("callback"),
callback_templ->GetFunction());
calling_context0->Exit();
// Expose context0 in context1 and set up a function that calls the
// callback function.
calling_context1->Enter();
calling_context1->Global()->Set(v8_str("context0"),
calling_context0->Global());
CompileRun("function f() { context0.callback() }");
calling_context1->Exit();
// Expose context1 in context2 and call the callback function in
// context0 indirectly through f in context1.
calling_context2->Enter();
calling_context2->Global()->Set(v8_str("context1"),
calling_context1->Global());
CompileRun("context1.f()");
calling_context2->Exit();
// Dispose the contexts to allow them to be garbage collected.
calling_context0.Dispose();
calling_context1.Dispose();
calling_context2.Dispose();
calling_context0.Clear();
calling_context1.Clear();
calling_context2.Clear();
}
// Check that a variable declaration with no explicit initialization
// value does shadow an existing property in the prototype chain.
THREADED_TEST(InitGlobalVarInProtoChain) {
i::FLAG_es52_globals = true;
v8::HandleScope scope;
LocalContext context;
// Introduce a variable in the prototype chain.
CompileRun("__proto__.x = 42");
v8::Handle<v8::Value> result = CompileRun("var x = 43; x");
CHECK(!result->IsUndefined());
CHECK_EQ(43, result->Int32Value());
}
// Regression test for issue 398.
// If a function is added to an object, creating a constant function
// field, and the result is cloned, replacing the constant function on the
// original should not affect the clone.
// See http://code.google.com/p/v8/issues/detail?id=398
THREADED_TEST(ReplaceConstantFunction) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<v8::Object> obj = v8::Object::New();
v8::Handle<v8::FunctionTemplate> func_templ = v8::FunctionTemplate::New();
v8::Handle<v8::String> foo_string = v8::String::New("foo");
obj->Set(foo_string, func_templ->GetFunction());
v8::Handle<v8::Object> obj_clone = obj->Clone();
obj_clone->Set(foo_string, v8::String::New("Hello"));
CHECK(!obj->Get(foo_string)->IsUndefined());
}
// Regression test for http://crbug.com/16276.
THREADED_TEST(Regress16276) {
v8::HandleScope scope;
LocalContext context;
// Force the IC in f to be a dictionary load IC.
CompileRun("function f(obj) { return obj.x; }\n"
"var obj = { x: { foo: 42 }, y: 87 };\n"
"var x = obj.x;\n"
"delete obj.y;\n"
"for (var i = 0; i < 5; i++) f(obj);");
// Detach the global object to make 'this' refer directly to the
// global object (not the proxy), and make sure that the dictionary
// load IC doesn't mess up loading directly from the global object.
context->DetachGlobal();
CHECK_EQ(42, CompileRun("f(this).foo")->Int32Value());
}
THREADED_TEST(PixelArray) {
v8::HandleScope scope;
LocalContext context;
const int kElementCount = 260;
uint8_t* pixel_data = reinterpret_cast<uint8_t*>(malloc(kElementCount));
i::Handle<i::ExternalPixelArray> pixels =
i::Handle<i::ExternalPixelArray>::cast(
FACTORY->NewExternalArray(kElementCount,
v8::kExternalPixelArray,
pixel_data));
// Force GC to trigger verification.
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
for (int i = 0; i < kElementCount; i++) {
pixels->set(i, i % 256);
}
// Force GC to trigger verification.
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
for (int i = 0; i < kElementCount; i++) {
CHECK_EQ(i % 256, pixels->get_scalar(i));
CHECK_EQ(i % 256, pixel_data[i]);
}
v8::Handle<v8::Object> obj = v8::Object::New();
i::Handle<i::JSObject> jsobj = v8::Utils::OpenHandle(*obj);
// Set the elements to be the pixels.
// jsobj->set_elements(*pixels);
obj->SetIndexedPropertiesToPixelData(pixel_data, kElementCount);
CHECK_EQ(1, i::Smi::cast(jsobj->GetElement(1)->ToObjectChecked())->value());
obj->Set(v8_str("field"), v8::Int32::New(1503));
context->Global()->Set(v8_str("pixels"), obj);
v8::Handle<v8::Value> result = CompileRun("pixels.field");
CHECK_EQ(1503, result->Int32Value());
result = CompileRun("pixels[1]");
CHECK_EQ(1, result->Int32Value());
result = CompileRun("var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += pixels[i] = pixels[i] = -i;"
"}"
"sum;");
CHECK_EQ(-28, result->Int32Value());
result = CompileRun("var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += pixels[i] = pixels[i] = 0;"
"}"
"sum;");
CHECK_EQ(0, result->Int32Value());
result = CompileRun("var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += pixels[i] = pixels[i] = 255;"
"}"
"sum;");
CHECK_EQ(8 * 255, result->Int32Value());
result = CompileRun("var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += pixels[i] = pixels[i] = 256 + i;"
"}"
"sum;");
CHECK_EQ(2076, result->Int32Value());
result = CompileRun("var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += pixels[i] = pixels[i] = i;"
"}"
"sum;");
CHECK_EQ(28, result->Int32Value());
result = CompileRun("var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += pixels[i];"
"}"
"sum;");
CHECK_EQ(28, result->Int32Value());
i::Handle<i::Smi> value(i::Smi::FromInt(2));
i::Handle<i::Object> no_failure;
no_failure =
i::JSObject::SetElement(jsobj, 1, value, NONE, i::kNonStrictMode);
ASSERT(!no_failure.is_null());
i::USE(no_failure);
CHECK_EQ(2, i::Smi::cast(jsobj->GetElement(1)->ToObjectChecked())->value());
*value.location() = i::Smi::FromInt(256);
no_failure =
i::JSObject::SetElement(jsobj, 1, value, NONE, i::kNonStrictMode);
ASSERT(!no_failure.is_null());
i::USE(no_failure);
CHECK_EQ(255,
i::Smi::cast(jsobj->GetElement(1)->ToObjectChecked())->value());
*value.location() = i::Smi::FromInt(-1);
no_failure =
i::JSObject::SetElement(jsobj, 1, value, NONE, i::kNonStrictMode);
ASSERT(!no_failure.is_null());
i::USE(no_failure);
CHECK_EQ(0, i::Smi::cast(jsobj->GetElement(1)->ToObjectChecked())->value());
result = CompileRun("for (var i = 0; i < 8; i++) {"
" pixels[i] = (i * 65) - 109;"
"}"
"pixels[1] + pixels[6];");
CHECK_EQ(255, result->Int32Value());
CHECK_EQ(0, i::Smi::cast(jsobj->GetElement(0)->ToObjectChecked())->value());
CHECK_EQ(0, i::Smi::cast(jsobj->GetElement(1)->ToObjectChecked())->value());
CHECK_EQ(21,
i::Smi::cast(jsobj->GetElement(2)->ToObjectChecked())->value());
CHECK_EQ(86,
i::Smi::cast(jsobj->GetElement(3)->ToObjectChecked())->value());
CHECK_EQ(151,
i::Smi::cast(jsobj->GetElement(4)->ToObjectChecked())->value());
CHECK_EQ(216,
i::Smi::cast(jsobj->GetElement(5)->ToObjectChecked())->value());
CHECK_EQ(255,
i::Smi::cast(jsobj->GetElement(6)->ToObjectChecked())->value());
CHECK_EQ(255,
i::Smi::cast(jsobj->GetElement(7)->ToObjectChecked())->value());
result = CompileRun("var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += pixels[i];"
"}"
"sum;");
CHECK_EQ(984, result->Int32Value());
result = CompileRun("for (var i = 0; i < 8; i++) {"
" pixels[i] = (i * 1.1);"
"}"
"pixels[1] + pixels[6];");
CHECK_EQ(8, result->Int32Value());
CHECK_EQ(0, i::Smi::cast(jsobj->GetElement(0)->ToObjectChecked())->value());
CHECK_EQ(1, i::Smi::cast(jsobj->GetElement(1)->ToObjectChecked())->value());
CHECK_EQ(2, i::Smi::cast(jsobj->GetElement(2)->ToObjectChecked())->value());
CHECK_EQ(3, i::Smi::cast(jsobj->GetElement(3)->ToObjectChecked())->value());
CHECK_EQ(4, i::Smi::cast(jsobj->GetElement(4)->ToObjectChecked())->value());
CHECK_EQ(6, i::Smi::cast(jsobj->GetElement(5)->ToObjectChecked())->value());
CHECK_EQ(7, i::Smi::cast(jsobj->GetElement(6)->ToObjectChecked())->value());
CHECK_EQ(8, i::Smi::cast(jsobj->GetElement(7)->ToObjectChecked())->value());
result = CompileRun("for (var i = 0; i < 8; i++) {"
" pixels[7] = undefined;"
"}"
"pixels[7];");
CHECK_EQ(0, result->Int32Value());
CHECK_EQ(0, i::Smi::cast(jsobj->GetElement(7)->ToObjectChecked())->value());
result = CompileRun("for (var i = 0; i < 8; i++) {"
" pixels[6] = '2.3';"
"}"
"pixels[6];");
CHECK_EQ(2, result->Int32Value());
CHECK_EQ(2, i::Smi::cast(jsobj->GetElement(6)->ToObjectChecked())->value());
result = CompileRun("for (var i = 0; i < 8; i++) {"
" pixels[5] = NaN;"
"}"
"pixels[5];");
CHECK_EQ(0, result->Int32Value());
CHECK_EQ(0, i::Smi::cast(jsobj->GetElement(5)->ToObjectChecked())->value());
result = CompileRun("for (var i = 0; i < 8; i++) {"
" pixels[8] = Infinity;"
"}"
"pixels[8];");
CHECK_EQ(255, result->Int32Value());
CHECK_EQ(255,
i::Smi::cast(jsobj->GetElement(8)->ToObjectChecked())->value());
result = CompileRun("for (var i = 0; i < 8; i++) {"
" pixels[9] = -Infinity;"
"}"
"pixels[9];");
CHECK_EQ(0, result->Int32Value());
CHECK_EQ(0, i::Smi::cast(jsobj->GetElement(9)->ToObjectChecked())->value());
result = CompileRun("pixels[3] = 33;"
"delete pixels[3];"
"pixels[3];");
CHECK_EQ(33, result->Int32Value());
result = CompileRun("pixels[0] = 10; pixels[1] = 11;"
"pixels[2] = 12; pixels[3] = 13;"
"pixels.__defineGetter__('2',"
"function() { return 120; });"
"pixels[2];");
CHECK_EQ(12, result->Int32Value());
result = CompileRun("var js_array = new Array(40);"
"js_array[0] = 77;"
"js_array;");
CHECK_EQ(77, v8::Object::Cast(*result)->Get(v8_str("0"))->Int32Value());
result = CompileRun("pixels[1] = 23;"
"pixels.__proto__ = [];"
"js_array.__proto__ = pixels;"
"js_array.concat(pixels);");
CHECK_EQ(77, v8::Object::Cast(*result)->Get(v8_str("0"))->Int32Value());
CHECK_EQ(23, v8::Object::Cast(*result)->Get(v8_str("1"))->Int32Value());
result = CompileRun("pixels[1] = 23;");
CHECK_EQ(23, result->Int32Value());
// Test for index greater than 255. Regression test for:
// http://code.google.com/p/chromium/issues/detail?id=26337.
result = CompileRun("pixels[256] = 255;");
CHECK_EQ(255, result->Int32Value());
result = CompileRun("var i = 0;"
"for (var j = 0; j < 8; j++) { i = pixels[256]; }"
"i");
CHECK_EQ(255, result->Int32Value());
// Make sure that pixel array ICs recognize when a non-pixel array
// is passed to it.
result = CompileRun("function pa_load(p) {"
" var sum = 0;"
" for (var j = 0; j < 256; j++) { sum += p[j]; }"
" return sum;"
"}"
"for (var i = 0; i < 256; ++i) { pixels[i] = i; }"
"for (var i = 0; i < 10; ++i) { pa_load(pixels); }"
"just_ints = new Object();"
"for (var i = 0; i < 256; ++i) { just_ints[i] = i; }"
"for (var i = 0; i < 10; ++i) {"
" result = pa_load(just_ints);"
"}"
"result");
CHECK_EQ(32640, result->Int32Value());
// Make sure that pixel array ICs recognize out-of-bound accesses.
result = CompileRun("function pa_load(p, start) {"
" var sum = 0;"
" for (var j = start; j < 256; j++) { sum += p[j]; }"
" return sum;"
"}"
"for (var i = 0; i < 256; ++i) { pixels[i] = i; }"
"for (var i = 0; i < 10; ++i) { pa_load(pixels,0); }"
"for (var i = 0; i < 10; ++i) {"
" result = pa_load(pixels,-10);"
"}"
"result");
CHECK_EQ(0, result->Int32Value());
// Make sure that generic ICs properly handles a pixel array.
result = CompileRun("function pa_load(p) {"
" var sum = 0;"
" for (var j = 0; j < 256; j++) { sum += p[j]; }"
" return sum;"
"}"
"for (var i = 0; i < 256; ++i) { pixels[i] = i; }"
"just_ints = new Object();"
"for (var i = 0; i < 256; ++i) { just_ints[i] = i; }"
"for (var i = 0; i < 10; ++i) { pa_load(just_ints); }"
"for (var i = 0; i < 10; ++i) {"
" result = pa_load(pixels);"
"}"
"result");
CHECK_EQ(32640, result->Int32Value());
// Make sure that generic load ICs recognize out-of-bound accesses in
// pixel arrays.
result = CompileRun("function pa_load(p, start) {"
" var sum = 0;"
" for (var j = start; j < 256; j++) { sum += p[j]; }"
" return sum;"
"}"
"for (var i = 0; i < 256; ++i) { pixels[i] = i; }"
"just_ints = new Object();"
"for (var i = 0; i < 256; ++i) { just_ints[i] = i; }"
"for (var i = 0; i < 10; ++i) { pa_load(just_ints,0); }"
"for (var i = 0; i < 10; ++i) { pa_load(pixels,0); }"
"for (var i = 0; i < 10; ++i) {"
" result = pa_load(pixels,-10);"
"}"
"result");
CHECK_EQ(0, result->Int32Value());
// Make sure that generic ICs properly handles other types than pixel
// arrays (that the inlined fast pixel array test leaves the right information
// in the right registers).
result = CompileRun("function pa_load(p) {"
" var sum = 0;"
" for (var j = 0; j < 256; j++) { sum += p[j]; }"
" return sum;"
"}"
"for (var i = 0; i < 256; ++i) { pixels[i] = i; }"
"just_ints = new Object();"
"for (var i = 0; i < 256; ++i) { just_ints[i] = i; }"
"for (var i = 0; i < 10; ++i) { pa_load(just_ints); }"
"for (var i = 0; i < 10; ++i) { pa_load(pixels); }"
"sparse_array = new Object();"
"for (var i = 0; i < 256; ++i) { sparse_array[i] = i; }"
"sparse_array[1000000] = 3;"
"for (var i = 0; i < 10; ++i) {"
" result = pa_load(sparse_array);"
"}"
"result");
CHECK_EQ(32640, result->Int32Value());
// Make sure that pixel array store ICs clamp values correctly.
result = CompileRun("function pa_store(p) {"
" for (var j = 0; j < 256; j++) { p[j] = j * 2; }"
"}"
"pa_store(pixels);"
"var sum = 0;"
"for (var j = 0; j < 256; j++) { sum += pixels[j]; }"
"sum");
CHECK_EQ(48896, result->Int32Value());
// Make sure that pixel array stores correctly handle accesses outside
// of the pixel array..
result = CompileRun("function pa_store(p,start) {"
" for (var j = 0; j < 256; j++) {"
" p[j+start] = j * 2;"
" }"
"}"
"pa_store(pixels,0);"
"pa_store(pixels,-128);"
"var sum = 0;"
"for (var j = 0; j < 256; j++) { sum += pixels[j]; }"
"sum");
CHECK_EQ(65280, result->Int32Value());
// Make sure that the generic store stub correctly handle accesses outside
// of the pixel array..
result = CompileRun("function pa_store(p,start) {"
" for (var j = 0; j < 256; j++) {"
" p[j+start] = j * 2;"
" }"
"}"
"pa_store(pixels,0);"
"just_ints = new Object();"
"for (var i = 0; i < 256; ++i) { just_ints[i] = i; }"
"pa_store(just_ints, 0);"
"pa_store(pixels,-128);"
"var sum = 0;"
"for (var j = 0; j < 256; j++) { sum += pixels[j]; }"
"sum");
CHECK_EQ(65280, result->Int32Value());
// Make sure that the generic keyed store stub clamps pixel array values
// correctly.
result = CompileRun("function pa_store(p) {"
" for (var j = 0; j < 256; j++) { p[j] = j * 2; }"
"}"
"pa_store(pixels);"
"just_ints = new Object();"
"pa_store(just_ints);"
"pa_store(pixels);"
"var sum = 0;"
"for (var j = 0; j < 256; j++) { sum += pixels[j]; }"
"sum");
CHECK_EQ(48896, result->Int32Value());
// Make sure that pixel array loads are optimized by crankshaft.
result = CompileRun("function pa_load(p) {"
" var sum = 0;"
" for (var i=0; i<256; ++i) {"
" sum += p[i];"
" }"
" return sum; "
"}"
"for (var i = 0; i < 256; ++i) { pixels[i] = i; }"
"for (var i = 0; i < 5000; ++i) {"
" result = pa_load(pixels);"
"}"
"result");
CHECK_EQ(32640, result->Int32Value());
// Make sure that pixel array stores are optimized by crankshaft.
result = CompileRun("function pa_init(p) {"
"for (var i = 0; i < 256; ++i) { p[i] = i; }"
"}"
"function pa_load(p) {"
" var sum = 0;"
" for (var i=0; i<256; ++i) {"
" sum += p[i];"
" }"
" return sum; "
"}"
"for (var i = 0; i < 5000; ++i) {"
" pa_init(pixels);"
"}"
"result = pa_load(pixels);"
"result");
CHECK_EQ(32640, result->Int32Value());
free(pixel_data);
}
THREADED_TEST(PixelArrayInfo) {
v8::HandleScope scope;
LocalContext context;
for (int size = 0; size < 100; size += 10) {
uint8_t* pixel_data = reinterpret_cast<uint8_t*>(malloc(size));
v8::Handle<v8::Object> obj = v8::Object::New();
obj->SetIndexedPropertiesToPixelData(pixel_data, size);
CHECK(obj->HasIndexedPropertiesInPixelData());
CHECK_EQ(pixel_data, obj->GetIndexedPropertiesPixelData());
CHECK_EQ(size, obj->GetIndexedPropertiesPixelDataLength());
free(pixel_data);
}
}
static v8::Handle<Value> NotHandledIndexedPropertyGetter(
uint32_t index,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
return v8::Handle<Value>();
}
static v8::Handle<Value> NotHandledIndexedPropertySetter(
uint32_t index,
Local<Value> value,
const AccessorInfo& info) {
ApiTestFuzzer::Fuzz();
return v8::Handle<Value>();
}
THREADED_TEST(PixelArrayWithInterceptor) {
v8::HandleScope scope;
LocalContext context;
const int kElementCount = 260;
uint8_t* pixel_data = reinterpret_cast<uint8_t*>(malloc(kElementCount));
i::Handle<i::ExternalPixelArray> pixels =
i::Handle<i::ExternalPixelArray>::cast(
FACTORY->NewExternalArray(kElementCount,
v8::kExternalPixelArray,
pixel_data));
for (int i = 0; i < kElementCount; i++) {
pixels->set(i, i % 256);
}
v8::Handle<v8::ObjectTemplate> templ = v8::ObjectTemplate::New();
templ->SetIndexedPropertyHandler(NotHandledIndexedPropertyGetter,
NotHandledIndexedPropertySetter);
v8::Handle<v8::Object> obj = templ->NewInstance();
obj->SetIndexedPropertiesToPixelData(pixel_data, kElementCount);
context->Global()->Set(v8_str("pixels"), obj);
v8::Handle<v8::Value> result = CompileRun("pixels[1]");
CHECK_EQ(1, result->Int32Value());
result = CompileRun("var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += pixels[i] = pixels[i] = -i;"
"}"
"sum;");
CHECK_EQ(-28, result->Int32Value());
result = CompileRun("pixels.hasOwnProperty('1')");
CHECK(result->BooleanValue());
free(pixel_data);
}
static int ExternalArrayElementSize(v8::ExternalArrayType array_type) {
switch (array_type) {
case v8::kExternalByteArray:
case v8::kExternalUnsignedByteArray:
case v8::kExternalPixelArray:
return 1;
break;
case v8::kExternalShortArray:
case v8::kExternalUnsignedShortArray:
return 2;
break;
case v8::kExternalIntArray:
case v8::kExternalUnsignedIntArray:
case v8::kExternalFloatArray:
return 4;
break;
case v8::kExternalDoubleArray:
return 8;
break;
default:
UNREACHABLE();
return -1;
}
UNREACHABLE();
return -1;
}
template <class ExternalArrayClass, class ElementType>
static void ExternalArrayTestHelper(v8::ExternalArrayType array_type,
int64_t low,
int64_t high) {
v8::HandleScope scope;
LocalContext context;
const int kElementCount = 40;
int element_size = ExternalArrayElementSize(array_type);
ElementType* array_data =
static_cast<ElementType*>(malloc(kElementCount * element_size));
i::Handle<ExternalArrayClass> array =
i::Handle<ExternalArrayClass>::cast(
FACTORY->NewExternalArray(kElementCount, array_type, array_data));
// Force GC to trigger verification.
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
for (int i = 0; i < kElementCount; i++) {
array->set(i, static_cast<ElementType>(i));
}
// Force GC to trigger verification.
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
for (int i = 0; i < kElementCount; i++) {
CHECK_EQ(static_cast<int64_t>(i),
static_cast<int64_t>(array->get_scalar(i)));
CHECK_EQ(static_cast<int64_t>(i), static_cast<int64_t>(array_data[i]));
}
v8::Handle<v8::Object> obj = v8::Object::New();
i::Handle<i::JSObject> jsobj = v8::Utils::OpenHandle(*obj);
// Set the elements to be the external array.
obj->SetIndexedPropertiesToExternalArrayData(array_data,
array_type,
kElementCount);
CHECK_EQ(
1, static_cast<int>(jsobj->GetElement(1)->ToObjectChecked()->Number()));
obj->Set(v8_str("field"), v8::Int32::New(1503));
context->Global()->Set(v8_str("ext_array"), obj);
v8::Handle<v8::Value> result = CompileRun("ext_array.field");
CHECK_EQ(1503, result->Int32Value());
result = CompileRun("ext_array[1]");
CHECK_EQ(1, result->Int32Value());
// Check pass through of assigned smis
result = CompileRun("var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += ext_array[i] = ext_array[i] = -i;"
"}"
"sum;");
CHECK_EQ(-28, result->Int32Value());
// Check assigned smis
result = CompileRun("for (var i = 0; i < 8; i++) {"
" ext_array[i] = i;"
"}"
"var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += ext_array[i];"
"}"
"sum;");
CHECK_EQ(28, result->Int32Value());
// Check assigned smis in reverse order
result = CompileRun("for (var i = 8; --i >= 0; ) {"
" ext_array[i] = i;"
"}"
"var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" sum += ext_array[i];"
"}"
"sum;");
CHECK_EQ(28, result->Int32Value());
// Check pass through of assigned HeapNumbers
result = CompileRun("var sum = 0;"
"for (var i = 0; i < 16; i+=2) {"
" sum += ext_array[i] = ext_array[i] = (-i * 0.5);"
"}"
"sum;");
CHECK_EQ(-28, result->Int32Value());
// Check assigned HeapNumbers
result = CompileRun("for (var i = 0; i < 16; i+=2) {"
" ext_array[i] = (i * 0.5);"
"}"
"var sum = 0;"
"for (var i = 0; i < 16; i+=2) {"
" sum += ext_array[i];"
"}"
"sum;");
CHECK_EQ(28, result->Int32Value());
// Check assigned HeapNumbers in reverse order
result = CompileRun("for (var i = 14; i >= 0; i-=2) {"
" ext_array[i] = (i * 0.5);"
"}"
"var sum = 0;"
"for (var i = 0; i < 16; i+=2) {"
" sum += ext_array[i];"
"}"
"sum;");
CHECK_EQ(28, result->Int32Value());
i::ScopedVector<char> test_buf(1024);
// Check legal boundary conditions.
// The repeated loads and stores ensure the ICs are exercised.
const char* boundary_program =
"var res = 0;"
"for (var i = 0; i < 16; i++) {"
" ext_array[i] = %lld;"
" if (i > 8) {"
" res = ext_array[i];"
" }"
"}"
"res;";
i::OS::SNPrintF(test_buf,
boundary_program,
low);
result = CompileRun(test_buf.start());
CHECK_EQ(low, result->IntegerValue());
i::OS::SNPrintF(test_buf,
boundary_program,
high);
result = CompileRun(test_buf.start());
CHECK_EQ(high, result->IntegerValue());
// Check misprediction of type in IC.
result = CompileRun("var tmp_array = ext_array;"
"var sum = 0;"
"for (var i = 0; i < 8; i++) {"
" tmp_array[i] = i;"
" sum += tmp_array[i];"
" if (i == 4) {"
" tmp_array = {};"
" }"
"}"
"sum;");
// Force GC to trigger verification.
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK_EQ(28, result->Int32Value());
// Make sure out-of-range loads do not throw.
i::OS::SNPrintF(test_buf,
"var caught_exception = false;"
"try {"
" ext_array[%d];"
"} catch (e) {"
" caught_exception = true;"
"}"
"caught_exception;",
kElementCount);
result = CompileRun(test_buf.start());
CHECK_EQ(false, result->BooleanValue());
// Make sure out-of-range stores do not throw.
i::OS::SNPrintF(test_buf,
"var caught_exception = false;"
"try {"
" ext_array[%d] = 1;"
"} catch (e) {"
" caught_exception = true;"
"}"
"caught_exception;",
kElementCount);
result = CompileRun(test_buf.start());
CHECK_EQ(false, result->BooleanValue());
// Check other boundary conditions, values and operations.
result = CompileRun("for (var i = 0; i < 8; i++) {"
" ext_array[7] = undefined;"
"}"
"ext_array[7];");
CHECK_EQ(0, result->Int32Value());
if (array_type == v8::kExternalDoubleArray ||
array_type == v8::kExternalFloatArray) {
CHECK_EQ(
static_cast<int>(i::OS::nan_value()),
static_cast<int>(jsobj->GetElement(7)->ToObjectChecked()->Number()));
} else {
CHECK_EQ(0, static_cast<int>(
jsobj->GetElement(7)->ToObjectChecked()->Number()));
}
result = CompileRun("for (var i = 0; i < 8; i++) {"
" ext_array[6] = '2.3';"
"}"
"ext_array[6];");
CHECK_EQ(2, result->Int32Value());
CHECK_EQ(
2, static_cast<int>(jsobj->GetElement(6)->ToObjectChecked()->Number()));
if (array_type != v8::kExternalFloatArray &&
array_type != v8::kExternalDoubleArray) {
// Though the specification doesn't state it, be explicit about
// converting NaNs and +/-Infinity to zero.
result = CompileRun("for (var i = 0; i < 8; i++) {"
" ext_array[i] = 5;"
"}"
"for (var i = 0; i < 8; i++) {"
" ext_array[i] = NaN;"
"}"
"ext_array[5];");
CHECK_EQ(0, result->Int32Value());
CHECK_EQ(0,
i::Smi::cast(jsobj->GetElement(5)->ToObjectChecked())->value());
result = CompileRun("for (var i = 0; i < 8; i++) {"
" ext_array[i] = 5;"
"}"
"for (var i = 0; i < 8; i++) {"
" ext_array[i] = Infinity;"
"}"
"ext_array[5];");
int expected_value =
(array_type == v8::kExternalPixelArray) ? 255 : 0;
CHECK_EQ(expected_value, result->Int32Value());
CHECK_EQ(expected_value,
i::Smi::cast(jsobj->GetElement(5)->ToObjectChecked())->value());
result = CompileRun("for (var i = 0; i < 8; i++) {"
" ext_array[i] = 5;"
"}"
"for (var i = 0; i < 8; i++) {"
" ext_array[i] = -Infinity;"
"}"
"ext_array[5];");
CHECK_EQ(0, result->Int32Value());
CHECK_EQ(0,
i::Smi::cast(jsobj->GetElement(5)->ToObjectChecked())->value());
// Check truncation behavior of integral arrays.
const char* unsigned_data =
"var source_data = [0.6, 10.6];"
"var expected_results = [0, 10];";
const char* signed_data =
"var source_data = [0.6, 10.6, -0.6, -10.6];"
"var expected_results = [0, 10, 0, -10];";
const char* pixel_data =
"var source_data = [0.6, 10.6];"
"var expected_results = [1, 11];";
bool is_unsigned =
(array_type == v8::kExternalUnsignedByteArray ||
array_type == v8::kExternalUnsignedShortArray ||
array_type == v8::kExternalUnsignedIntArray);
bool is_pixel_data = array_type == v8::kExternalPixelArray;
i::OS::SNPrintF(test_buf,
"%s"
"var all_passed = true;"
"for (var i = 0; i < source_data.length; i++) {"
" for (var j = 0; j < 8; j++) {"
" ext_array[j] = source_data[i];"
" }"
" all_passed = all_passed &&"
" (ext_array[5] == expected_results[i]);"
"}"
"all_passed;",
(is_unsigned ?
unsigned_data :
(is_pixel_data ? pixel_data : signed_data)));
result = CompileRun(test_buf.start());
CHECK_EQ(true, result->BooleanValue());
}
for (int i = 0; i < kElementCount; i++) {
array->set(i, static_cast<ElementType>(i));
}
// Test complex assignments
result = CompileRun("function ee_op_test_complex_func(sum) {"
" for (var i = 0; i < 40; ++i) {"
" sum += (ext_array[i] += 1);"
" sum += (ext_array[i] -= 1);"
" } "
" return sum;"
"}"
"sum=0;"
"for (var i=0;i<10000;++i) {"
" sum=ee_op_test_complex_func(sum);"
"}"
"sum;");
CHECK_EQ(16000000, result->Int32Value());
// Test count operations
result = CompileRun("function ee_op_test_count_func(sum) {"
" for (var i = 0; i < 40; ++i) {"
" sum += (++ext_array[i]);"
" sum += (--ext_array[i]);"
" } "
" return sum;"
"}"
"sum=0;"
"for (var i=0;i<10000;++i) {"
" sum=ee_op_test_count_func(sum);"
"}"
"sum;");
CHECK_EQ(16000000, result->Int32Value());
result = CompileRun("ext_array[3] = 33;"
"delete ext_array[3];"
"ext_array[3];");
CHECK_EQ(33, result->Int32Value());
result = CompileRun("ext_array[0] = 10; ext_array[1] = 11;"
"ext_array[2] = 12; ext_array[3] = 13;"
"ext_array.__defineGetter__('2',"
"function() { return 120; });"
"ext_array[2];");
CHECK_EQ(12, result->Int32Value());
result = CompileRun("var js_array = new Array(40);"
"js_array[0] = 77;"
"js_array;");
CHECK_EQ(77, v8::Object::Cast(*result)->Get(v8_str("0"))->Int32Value());
result = CompileRun("ext_array[1] = 23;"
"ext_array.__proto__ = [];"
"js_array.__proto__ = ext_array;"
"js_array.concat(ext_array);");
CHECK_EQ(77, v8::Object::Cast(*result)->Get(v8_str("0"))->Int32Value());
CHECK_EQ(23, v8::Object::Cast(*result)->Get(v8_str("1"))->Int32Value());
result = CompileRun("ext_array[1] = 23;");
CHECK_EQ(23, result->Int32Value());
// Test more complex manipulations which cause eax to contain values
// that won't be completely overwritten by loads from the arrays.
// This catches bugs in the instructions used for the KeyedLoadIC
// for byte and word types.
{
const int kXSize = 300;
const int kYSize = 300;
const int kLargeElementCount = kXSize * kYSize * 4;
ElementType* large_array_data =
static_cast<ElementType*>(malloc(kLargeElementCount * element_size));
v8::Handle<v8::Object> large_obj = v8::Object::New();
// Set the elements to be the external array.
large_obj->SetIndexedPropertiesToExternalArrayData(large_array_data,
array_type,
kLargeElementCount);
context->Global()->Set(v8_str("large_array"), large_obj);
// Initialize contents of a few rows.
for (int x = 0; x < 300; x++) {
int row = 0;
int offset = row * 300 * 4;
large_array_data[offset + 4 * x + 0] = (ElementType) 127;
large_array_data[offset + 4 * x + 1] = (ElementType) 0;
large_array_data[offset + 4 * x + 2] = (ElementType) 0;
large_array_data[offset + 4 * x + 3] = (ElementType) 127;
row = 150;
offset = row * 300 * 4;
large_array_data[offset + 4 * x + 0] = (ElementType) 127;
large_array_data[offset + 4 * x + 1] = (ElementType) 0;
large_array_data[offset + 4 * x + 2] = (ElementType) 0;
large_array_data[offset + 4 * x + 3] = (ElementType) 127;
row = 298;
offset = row * 300 * 4;
large_array_data[offset + 4 * x + 0] = (ElementType) 127;
large_array_data[offset + 4 * x + 1] = (ElementType) 0;
large_array_data[offset + 4 * x + 2] = (ElementType) 0;
large_array_data[offset + 4 * x + 3] = (ElementType) 127;
}
// The goal of the code below is to make "offset" large enough
// that the computation of the index (which goes into eax) has
// high bits set which will not be overwritten by a byte or short
// load.
result = CompileRun("var failed = false;"
"var offset = 0;"
"for (var i = 0; i < 300; i++) {"
" if (large_array[4 * i] != 127 ||"
" large_array[4 * i + 1] != 0 ||"
" large_array[4 * i + 2] != 0 ||"
" large_array[4 * i + 3] != 127) {"
" failed = true;"
" }"
"}"
"offset = 150 * 300 * 4;"
"for (var i = 0; i < 300; i++) {"
" if (large_array[offset + 4 * i] != 127 ||"
" large_array[offset + 4 * i + 1] != 0 ||"
" large_array[offset + 4 * i + 2] != 0 ||"
" large_array[offset + 4 * i + 3] != 127) {"
" failed = true;"
" }"
"}"
"offset = 298 * 300 * 4;"
"for (var i = 0; i < 300; i++) {"
" if (large_array[offset + 4 * i] != 127 ||"
" large_array[offset + 4 * i + 1] != 0 ||"
" large_array[offset + 4 * i + 2] != 0 ||"
" large_array[offset + 4 * i + 3] != 127) {"
" failed = true;"
" }"
"}"
"!failed;");
CHECK_EQ(true, result->BooleanValue());
free(large_array_data);
}
// The "" property descriptor is overloaded to store information about
// the external array. Ensure that setting and accessing the "" property
// works (it should overwrite the information cached about the external
// array in the DescriptorArray) in various situations.
result = CompileRun("ext_array[''] = 23; ext_array['']");
CHECK_EQ(23, result->Int32Value());
// Property "" set after the external array is associated with the object.
{
v8::Handle<v8::Object> obj2 = v8::Object::New();
obj2->Set(v8_str("ee_test_field"), v8::Int32::New(256));
obj2->Set(v8_str(""), v8::Int32::New(1503));
// Set the elements to be the external array.
obj2->SetIndexedPropertiesToExternalArrayData(array_data,
array_type,
kElementCount);
context->Global()->Set(v8_str("ext_array"), obj2);
result = CompileRun("ext_array['']");
CHECK_EQ(1503, result->Int32Value());
}
// Property "" set after the external array is associated with the object.
{
v8::Handle<v8::Object> obj2 = v8::Object::New();
obj2->Set(v8_str("ee_test_field_2"), v8::Int32::New(256));
// Set the elements to be the external array.
obj2->SetIndexedPropertiesToExternalArrayData(array_data,
array_type,
kElementCount);
obj2->Set(v8_str(""), v8::Int32::New(1503));
context->Global()->Set(v8_str("ext_array"), obj2);
result = CompileRun("ext_array['']");
CHECK_EQ(1503, result->Int32Value());
}
// Should reuse the map from previous test.
{
v8::Handle<v8::Object> obj2 = v8::Object::New();
obj2->Set(v8_str("ee_test_field_2"), v8::Int32::New(256));
// Set the elements to be the external array. Should re-use the map
// from previous test.
obj2->SetIndexedPropertiesToExternalArrayData(array_data,
array_type,
kElementCount);
context->Global()->Set(v8_str("ext_array"), obj2);
result = CompileRun("ext_array['']");
}
// Property "" is a constant function that shouldn't not be interfered with
// when an external array is set.
{
v8::Handle<v8::Object> obj2 = v8::Object::New();
// Start
obj2->Set(v8_str("ee_test_field3"), v8::Int32::New(256));
// Add a constant function to an object.
context->Global()->Set(v8_str("ext_array"), obj2);
result = CompileRun("ext_array[''] = function() {return 1503;};"
"ext_array['']();");
// Add an external array transition to the same map that
// has the constant transition.
v8::Handle<v8::Object> obj3 = v8::Object::New();
obj3->Set(v8_str("ee_test_field3"), v8::Int32::New(256));
obj3->SetIndexedPropertiesToExternalArrayData(array_data,
array_type,
kElementCount);
context->Global()->Set(v8_str("ext_array"), obj3);
}
// If a external array transition is in the map, it should get clobbered
// by a constant function.
{
// Add an external array transition.
v8::Handle<v8::Object> obj3 = v8::Object::New();
obj3->Set(v8_str("ee_test_field4"), v8::Int32::New(256));
obj3->SetIndexedPropertiesToExternalArrayData(array_data,
array_type,
kElementCount);
// Add a constant function to the same map that just got an external array
// transition.
v8::Handle<v8::Object> obj2 = v8::Object::New();
obj2->Set(v8_str("ee_test_field4"), v8::Int32::New(256));
context->Global()->Set(v8_str("ext_array"), obj2);
result = CompileRun("ext_array[''] = function() {return 1503;};"
"ext_array['']();");
}
free(array_data);
}
THREADED_TEST(ExternalByteArray) {
ExternalArrayTestHelper<i::ExternalByteArray, int8_t>(
v8::kExternalByteArray,
-128,
127);
}
THREADED_TEST(ExternalUnsignedByteArray) {
ExternalArrayTestHelper<i::ExternalUnsignedByteArray, uint8_t>(
v8::kExternalUnsignedByteArray,
0,
255);
}
THREADED_TEST(ExternalPixelArray) {
ExternalArrayTestHelper<i::ExternalPixelArray, uint8_t>(
v8::kExternalPixelArray,
0,
255);
}
THREADED_TEST(ExternalShortArray) {
ExternalArrayTestHelper<i::ExternalShortArray, int16_t>(
v8::kExternalShortArray,
-32768,
32767);
}
THREADED_TEST(ExternalUnsignedShortArray) {
ExternalArrayTestHelper<i::ExternalUnsignedShortArray, uint16_t>(
v8::kExternalUnsignedShortArray,
0,
65535);
}
THREADED_TEST(ExternalIntArray) {
ExternalArrayTestHelper<i::ExternalIntArray, int32_t>(
v8::kExternalIntArray,
INT_MIN, // -2147483648
INT_MAX); // 2147483647
}
THREADED_TEST(ExternalUnsignedIntArray) {
ExternalArrayTestHelper<i::ExternalUnsignedIntArray, uint32_t>(
v8::kExternalUnsignedIntArray,
0,
UINT_MAX); // 4294967295
}
THREADED_TEST(ExternalFloatArray) {
ExternalArrayTestHelper<i::ExternalFloatArray, float>(
v8::kExternalFloatArray,
-500,
500);
}
THREADED_TEST(ExternalDoubleArray) {
ExternalArrayTestHelper<i::ExternalDoubleArray, double>(
v8::kExternalDoubleArray,
-500,
500);
}
THREADED_TEST(ExternalArrays) {
TestExternalByteArray();
TestExternalUnsignedByteArray();
TestExternalShortArray();
TestExternalUnsignedShortArray();
TestExternalIntArray();
TestExternalUnsignedIntArray();
TestExternalFloatArray();
}
void ExternalArrayInfoTestHelper(v8::ExternalArrayType array_type) {
v8::HandleScope scope;
LocalContext context;
for (int size = 0; size < 100; size += 10) {
int element_size = ExternalArrayElementSize(array_type);
void* external_data = malloc(size * element_size);
v8::Handle<v8::Object> obj = v8::Object::New();
obj->SetIndexedPropertiesToExternalArrayData(
external_data, array_type, size);
CHECK(obj->HasIndexedPropertiesInExternalArrayData());
CHECK_EQ(external_data, obj->GetIndexedPropertiesExternalArrayData());
CHECK_EQ(array_type, obj->GetIndexedPropertiesExternalArrayDataType());
CHECK_EQ(size, obj->GetIndexedPropertiesExternalArrayDataLength());
free(external_data);
}
}
THREADED_TEST(ExternalArrayInfo) {
ExternalArrayInfoTestHelper(v8::kExternalByteArray);
ExternalArrayInfoTestHelper(v8::kExternalUnsignedByteArray);
ExternalArrayInfoTestHelper(v8::kExternalShortArray);
ExternalArrayInfoTestHelper(v8::kExternalUnsignedShortArray);
ExternalArrayInfoTestHelper(v8::kExternalIntArray);
ExternalArrayInfoTestHelper(v8::kExternalUnsignedIntArray);
ExternalArrayInfoTestHelper(v8::kExternalFloatArray);
ExternalArrayInfoTestHelper(v8::kExternalDoubleArray);
ExternalArrayInfoTestHelper(v8::kExternalPixelArray);
}
void ExternalArrayLimitTestHelper(v8::ExternalArrayType array_type, int size) {
v8::Handle<v8::Object> obj = v8::Object::New();
v8::V8::SetFatalErrorHandler(StoringErrorCallback);
last_location = last_message = NULL;
obj->SetIndexedPropertiesToExternalArrayData(NULL, array_type, size);
CHECK(!obj->HasIndexedPropertiesInExternalArrayData());
CHECK_NE(NULL, last_location);
CHECK_NE(NULL, last_message);
}
TEST(ExternalArrayLimits) {
v8::HandleScope scope;
LocalContext context;
ExternalArrayLimitTestHelper(v8::kExternalByteArray, 0x40000000);
ExternalArrayLimitTestHelper(v8::kExternalByteArray, 0xffffffff);
ExternalArrayLimitTestHelper(v8::kExternalUnsignedByteArray, 0x40000000);
ExternalArrayLimitTestHelper(v8::kExternalUnsignedByteArray, 0xffffffff);
ExternalArrayLimitTestHelper(v8::kExternalShortArray, 0x40000000);
ExternalArrayLimitTestHelper(v8::kExternalShortArray, 0xffffffff);
ExternalArrayLimitTestHelper(v8::kExternalUnsignedShortArray, 0x40000000);
ExternalArrayLimitTestHelper(v8::kExternalUnsignedShortArray, 0xffffffff);
ExternalArrayLimitTestHelper(v8::kExternalIntArray, 0x40000000);
ExternalArrayLimitTestHelper(v8::kExternalIntArray, 0xffffffff);
ExternalArrayLimitTestHelper(v8::kExternalUnsignedIntArray, 0x40000000);
ExternalArrayLimitTestHelper(v8::kExternalUnsignedIntArray, 0xffffffff);
ExternalArrayLimitTestHelper(v8::kExternalFloatArray, 0x40000000);
ExternalArrayLimitTestHelper(v8::kExternalFloatArray, 0xffffffff);
ExternalArrayLimitTestHelper(v8::kExternalDoubleArray, 0x40000000);
ExternalArrayLimitTestHelper(v8::kExternalDoubleArray, 0xffffffff);
ExternalArrayLimitTestHelper(v8::kExternalPixelArray, 0x40000000);
ExternalArrayLimitTestHelper(v8::kExternalPixelArray, 0xffffffff);
}
THREADED_TEST(ScriptContextDependence) {
v8::HandleScope scope;
LocalContext c1;
const char *source = "foo";
v8::Handle<v8::Script> dep = v8::Script::Compile(v8::String::New(source));
v8::Handle<v8::Script> indep = v8::Script::New(v8::String::New(source));
c1->Global()->Set(v8::String::New("foo"), v8::Integer::New(100));
CHECK_EQ(dep->Run()->Int32Value(), 100);
CHECK_EQ(indep->Run()->Int32Value(), 100);
LocalContext c2;
c2->Global()->Set(v8::String::New("foo"), v8::Integer::New(101));
CHECK_EQ(dep->Run()->Int32Value(), 100);
CHECK_EQ(indep->Run()->Int32Value(), 101);
}
THREADED_TEST(StackTrace) {
v8::HandleScope scope;
LocalContext context;
v8::TryCatch try_catch;
const char *source = "function foo() { FAIL.FAIL; }; foo();";
v8::Handle<v8::String> src = v8::String::New(source);
v8::Handle<v8::String> origin = v8::String::New("stack-trace-test");
v8::Script::New(src, origin)->Run();
CHECK(try_catch.HasCaught());
v8::String::Utf8Value stack(try_catch.StackTrace());
CHECK(strstr(*stack, "at foo (stack-trace-test") != NULL);
}
// Checks that a StackFrame has certain expected values.
void checkStackFrame(const char* expected_script_name,
const char* expected_func_name, int expected_line_number,
int expected_column, bool is_eval, bool is_constructor,
v8::Handle<v8::StackFrame> frame) {
v8::HandleScope scope;
v8::String::Utf8Value func_name(frame->GetFunctionName());
v8::String::Utf8Value script_name(frame->GetScriptName());
if (*script_name == NULL) {
// The situation where there is no associated script, like for evals.
CHECK(expected_script_name == NULL);
} else {
CHECK(strstr(*script_name, expected_script_name) != NULL);
}
CHECK(strstr(*func_name, expected_func_name) != NULL);
CHECK_EQ(expected_line_number, frame->GetLineNumber());
CHECK_EQ(expected_column, frame->GetColumn());
CHECK_EQ(is_eval, frame->IsEval());
CHECK_EQ(is_constructor, frame->IsConstructor());
}
v8::Handle<Value> AnalyzeStackInNativeCode(const v8::Arguments& args) {
v8::HandleScope scope;
const char* origin = "capture-stack-trace-test";
const int kOverviewTest = 1;
const int kDetailedTest = 2;
ASSERT(args.Length() == 1);
int testGroup = args[0]->Int32Value();
if (testGroup == kOverviewTest) {
v8::Handle<v8::StackTrace> stackTrace =
v8::StackTrace::CurrentStackTrace(10, v8::StackTrace::kOverview);
CHECK_EQ(4, stackTrace->GetFrameCount());
checkStackFrame(origin, "bar", 2, 10, false, false,
stackTrace->GetFrame(0));
checkStackFrame(origin, "foo", 6, 3, false, false,
stackTrace->GetFrame(1));
// This is the source string inside the eval which has the call to foo.
checkStackFrame(NULL, "", 1, 5, false, false,
stackTrace->GetFrame(2));
// The last frame is an anonymous function which has the initial eval call.
checkStackFrame(origin, "", 8, 7, false, false,
stackTrace->GetFrame(3));
CHECK(stackTrace->AsArray()->IsArray());
} else if (testGroup == kDetailedTest) {
v8::Handle<v8::StackTrace> stackTrace =
v8::StackTrace::CurrentStackTrace(10, v8::StackTrace::kDetailed);
CHECK_EQ(4, stackTrace->GetFrameCount());
checkStackFrame(origin, "bat", 4, 22, false, false,
stackTrace->GetFrame(0));
checkStackFrame(origin, "baz", 8, 3, false, true,
stackTrace->GetFrame(1));
#ifdef ENABLE_DEBUGGER_SUPPORT
bool is_eval = true;
#else // ENABLE_DEBUGGER_SUPPORT
bool is_eval = false;
#endif // ENABLE_DEBUGGER_SUPPORT
// This is the source string inside the eval which has the call to baz.
checkStackFrame(NULL, "", 1, 5, is_eval, false,
stackTrace->GetFrame(2));
// The last frame is an anonymous function which has the initial eval call.
checkStackFrame(origin, "", 10, 1, false, false,
stackTrace->GetFrame(3));
CHECK(stackTrace->AsArray()->IsArray());
}
return v8::Undefined();
}
// Tests the C++ StackTrace API.
// TODO(3074796): Reenable this as a THREADED_TEST once it passes.
// THREADED_TEST(CaptureStackTrace) {
TEST(CaptureStackTrace) {
v8::HandleScope scope;
v8::Handle<v8::String> origin = v8::String::New("capture-stack-trace-test");
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("AnalyzeStackInNativeCode"),
v8::FunctionTemplate::New(AnalyzeStackInNativeCode));
LocalContext context(0, templ);
// Test getting OVERVIEW information. Should ignore information that is not
// script name, function name, line number, and column offset.
const char *overview_source =
"function bar() {\n"
" var y; AnalyzeStackInNativeCode(1);\n"
"}\n"
"function foo() {\n"
"\n"
" bar();\n"
"}\n"
"var x;eval('new foo();');";
v8::Handle<v8::String> overview_src = v8::String::New(overview_source);
v8::Handle<Value> overview_result(
v8::Script::New(overview_src, origin)->Run());
CHECK(!overview_result.IsEmpty());
CHECK(overview_result->IsObject());
// Test getting DETAILED information.
const char *detailed_source =
"function bat() {AnalyzeStackInNativeCode(2);\n"
"}\n"
"\n"
"function baz() {\n"
" bat();\n"
"}\n"
"eval('new baz();');";
v8::Handle<v8::String> detailed_src = v8::String::New(detailed_source);
// Make the script using a non-zero line and column offset.
v8::Handle<v8::Integer> line_offset = v8::Integer::New(3);
v8::Handle<v8::Integer> column_offset = v8::Integer::New(5);
v8::ScriptOrigin detailed_origin(origin, line_offset, column_offset);
v8::Handle<v8::Script> detailed_script(
v8::Script::New(detailed_src, &detailed_origin));
v8::Handle<Value> detailed_result(detailed_script->Run());
CHECK(!detailed_result.IsEmpty());
CHECK(detailed_result->IsObject());
}
static void StackTraceForUncaughtExceptionListener(
v8::Handle<v8::Message> message,
v8::Handle<Value>) {
v8::Handle<v8::StackTrace> stack_trace = message->GetStackTrace();
CHECK_EQ(2, stack_trace->GetFrameCount());
checkStackFrame("origin", "foo", 2, 3, false, false,
stack_trace->GetFrame(0));
checkStackFrame("origin", "bar", 5, 3, false, false,
stack_trace->GetFrame(1));
}
TEST(CaptureStackTraceForUncaughtException) {
report_count = 0;
v8::HandleScope scope;
LocalContext env;
v8::V8::AddMessageListener(StackTraceForUncaughtExceptionListener);
v8::V8::SetCaptureStackTraceForUncaughtExceptions(true);
Script::Compile(v8_str("function foo() {\n"
" throw 1;\n"
"};\n"
"function bar() {\n"
" foo();\n"
"};"),
v8_str("origin"))->Run();
v8::Local<v8::Object> global = env->Global();
Local<Value> trouble = global->Get(v8_str("bar"));
CHECK(trouble->IsFunction());
Function::Cast(*trouble)->Call(global, 0, NULL);
v8::V8::SetCaptureStackTraceForUncaughtExceptions(false);
v8::V8::RemoveMessageListeners(StackTraceForUncaughtExceptionListener);
}
TEST(CaptureStackTraceForUncaughtExceptionAndSetters) {
v8::HandleScope scope;
LocalContext env;
v8::V8::SetCaptureStackTraceForUncaughtExceptions(true,
1024,
v8::StackTrace::kDetailed);
CompileRun(
"var setters = ['column', 'lineNumber', 'scriptName',\n"
" 'scriptNameOrSourceURL', 'functionName', 'isEval',\n"
" 'isConstructor'];\n"
"for (var i = 0; i < setters.length; i++) {\n"
" var prop = setters[i];\n"
" Object.prototype.__defineSetter__(prop, function() { throw prop; });\n"
"}\n");
CompileRun("throw 'exception';");
v8::V8::SetCaptureStackTraceForUncaughtExceptions(false);
}
static void RethrowStackTraceHandler(v8::Handle<v8::Message> message,
v8::Handle<v8::Value> data) {
// Use the frame where JavaScript is called from.
v8::Handle<v8::StackTrace> stack_trace = message->GetStackTrace();
CHECK(!stack_trace.IsEmpty());
int frame_count = stack_trace->GetFrameCount();
CHECK_EQ(3, frame_count);
int line_number[] = {1, 2, 5};
for (int i = 0; i < frame_count; i++) {
CHECK_EQ(line_number[i], stack_trace->GetFrame(i)->GetLineNumber());
}
}
// Test that we only return the stack trace at the site where the exception
// is first thrown (not where it is rethrown).
TEST(RethrowStackTrace) {
v8::HandleScope scope;
LocalContext env;
// We make sure that
// - the stack trace of the ReferenceError in g() is reported.
// - the stack trace is not overwritten when e1 is rethrown by t().
// - the stack trace of e2 does not overwrite that of e1.
const char* source =
"function g() { error; } \n"
"function f() { g(); } \n"
"function t(e) { throw e; } \n"
"try { \n"
" f(); \n"
"} catch (e1) { \n"
" try { \n"
" error; \n"
" } catch (e2) { \n"
" t(e1); \n"
" } \n"
"} \n";
v8::V8::AddMessageListener(RethrowStackTraceHandler);
v8::V8::SetCaptureStackTraceForUncaughtExceptions(true);
CompileRun(source);
v8::V8::SetCaptureStackTraceForUncaughtExceptions(false);
v8::V8::RemoveMessageListeners(RethrowStackTraceHandler);
}
static void RethrowPrimitiveStackTraceHandler(v8::Handle<v8::Message> message,
v8::Handle<v8::Value> data) {
v8::Handle<v8::StackTrace> stack_trace = message->GetStackTrace();
CHECK(!stack_trace.IsEmpty());
int frame_count = stack_trace->GetFrameCount();
CHECK_EQ(2, frame_count);
int line_number[] = {3, 7};
for (int i = 0; i < frame_count; i++) {
CHECK_EQ(line_number[i], stack_trace->GetFrame(i)->GetLineNumber());
}
}
// Test that we do not recognize identity for primitive exceptions.
TEST(RethrowPrimitiveStackTrace) {
v8::HandleScope scope;
LocalContext env;
// We do not capture stack trace for non Error objects on creation time.
// Instead, we capture the stack trace on last throw.
const char* source =
"function g() { throw 404; } \n"
"function f() { g(); } \n"
"function t(e) { throw e; } \n"
"try { \n"
" f(); \n"
"} catch (e1) { \n"
" t(e1) \n"
"} \n";
v8::V8::AddMessageListener(RethrowPrimitiveStackTraceHandler);
v8::V8::SetCaptureStackTraceForUncaughtExceptions(true);
CompileRun(source);
v8::V8::SetCaptureStackTraceForUncaughtExceptions(false);
v8::V8::RemoveMessageListeners(RethrowPrimitiveStackTraceHandler);
}
static void RethrowExistingStackTraceHandler(v8::Handle<v8::Message> message,
v8::Handle<v8::Value> data) {
// Use the frame where JavaScript is called from.
v8::Handle<v8::StackTrace> stack_trace = message->GetStackTrace();
CHECK(!stack_trace.IsEmpty());
CHECK_EQ(1, stack_trace->GetFrameCount());
CHECK_EQ(1, stack_trace->GetFrame(0)->GetLineNumber());
}
// Test that the stack trace is captured when the error object is created and
// not where it is thrown.
TEST(RethrowExistingStackTrace) {
v8::HandleScope scope;
LocalContext env;
const char* source =
"var e = new Error(); \n"
"throw e; \n";
v8::V8::AddMessageListener(RethrowExistingStackTraceHandler);
v8::V8::SetCaptureStackTraceForUncaughtExceptions(true);
CompileRun(source);
v8::V8::SetCaptureStackTraceForUncaughtExceptions(false);
v8::V8::RemoveMessageListeners(RethrowExistingStackTraceHandler);
}
static void RethrowBogusErrorStackTraceHandler(v8::Handle<v8::Message> message,
v8::Handle<v8::Value> data) {
// Use the frame where JavaScript is called from.
v8::Handle<v8::StackTrace> stack_trace = message->GetStackTrace();
CHECK(!stack_trace.IsEmpty());
CHECK_EQ(1, stack_trace->GetFrameCount());
CHECK_EQ(2, stack_trace->GetFrame(0)->GetLineNumber());
}
// Test that the stack trace is captured where the bogus Error object is thrown.
TEST(RethrowBogusErrorStackTrace) {
v8::HandleScope scope;
LocalContext env;
const char* source =
"var e = {__proto__: new Error()} \n"
"throw e; \n";
v8::V8::AddMessageListener(RethrowBogusErrorStackTraceHandler);
v8::V8::SetCaptureStackTraceForUncaughtExceptions(true);
CompileRun(source);
v8::V8::SetCaptureStackTraceForUncaughtExceptions(false);
v8::V8::RemoveMessageListeners(RethrowBogusErrorStackTraceHandler);
}
v8::Handle<Value> AnalyzeStackOfEvalWithSourceURL(const v8::Arguments& args) {
v8::HandleScope scope;
v8::Handle<v8::StackTrace> stackTrace =
v8::StackTrace::CurrentStackTrace(10, v8::StackTrace::kDetailed);
CHECK_EQ(5, stackTrace->GetFrameCount());
v8::Handle<v8::String> url = v8_str("eval_url");
for (int i = 0; i < 3; i++) {
v8::Handle<v8::String> name =
stackTrace->GetFrame(i)->GetScriptNameOrSourceURL();
CHECK(!name.IsEmpty());
CHECK_EQ(url, name);
}
return v8::Undefined();
}
TEST(SourceURLInStackTrace) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("AnalyzeStackOfEvalWithSourceURL"),
v8::FunctionTemplate::New(AnalyzeStackOfEvalWithSourceURL));
LocalContext context(0, templ);
const char *source =
"function outer() {\n"
"function bar() {\n"
" AnalyzeStackOfEvalWithSourceURL();\n"
"}\n"
"function foo() {\n"
"\n"
" bar();\n"
"}\n"
"foo();\n"
"}\n"
"eval('(' + outer +')()//@ sourceURL=eval_url');";
CHECK(CompileRun(source)->IsUndefined());
}
v8::Handle<Value> AnalyzeStackOfInlineScriptWithSourceURL(
const v8::Arguments& args) {
v8::HandleScope scope;
v8::Handle<v8::StackTrace> stackTrace =
v8::StackTrace::CurrentStackTrace(10, v8::StackTrace::kDetailed);
CHECK_EQ(4, stackTrace->GetFrameCount());
v8::Handle<v8::String> url = v8_str("url");
for (int i = 0; i < 3; i++) {
v8::Handle<v8::String> name =
stackTrace->GetFrame(i)->GetScriptNameOrSourceURL();
CHECK(!name.IsEmpty());
CHECK_EQ(url, name);
}
return v8::Undefined();
}
TEST(InlineScriptWithSourceURLInStackTrace) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("AnalyzeStackOfInlineScriptWithSourceURL"),
v8::FunctionTemplate::New(
AnalyzeStackOfInlineScriptWithSourceURL));
LocalContext context(0, templ);
const char *source =
"function outer() {\n"
"function bar() {\n"
" AnalyzeStackOfInlineScriptWithSourceURL();\n"
"}\n"
"function foo() {\n"
"\n"
" bar();\n"
"}\n"
"foo();\n"
"}\n"
"outer()\n"
"//@ sourceURL=source_url";
CHECK(CompileRunWithOrigin(source, "url", 0, 1)->IsUndefined());
}
v8::Handle<Value> AnalyzeStackOfDynamicScriptWithSourceURL(
const v8::Arguments& args) {
v8::HandleScope scope;
v8::Handle<v8::StackTrace> stackTrace =
v8::StackTrace::CurrentStackTrace(10, v8::StackTrace::kDetailed);
CHECK_EQ(4, stackTrace->GetFrameCount());
v8::Handle<v8::String> url = v8_str("source_url");
for (int i = 0; i < 3; i++) {
v8::Handle<v8::String> name =
stackTrace->GetFrame(i)->GetScriptNameOrSourceURL();
CHECK(!name.IsEmpty());
CHECK_EQ(url, name);
}
return v8::Undefined();
}
TEST(DynamicWithSourceURLInStackTrace) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("AnalyzeStackOfDynamicScriptWithSourceURL"),
v8::FunctionTemplate::New(
AnalyzeStackOfDynamicScriptWithSourceURL));
LocalContext context(0, templ);
const char *source =
"function outer() {\n"
"function bar() {\n"
" AnalyzeStackOfDynamicScriptWithSourceURL();\n"
"}\n"
"function foo() {\n"
"\n"
" bar();\n"
"}\n"
"foo();\n"
"}\n"
"outer()\n"
"//@ sourceURL=source_url";
CHECK(CompileRunWithOrigin(source, "url", 0, 0)->IsUndefined());
}
static void CreateGarbageInOldSpace() {
v8::HandleScope scope;
i::AlwaysAllocateScope always_allocate;
for (int i = 0; i < 1000; i++) {
FACTORY->NewFixedArray(1000, i::TENURED);
}
}
// Test that idle notification can be handled and eventually returns true.
TEST(IdleNotification) {
const intptr_t MB = 1024 * 1024;
v8::HandleScope scope;
LocalContext env;
intptr_t initial_size = HEAP->SizeOfObjects();
CreateGarbageInOldSpace();
intptr_t size_with_garbage = HEAP->SizeOfObjects();
CHECK_GT(size_with_garbage, initial_size + MB);
bool finished = false;
for (int i = 0; i < 200 && !finished; i++) {
finished = v8::V8::IdleNotification();
}
intptr_t final_size = HEAP->SizeOfObjects();
CHECK(finished);
CHECK_LT(final_size, initial_size + 1);
}
// Test that idle notification can be handled and eventually collects garbage.
TEST(IdleNotificationWithSmallHint) {
const intptr_t MB = 1024 * 1024;
const int IdlePauseInMs = 900;
v8::HandleScope scope;
LocalContext env;
intptr_t initial_size = HEAP->SizeOfObjects();
CreateGarbageInOldSpace();
intptr_t size_with_garbage = HEAP->SizeOfObjects();
CHECK_GT(size_with_garbage, initial_size + MB);
bool finished = false;
for (int i = 0; i < 200 && !finished; i++) {
finished = v8::V8::IdleNotification(IdlePauseInMs);
}
intptr_t final_size = HEAP->SizeOfObjects();
CHECK(finished);
CHECK_LT(final_size, initial_size + 1);
}
// Test that idle notification can be handled and eventually collects garbage.
TEST(IdleNotificationWithLargeHint) {
const intptr_t MB = 1024 * 1024;
const int IdlePauseInMs = 900;
v8::HandleScope scope;
LocalContext env;
intptr_t initial_size = HEAP->SizeOfObjects();
CreateGarbageInOldSpace();
intptr_t size_with_garbage = HEAP->SizeOfObjects();
CHECK_GT(size_with_garbage, initial_size + MB);
bool finished = false;
for (int i = 0; i < 200 && !finished; i++) {
finished = v8::V8::IdleNotification(IdlePauseInMs);
}
intptr_t final_size = HEAP->SizeOfObjects();
CHECK(finished);
CHECK_LT(final_size, initial_size + 1);
}
TEST(Regress2107) {
const intptr_t MB = 1024 * 1024;
const int kShortIdlePauseInMs = 100;
const int kLongIdlePauseInMs = 1000;
v8::HandleScope scope;
LocalContext env;
intptr_t initial_size = HEAP->SizeOfObjects();
// Send idle notification to start a round of incremental GCs.
v8::V8::IdleNotification(kShortIdlePauseInMs);
// Emulate 7 page reloads.
for (int i = 0; i < 7; i++) {
v8::Persistent<v8::Context> ctx = v8::Context::New();
ctx->Enter();
CreateGarbageInOldSpace();
ctx->Exit();
ctx.Dispose();
v8::V8::ContextDisposedNotification();
v8::V8::IdleNotification(kLongIdlePauseInMs);
}
// Create garbage and check that idle notification still collects it.
CreateGarbageInOldSpace();
intptr_t size_with_garbage = HEAP->SizeOfObjects();
CHECK_GT(size_with_garbage, initial_size + MB);
bool finished = false;
for (int i = 0; i < 200 && !finished; i++) {
finished = v8::V8::IdleNotification(kShortIdlePauseInMs);
}
intptr_t final_size = HEAP->SizeOfObjects();
CHECK_LT(final_size, initial_size + 1);
}
static uint32_t* stack_limit;
static v8::Handle<Value> GetStackLimitCallback(const v8::Arguments& args) {
stack_limit = reinterpret_cast<uint32_t*>(
i::Isolate::Current()->stack_guard()->real_climit());
return v8::Undefined();
}
// Uses the address of a local variable to determine the stack top now.
// Given a size, returns an address that is that far from the current
// top of stack.
static uint32_t* ComputeStackLimit(uint32_t size) {
uint32_t* answer = &size - (size / sizeof(size));
// If the size is very large and the stack is very near the bottom of
// memory then the calculation above may wrap around and give an address
// that is above the (downwards-growing) stack. In that case we return
// a very low address.
if (answer > &size) return reinterpret_cast<uint32_t*>(sizeof(size));
return answer;
}
TEST(SetResourceConstraints) {
static const int K = 1024;
uint32_t* set_limit = ComputeStackLimit(128 * K);
// Set stack limit.
v8::ResourceConstraints constraints;
constraints.set_stack_limit(set_limit);
CHECK(v8::SetResourceConstraints(&constraints));
// Execute a script.
v8::HandleScope scope;
LocalContext env;
Local<v8::FunctionTemplate> fun_templ =
v8::FunctionTemplate::New(GetStackLimitCallback);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("get_stack_limit"), fun);
CompileRun("get_stack_limit();");
CHECK(stack_limit == set_limit);
}
TEST(SetResourceConstraintsInThread) {
uint32_t* set_limit;
{
v8::Locker locker(CcTest::default_isolate());
static const int K = 1024;
set_limit = ComputeStackLimit(128 * K);
// Set stack limit.
v8::ResourceConstraints constraints;
constraints.set_stack_limit(set_limit);
CHECK(v8::SetResourceConstraints(&constraints));
// Execute a script.
v8::HandleScope scope;
LocalContext env;
Local<v8::FunctionTemplate> fun_templ =
v8::FunctionTemplate::New(GetStackLimitCallback);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("get_stack_limit"), fun);
CompileRun("get_stack_limit();");
CHECK(stack_limit == set_limit);
}
{
v8::Locker locker(CcTest::default_isolate());
CHECK(stack_limit == set_limit);
}
}
THREADED_TEST(GetHeapStatistics) {
v8::HandleScope scope;
LocalContext c1;
v8::HeapStatistics heap_statistics;
CHECK_EQ(static_cast<int>(heap_statistics.total_heap_size()), 0);
CHECK_EQ(static_cast<int>(heap_statistics.used_heap_size()), 0);
v8::V8::GetHeapStatistics(&heap_statistics);
CHECK_NE(static_cast<int>(heap_statistics.total_heap_size()), 0);
CHECK_NE(static_cast<int>(heap_statistics.used_heap_size()), 0);
}
class VisitorImpl : public v8::ExternalResourceVisitor {
public:
explicit VisitorImpl(TestResource** resource) {
for (int i = 0; i < 4; i++) {
resource_[i] = resource[i];
found_resource_[i] = false;
}
}
virtual ~VisitorImpl() {}
virtual void VisitExternalString(v8::Handle<v8::String> string) {
if (!string->IsExternal()) {
CHECK(string->IsExternalAscii());
return;
}
v8::String::ExternalStringResource* resource =
string->GetExternalStringResource();
CHECK(resource);
for (int i = 0; i < 4; i++) {
if (resource_[i] == resource) {
CHECK(!found_resource_[i]);
found_resource_[i] = true;
}
}
}
void CheckVisitedResources() {
for (int i = 0; i < 4; i++) {
CHECK(found_resource_[i]);
}
}
private:
v8::String::ExternalStringResource* resource_[4];
bool found_resource_[4];
};
TEST(VisitExternalStrings) {
v8::HandleScope scope;
LocalContext env;
const char* string = "Some string";
uint16_t* two_byte_string = AsciiToTwoByteString(string);
TestResource* resource[4];
resource[0] = new TestResource(two_byte_string);
v8::Local<v8::String> string0 = v8::String::NewExternal(resource[0]);
resource[1] = new TestResource(two_byte_string);
v8::Local<v8::String> string1 = v8::String::NewExternal(resource[1]);
// Externalized symbol.
resource[2] = new TestResource(two_byte_string);
v8::Local<v8::String> string2 = v8::String::NewSymbol(string);
CHECK(string2->MakeExternal(resource[2]));
// Symbolized External.
resource[3] = new TestResource(AsciiToTwoByteString("Some other string"));
v8::Local<v8::String> string3 = v8::String::NewExternal(resource[3]);
HEAP->CollectAllAvailableGarbage(); // Tenure string.
// Turn into a symbol.
i::Handle<i::String> string3_i = v8::Utils::OpenHandle(*string3);
CHECK(!HEAP->LookupSymbol(*string3_i)->IsFailure());
CHECK(string3_i->IsSymbol());
// We need to add usages for string* to avoid warnings in GCC 4.7
CHECK(string0->IsExternal());
CHECK(string1->IsExternal());
CHECK(string2->IsExternal());
CHECK(string3->IsExternal());
VisitorImpl visitor(resource);
v8::V8::VisitExternalResources(&visitor);
visitor.CheckVisitedResources();
}
static double DoubleFromBits(uint64_t value) {
double target;
memcpy(&target, &value, sizeof(target));
return target;
}
static uint64_t DoubleToBits(double value) {
uint64_t target;
memcpy(&target, &value, sizeof(target));
return target;
}
static double DoubleToDateTime(double input) {
double date_limit = 864e13;
if (IsNaN(input) || input < -date_limit || input > date_limit) {
return i::OS::nan_value();
}
return (input < 0) ? -(floor(-input)) : floor(input);
}
// We don't have a consistent way to write 64-bit constants syntactically, so we
// split them into two 32-bit constants and combine them programmatically.
static double DoubleFromBits(uint32_t high_bits, uint32_t low_bits) {
return DoubleFromBits((static_cast<uint64_t>(high_bits) << 32) | low_bits);
}
THREADED_TEST(QuietSignalingNaNs) {
v8::HandleScope scope;
LocalContext context;
v8::TryCatch try_catch;
// Special double values.
double snan = DoubleFromBits(0x7ff00000, 0x00000001);
double qnan = DoubleFromBits(0x7ff80000, 0x00000000);
double infinity = DoubleFromBits(0x7ff00000, 0x00000000);
double max_normal = DoubleFromBits(0x7fefffff, 0xffffffffu);
double min_normal = DoubleFromBits(0x00100000, 0x00000000);
double max_denormal = DoubleFromBits(0x000fffff, 0xffffffffu);
double min_denormal = DoubleFromBits(0x00000000, 0x00000001);
// Date values are capped at +/-100000000 days (times 864e5 ms per day)
// on either side of the epoch.
double date_limit = 864e13;
double test_values[] = {
snan,
qnan,
infinity,
max_normal,
date_limit + 1,
date_limit,
min_normal,
max_denormal,
min_denormal,
0,
-0,
-min_denormal,
-max_denormal,
-min_normal,
-date_limit,
-date_limit - 1,
-max_normal,
-infinity,
-qnan,
-snan
};
int num_test_values = 20;
for (int i = 0; i < num_test_values; i++) {
double test_value = test_values[i];
// Check that Number::New preserves non-NaNs and quiets SNaNs.
v8::Handle<v8::Value> number = v8::Number::New(test_value);
double stored_number = number->NumberValue();
if (!IsNaN(test_value)) {
CHECK_EQ(test_value, stored_number);
} else {
uint64_t stored_bits = DoubleToBits(stored_number);
// Check if quiet nan (bits 51..62 all set).
#if defined(V8_TARGET_ARCH_MIPS) && !defined(USE_SIMULATOR)
// Most significant fraction bit for quiet nan is set to 0
// on MIPS architecture. Allowed by IEEE-754.
CHECK_EQ(0xffe, static_cast<int>((stored_bits >> 51) & 0xfff));
#else
CHECK_EQ(0xfff, static_cast<int>((stored_bits >> 51) & 0xfff));
#endif
}
// Check that Date::New preserves non-NaNs in the date range and
// quiets SNaNs.
v8::Handle<v8::Value> date = v8::Date::New(test_value);
double expected_stored_date = DoubleToDateTime(test_value);
double stored_date = date->NumberValue();
if (!IsNaN(expected_stored_date)) {
CHECK_EQ(expected_stored_date, stored_date);
} else {
uint64_t stored_bits = DoubleToBits(stored_date);
// Check if quiet nan (bits 51..62 all set).
#if defined(V8_TARGET_ARCH_MIPS) && !defined(USE_SIMULATOR)
// Most significant fraction bit for quiet nan is set to 0
// on MIPS architecture. Allowed by IEEE-754.
CHECK_EQ(0xffe, static_cast<int>((stored_bits >> 51) & 0xfff));
#else
CHECK_EQ(0xfff, static_cast<int>((stored_bits >> 51) & 0xfff));
#endif
}
}
}
static v8::Handle<Value> SpaghettiIncident(const v8::Arguments& args) {
v8::HandleScope scope;
v8::TryCatch tc;
v8::Handle<v8::String> str(args[0]->ToString());
USE(str);
if (tc.HasCaught())
return tc.ReThrow();
return v8::Undefined();
}
// Test that an exception can be propagated down through a spaghetti
// stack using ReThrow.
THREADED_TEST(SpaghettiStackReThrow) {
v8::HandleScope scope;
LocalContext context;
context->Global()->Set(
v8::String::New("s"),
v8::FunctionTemplate::New(SpaghettiIncident)->GetFunction());
v8::TryCatch try_catch;
CompileRun(
"var i = 0;"
"var o = {"
" toString: function () {"
" if (i == 10) {"
" throw 'Hey!';"
" } else {"
" i++;"
" return s(o);"
" }"
" }"
"};"
"s(o);");
CHECK(try_catch.HasCaught());
v8::String::Utf8Value value(try_catch.Exception());
CHECK_EQ(0, strcmp(*value, "Hey!"));
}
TEST(Regress528) {
v8::V8::Initialize();
v8::HandleScope scope;
v8::Persistent<Context> context;
v8::Persistent<Context> other_context;
int gc_count;
// Create a context used to keep the code from aging in the compilation
// cache.
other_context = Context::New();
// Context-dependent context data creates reference from the compilation
// cache to the global object.
const char* source_simple = "1";
context = Context::New();
{
v8::HandleScope scope;
context->Enter();
Local<v8::String> obj = v8::String::New("");
context->SetEmbedderData(0, obj);
CompileRun(source_simple);
context->Exit();
}
context.Dispose();
v8::V8::ContextDisposedNotification();
for (gc_count = 1; gc_count < 10; gc_count++) {
other_context->Enter();
CompileRun(source_simple);
other_context->Exit();
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
if (GetGlobalObjectsCount() == 1) break;
}
CHECK_GE(2, gc_count);
CHECK_EQ(1, GetGlobalObjectsCount());
// Eval in a function creates reference from the compilation cache to the
// global object.
const char* source_eval = "function f(){eval('1')}; f()";
context = Context::New();
{
v8::HandleScope scope;
context->Enter();
CompileRun(source_eval);
context->Exit();
}
context.Dispose();
v8::V8::ContextDisposedNotification();
for (gc_count = 1; gc_count < 10; gc_count++) {
other_context->Enter();
CompileRun(source_eval);
other_context->Exit();
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
if (GetGlobalObjectsCount() == 1) break;
}
CHECK_GE(2, gc_count);
CHECK_EQ(1, GetGlobalObjectsCount());
// Looking up the line number for an exception creates reference from the
// compilation cache to the global object.
const char* source_exception = "function f(){throw 1;} f()";
context = Context::New();
{
v8::HandleScope scope;
context->Enter();
v8::TryCatch try_catch;
CompileRun(source_exception);
CHECK(try_catch.HasCaught());
v8::Handle<v8::Message> message = try_catch.Message();
CHECK(!message.IsEmpty());
CHECK_EQ(1, message->GetLineNumber());
context->Exit();
}
context.Dispose();
v8::V8::ContextDisposedNotification();
for (gc_count = 1; gc_count < 10; gc_count++) {
other_context->Enter();
CompileRun(source_exception);
other_context->Exit();
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
if (GetGlobalObjectsCount() == 1) break;
}
CHECK_GE(2, gc_count);
CHECK_EQ(1, GetGlobalObjectsCount());
other_context.Dispose();
v8::V8::ContextDisposedNotification();
}
THREADED_TEST(ScriptOrigin) {
v8::HandleScope scope;
LocalContext env;
v8::ScriptOrigin origin = v8::ScriptOrigin(v8::String::New("test"));
v8::Handle<v8::String> script = v8::String::New(
"function f() {}\n\nfunction g() {}");
v8::Script::Compile(script, &origin)->Run();
v8::Local<v8::Function> f = v8::Local<v8::Function>::Cast(
env->Global()->Get(v8::String::New("f")));
v8::Local<v8::Function> g = v8::Local<v8::Function>::Cast(
env->Global()->Get(v8::String::New("g")));
v8::ScriptOrigin script_origin_f = f->GetScriptOrigin();
CHECK_EQ("test", *v8::String::AsciiValue(script_origin_f.ResourceName()));
CHECK_EQ(0, script_origin_f.ResourceLineOffset()->Int32Value());
v8::ScriptOrigin script_origin_g = g->GetScriptOrigin();
CHECK_EQ("test", *v8::String::AsciiValue(script_origin_g.ResourceName()));
CHECK_EQ(0, script_origin_g.ResourceLineOffset()->Int32Value());
}
THREADED_TEST(FunctionGetInferredName) {
v8::HandleScope scope;
LocalContext env;
v8::ScriptOrigin origin = v8::ScriptOrigin(v8::String::New("test"));
v8::Handle<v8::String> script = v8::String::New(
"var foo = { bar : { baz : function() {}}}; var f = foo.bar.baz;");
v8::Script::Compile(script, &origin)->Run();
v8::Local<v8::Function> f = v8::Local<v8::Function>::Cast(
env->Global()->Get(v8::String::New("f")));
CHECK_EQ("foo.bar.baz", *v8::String::AsciiValue(f->GetInferredName()));
}
THREADED_TEST(ScriptLineNumber) {
v8::HandleScope scope;
LocalContext env;
v8::ScriptOrigin origin = v8::ScriptOrigin(v8::String::New("test"));
v8::Handle<v8::String> script = v8::String::New(
"function f() {}\n\nfunction g() {}");
v8::Script::Compile(script, &origin)->Run();
v8::Local<v8::Function> f = v8::Local<v8::Function>::Cast(
env->Global()->Get(v8::String::New("f")));
v8::Local<v8::Function> g = v8::Local<v8::Function>::Cast(
env->Global()->Get(v8::String::New("g")));
CHECK_EQ(0, f->GetScriptLineNumber());
CHECK_EQ(2, g->GetScriptLineNumber());
}
THREADED_TEST(ScriptColumnNumber) {
v8::HandleScope scope;
LocalContext env;
v8::ScriptOrigin origin = v8::ScriptOrigin(v8::String::New("test"),
v8::Integer::New(3), v8::Integer::New(2));
v8::Handle<v8::String> script = v8::String::New(
"function foo() {}\n\n function bar() {}");
v8::Script::Compile(script, &origin)->Run();
v8::Local<v8::Function> foo = v8::Local<v8::Function>::Cast(
env->Global()->Get(v8::String::New("foo")));
v8::Local<v8::Function> bar = v8::Local<v8::Function>::Cast(
env->Global()->Get(v8::String::New("bar")));
CHECK_EQ(14, foo->GetScriptColumnNumber());
CHECK_EQ(17, bar->GetScriptColumnNumber());
}
THREADED_TEST(FunctionGetScriptId) {
v8::HandleScope scope;
LocalContext env;
v8::ScriptOrigin origin = v8::ScriptOrigin(v8::String::New("test"),
v8::Integer::New(3), v8::Integer::New(2));
v8::Handle<v8::String> scriptSource = v8::String::New(
"function foo() {}\n\n function bar() {}");
v8::Local<v8::Script> script(v8::Script::Compile(scriptSource, &origin));
script->Run();
v8::Local<v8::Function> foo = v8::Local<v8::Function>::Cast(
env->Global()->Get(v8::String::New("foo")));
v8::Local<v8::Function> bar = v8::Local<v8::Function>::Cast(
env->Global()->Get(v8::String::New("bar")));
CHECK_EQ(script->Id(), foo->GetScriptId());
CHECK_EQ(script->Id(), bar->GetScriptId());
}
static v8::Handle<Value> GetterWhichReturns42(Local<String> name,
const AccessorInfo& info) {
CHECK(v8::Utils::OpenHandle(*info.This())->IsJSObject());
CHECK(v8::Utils::OpenHandle(*info.Holder())->IsJSObject());
return v8_num(42);
}
static void SetterWhichSetsYOnThisTo23(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
CHECK(v8::Utils::OpenHandle(*info.This())->IsJSObject());
CHECK(v8::Utils::OpenHandle(*info.Holder())->IsJSObject());
info.This()->Set(v8_str("y"), v8_num(23));
}
Handle<Value> FooGetInterceptor(Local<String> name,
const AccessorInfo& info) {
CHECK(v8::Utils::OpenHandle(*info.This())->IsJSObject());
CHECK(v8::Utils::OpenHandle(*info.Holder())->IsJSObject());
if (!name->Equals(v8_str("foo"))) return Handle<Value>();
return v8_num(42);
}
Handle<Value> FooSetInterceptor(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
CHECK(v8::Utils::OpenHandle(*info.This())->IsJSObject());
CHECK(v8::Utils::OpenHandle(*info.Holder())->IsJSObject());
if (!name->Equals(v8_str("foo"))) return Handle<Value>();
info.This()->Set(v8_str("y"), v8_num(23));
return v8_num(23);
}
TEST(SetterOnConstructorPrototype) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessor(v8_str("x"),
GetterWhichReturns42,
SetterWhichSetsYOnThisTo23);
LocalContext context;
context->Global()->Set(v8_str("P"), templ->NewInstance());
CompileRun("function C1() {"
" this.x = 23;"
"};"
"C1.prototype = P;"
"function C2() {"
" this.x = 23"
"};"
"C2.prototype = { };"
"C2.prototype.__proto__ = P;");
v8::Local<v8::Script> script;
script = v8::Script::Compile(v8_str("new C1();"));
for (int i = 0; i < 10; i++) {
v8::Handle<v8::Object> c1 = v8::Handle<v8::Object>::Cast(script->Run());
CHECK_EQ(42, c1->Get(v8_str("x"))->Int32Value());
CHECK_EQ(23, c1->Get(v8_str("y"))->Int32Value());
}
script = v8::Script::Compile(v8_str("new C2();"));
for (int i = 0; i < 10; i++) {
v8::Handle<v8::Object> c2 = v8::Handle<v8::Object>::Cast(script->Run());
CHECK_EQ(42, c2->Get(v8_str("x"))->Int32Value());
CHECK_EQ(23, c2->Get(v8_str("y"))->Int32Value());
}
}
static v8::Handle<Value> NamedPropertyGetterWhichReturns42(
Local<String> name, const AccessorInfo& info) {
return v8_num(42);
}
static v8::Handle<Value> NamedPropertySetterWhichSetsYOnThisTo23(
Local<String> name, Local<Value> value, const AccessorInfo& info) {
if (name->Equals(v8_str("x"))) {
info.This()->Set(v8_str("y"), v8_num(23));
}
return v8::Handle<Value>();
}
THREADED_TEST(InterceptorOnConstructorPrototype) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(NamedPropertyGetterWhichReturns42,
NamedPropertySetterWhichSetsYOnThisTo23);
LocalContext context;
context->Global()->Set(v8_str("P"), templ->NewInstance());
CompileRun("function C1() {"
" this.x = 23;"
"};"
"C1.prototype = P;"
"function C2() {"
" this.x = 23"
"};"
"C2.prototype = { };"
"C2.prototype.__proto__ = P;");
v8::Local<v8::Script> script;
script = v8::Script::Compile(v8_str("new C1();"));
for (int i = 0; i < 10; i++) {
v8::Handle<v8::Object> c1 = v8::Handle<v8::Object>::Cast(script->Run());
CHECK_EQ(23, c1->Get(v8_str("x"))->Int32Value());
CHECK_EQ(42, c1->Get(v8_str("y"))->Int32Value());
}
script = v8::Script::Compile(v8_str("new C2();"));
for (int i = 0; i < 10; i++) {
v8::Handle<v8::Object> c2 = v8::Handle<v8::Object>::Cast(script->Run());
CHECK_EQ(23, c2->Get(v8_str("x"))->Int32Value());
CHECK_EQ(42, c2->Get(v8_str("y"))->Int32Value());
}
}
TEST(Bug618) {
const char* source = "function C1() {"
" this.x = 23;"
"};"
"C1.prototype = P;";
v8::HandleScope scope;
LocalContext context;
v8::Local<v8::Script> script;
// Use a simple object as prototype.
v8::Local<v8::Object> prototype = v8::Object::New();
prototype->Set(v8_str("y"), v8_num(42));
context->Global()->Set(v8_str("P"), prototype);
// This compile will add the code to the compilation cache.
CompileRun(source);
script = v8::Script::Compile(v8_str("new C1();"));
// Allow enough iterations for the inobject slack tracking logic
// to finalize instance size and install the fast construct stub.
for (int i = 0; i < 256; i++) {
v8::Handle<v8::Object> c1 = v8::Handle<v8::Object>::Cast(script->Run());
CHECK_EQ(23, c1->Get(v8_str("x"))->Int32Value());
CHECK_EQ(42, c1->Get(v8_str("y"))->Int32Value());
}
// Use an API object with accessors as prototype.
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessor(v8_str("x"),
GetterWhichReturns42,
SetterWhichSetsYOnThisTo23);
context->Global()->Set(v8_str("P"), templ->NewInstance());
// This compile will get the code from the compilation cache.
CompileRun(source);
script = v8::Script::Compile(v8_str("new C1();"));
for (int i = 0; i < 10; i++) {
v8::Handle<v8::Object> c1 = v8::Handle<v8::Object>::Cast(script->Run());
CHECK_EQ(42, c1->Get(v8_str("x"))->Int32Value());
CHECK_EQ(23, c1->Get(v8_str("y"))->Int32Value());
}
}
int prologue_call_count = 0;
int epilogue_call_count = 0;
int prologue_call_count_second = 0;
int epilogue_call_count_second = 0;
void PrologueCallback(v8::GCType, v8::GCCallbackFlags) {
++prologue_call_count;
}
void EpilogueCallback(v8::GCType, v8::GCCallbackFlags) {
++epilogue_call_count;
}
void PrologueCallbackSecond(v8::GCType, v8::GCCallbackFlags) {
++prologue_call_count_second;
}
void EpilogueCallbackSecond(v8::GCType, v8::GCCallbackFlags) {
++epilogue_call_count_second;
}
TEST(GCCallbacks) {
LocalContext context;
v8::V8::AddGCPrologueCallback(PrologueCallback);
v8::V8::AddGCEpilogueCallback(EpilogueCallback);
CHECK_EQ(0, prologue_call_count);
CHECK_EQ(0, epilogue_call_count);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK_EQ(1, prologue_call_count);
CHECK_EQ(1, epilogue_call_count);
v8::V8::AddGCPrologueCallback(PrologueCallbackSecond);
v8::V8::AddGCEpilogueCallback(EpilogueCallbackSecond);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK_EQ(2, prologue_call_count);
CHECK_EQ(2, epilogue_call_count);
CHECK_EQ(1, prologue_call_count_second);
CHECK_EQ(1, epilogue_call_count_second);
v8::V8::RemoveGCPrologueCallback(PrologueCallback);
v8::V8::RemoveGCEpilogueCallback(EpilogueCallback);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK_EQ(2, prologue_call_count);
CHECK_EQ(2, epilogue_call_count);
CHECK_EQ(2, prologue_call_count_second);
CHECK_EQ(2, epilogue_call_count_second);
v8::V8::RemoveGCPrologueCallback(PrologueCallbackSecond);
v8::V8::RemoveGCEpilogueCallback(EpilogueCallbackSecond);
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
CHECK_EQ(2, prologue_call_count);
CHECK_EQ(2, epilogue_call_count);
CHECK_EQ(2, prologue_call_count_second);
CHECK_EQ(2, epilogue_call_count_second);
}
THREADED_TEST(AddToJSFunctionResultCache) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
LocalContext context;
const char* code =
"(function() {"
" var key0 = 'a';"
" var key1 = 'b';"
" var r0 = %_GetFromCache(0, key0);"
" var r1 = %_GetFromCache(0, key1);"
" var r0_ = %_GetFromCache(0, key0);"
" if (r0 !== r0_)"
" return 'Different results for ' + key0 + ': ' + r0 + ' vs. ' + r0_;"
" var r1_ = %_GetFromCache(0, key1);"
" if (r1 !== r1_)"
" return 'Different results for ' + key1 + ': ' + r1 + ' vs. ' + r1_;"
" return 'PASSED';"
"})()";
HEAP->ClearJSFunctionResultCaches();
ExpectString(code, "PASSED");
}
static const int k0CacheSize = 16;
THREADED_TEST(FillJSFunctionResultCache) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
LocalContext context;
const char* code =
"(function() {"
" var k = 'a';"
" var r = %_GetFromCache(0, k);"
" for (var i = 0; i < 16; i++) {"
" %_GetFromCache(0, 'a' + i);"
" };"
" if (r === %_GetFromCache(0, k))"
" return 'FAILED: k0CacheSize is too small';"
" return 'PASSED';"
"})()";
HEAP->ClearJSFunctionResultCaches();
ExpectString(code, "PASSED");
}
THREADED_TEST(RoundRobinGetFromCache) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
LocalContext context;
const char* code =
"(function() {"
" var keys = [];"
" for (var i = 0; i < 16; i++) keys.push(i);"
" var values = [];"
" for (var i = 0; i < 16; i++) values[i] = %_GetFromCache(0, keys[i]);"
" for (var i = 0; i < 16; i++) {"
" var v = %_GetFromCache(0, keys[i]);"
" if (v.toString() !== values[i].toString())"
" return 'Wrong value for ' + "
" keys[i] + ': ' + v + ' vs. ' + values[i];"
" };"
" return 'PASSED';"
"})()";
HEAP->ClearJSFunctionResultCaches();
ExpectString(code, "PASSED");
}
THREADED_TEST(ReverseGetFromCache) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
LocalContext context;
const char* code =
"(function() {"
" var keys = [];"
" for (var i = 0; i < 16; i++) keys.push(i);"
" var values = [];"
" for (var i = 0; i < 16; i++) values[i] = %_GetFromCache(0, keys[i]);"
" for (var i = 15; i >= 16; i--) {"
" var v = %_GetFromCache(0, keys[i]);"
" if (v !== values[i])"
" return 'Wrong value for ' + "
" keys[i] + ': ' + v + ' vs. ' + values[i];"
" };"
" return 'PASSED';"
"})()";
HEAP->ClearJSFunctionResultCaches();
ExpectString(code, "PASSED");
}
THREADED_TEST(TestEviction) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
LocalContext context;
const char* code =
"(function() {"
" for (var i = 0; i < 2*16; i++) {"
" %_GetFromCache(0, 'a' + i);"
" };"
" return 'PASSED';"
"})()";
HEAP->ClearJSFunctionResultCaches();
ExpectString(code, "PASSED");
}
THREADED_TEST(TwoByteStringInAsciiCons) {
// See Chromium issue 47824.
v8::HandleScope scope;
LocalContext context;
const char* init_code =
"var str1 = 'abelspendabel';"
"var str2 = str1 + str1 + str1;"
"str2;";
Local<Value> result = CompileRun(init_code);
Local<Value> indexof = CompileRun("str2.indexOf('els')");
Local<Value> lastindexof = CompileRun("str2.lastIndexOf('dab')");
CHECK(result->IsString());
i::Handle<i::String> string = v8::Utils::OpenHandle(String::Cast(*result));
int length = string->length();
CHECK(string->IsOneByteRepresentation());
FlattenString(string);
i::Handle<i::String> flat_string = FlattenGetString(string);
CHECK(string->IsOneByteRepresentation());
CHECK(flat_string->IsOneByteRepresentation());
// Create external resource.
uint16_t* uc16_buffer = new uint16_t[length + 1];
i::String::WriteToFlat(*flat_string, uc16_buffer, 0, length);
uc16_buffer[length] = 0;
TestResource resource(uc16_buffer);
flat_string->MakeExternal(&resource);
CHECK(flat_string->IsTwoByteRepresentation());
// At this point, we should have a Cons string which is flat and ASCII,
// with a first half that is a two-byte string (although it only contains
// ASCII characters). This is a valid sequence of steps, and it can happen
// in real pages.
CHECK(string->IsOneByteRepresentation());
i::ConsString* cons = i::ConsString::cast(*string);
CHECK_EQ(0, cons->second()->length());
CHECK(cons->first()->IsTwoByteRepresentation());
// Check that some string operations work.
// Atom RegExp.
Local<Value> reresult = CompileRun("str2.match(/abel/g).length;");
CHECK_EQ(6, reresult->Int32Value());
// Nonatom RegExp.
reresult = CompileRun("str2.match(/abe./g).length;");
CHECK_EQ(6, reresult->Int32Value());
reresult = CompileRun("str2.search(/bel/g);");
CHECK_EQ(1, reresult->Int32Value());
reresult = CompileRun("str2.search(/be./g);");
CHECK_EQ(1, reresult->Int32Value());
ExpectTrue("/bel/g.test(str2);");
ExpectTrue("/be./g.test(str2);");
reresult = CompileRun("/bel/g.exec(str2);");
CHECK(!reresult->IsNull());
reresult = CompileRun("/be./g.exec(str2);");
CHECK(!reresult->IsNull());
ExpectString("str2.substring(2, 10);", "elspenda");
ExpectString("str2.substring(2, 20);", "elspendabelabelspe");
ExpectString("str2.charAt(2);", "e");
ExpectObject("str2.indexOf('els');", indexof);
ExpectObject("str2.lastIndexOf('dab');", lastindexof);
reresult = CompileRun("str2.charCodeAt(2);");
CHECK_EQ(static_cast<int32_t>('e'), reresult->Int32Value());
}
// Failed access check callback that performs a GC on each invocation.
void FailedAccessCheckCallbackGC(Local<v8::Object> target,
v8::AccessType type,
Local<v8::Value> data) {
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
}
TEST(GCInFailedAccessCheckCallback) {
// Install a failed access check callback that performs a GC on each
// invocation. Then force the callback to be called from va
v8::V8::Initialize();
v8::V8::SetFailedAccessCheckCallbackFunction(&FailedAccessCheckCallbackGC);
v8::HandleScope scope;
// Create an ObjectTemplate for global objects and install access
// check callbacks that will block access.
v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
global_template->SetAccessCheckCallbacks(NamedGetAccessBlocker,
IndexedGetAccessBlocker,
v8::Handle<v8::Value>(),
false);
// Create a context and set an x property on it's global object.
LocalContext context0(NULL, global_template);
context0->Global()->Set(v8_str("x"), v8_num(42));
v8::Handle<v8::Object> global0 = context0->Global();
// Create a context with a different security token so that the
// failed access check callback will be called on each access.
LocalContext context1(NULL, global_template);
context1->Global()->Set(v8_str("other"), global0);
// Get property with failed access check.
ExpectUndefined("other.x");
// Get element with failed access check.
ExpectUndefined("other[0]");
// Set property with failed access check.
v8::Handle<v8::Value> result = CompileRun("other.x = new Object()");
CHECK(result->IsObject());
// Set element with failed access check.
result = CompileRun("other[0] = new Object()");
CHECK(result->IsObject());
// Get property attribute with failed access check.
ExpectFalse("\'x\' in other");
// Get property attribute for element with failed access check.
ExpectFalse("0 in other");
// Delete property.
ExpectFalse("delete other.x");
// Delete element.
CHECK_EQ(false, global0->Delete(0));
// DefineAccessor.
CHECK_EQ(false,
global0->SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("x")));
// Define JavaScript accessor.
ExpectUndefined("Object.prototype.__defineGetter__.call("
" other, \'x\', function() { return 42; })");
// LookupAccessor.
ExpectUndefined("Object.prototype.__lookupGetter__.call("
" other, \'x\')");
// HasLocalElement.
ExpectFalse("Object.prototype.hasOwnProperty.call(other, \'0\')");
CHECK_EQ(false, global0->HasRealIndexedProperty(0));
CHECK_EQ(false, global0->HasRealNamedProperty(v8_str("x")));
CHECK_EQ(false, global0->HasRealNamedCallbackProperty(v8_str("x")));
// Reset the failed access check callback so it does not influence
// the other tests.
v8::V8::SetFailedAccessCheckCallbackFunction(NULL);
}
TEST(DefaultIsolateGetCurrent) {
CHECK(v8::Isolate::GetCurrent() != NULL);
v8::Isolate* isolate = v8::Isolate::GetCurrent();
CHECK(reinterpret_cast<i::Isolate*>(isolate)->IsDefaultIsolate());
printf("*** %s\n", "DefaultIsolateGetCurrent success");
}
TEST(IsolateNewDispose) {
v8::Isolate* current_isolate = v8::Isolate::GetCurrent();
v8::Isolate* isolate = v8::Isolate::New();
CHECK(isolate != NULL);
CHECK(!reinterpret_cast<i::Isolate*>(isolate)->IsDefaultIsolate());
CHECK(current_isolate != isolate);
CHECK(current_isolate == v8::Isolate::GetCurrent());
v8::V8::SetFatalErrorHandler(StoringErrorCallback);
last_location = last_message = NULL;
isolate->Dispose();
CHECK_EQ(last_location, NULL);
CHECK_EQ(last_message, NULL);
}
TEST(IsolateEnterExitDefault) {
v8::HandleScope scope;
LocalContext context;
v8::Isolate* current_isolate = v8::Isolate::GetCurrent();
CHECK(current_isolate != NULL); // Default isolate.
ExpectString("'hello'", "hello");
current_isolate->Enter();
ExpectString("'still working'", "still working");
current_isolate->Exit();
ExpectString("'still working 2'", "still working 2");
current_isolate->Exit();
// Default isolate is always, well, 'default current'.
CHECK_EQ(v8::Isolate::GetCurrent(), current_isolate);
// Still working since default isolate is auto-entering any thread
// that has no isolate and attempts to execute V8 APIs.
ExpectString("'still working 3'", "still working 3");
}
TEST(DisposeDefaultIsolate) {
v8::V8::SetFatalErrorHandler(StoringErrorCallback);
// Run some V8 code to trigger default isolate to become 'current'.
v8::HandleScope scope;
LocalContext context;
ExpectString("'run some V8'", "run some V8");
v8::Isolate* isolate = v8::Isolate::GetCurrent();
CHECK(reinterpret_cast<i::Isolate*>(isolate)->IsDefaultIsolate());
last_location = last_message = NULL;
isolate->Dispose();
// It is not possible to dispose default isolate via Isolate API.
CHECK_NE(last_location, NULL);
CHECK_NE(last_message, NULL);
}
TEST(RunDefaultAndAnotherIsolate) {
v8::HandleScope scope;
LocalContext context;
// Enter new isolate.
v8::Isolate* isolate = v8::Isolate::New();
CHECK(isolate);
isolate->Enter();
{ // Need this block because subsequent Exit() will deallocate Heap,
// so we need all scope objects to be deconstructed when it happens.
v8::HandleScope scope_new;
LocalContext context_new;
// Run something in new isolate.
CompileRun("var foo = 153;");
ExpectTrue("function f() { return foo == 153; }; f()");
}
isolate->Exit();
// This runs automatically in default isolate.
// Variables in another isolate should be not available.
ExpectTrue("function f() {"
" try {"
" foo;"
" return false;"
" } catch(e) {"
" return true;"
" }"
"};"
"var bar = 371;"
"f()");
v8::V8::SetFatalErrorHandler(StoringErrorCallback);
last_location = last_message = NULL;
isolate->Dispose();
CHECK_EQ(last_location, NULL);
CHECK_EQ(last_message, NULL);
// Check that default isolate still runs.
ExpectTrue("function f() { return bar == 371; }; f()");
}
TEST(DisposeIsolateWhenInUse) {
v8::Isolate* isolate = v8::Isolate::New();
CHECK(isolate);
isolate->Enter();
v8::HandleScope scope;
LocalContext context;
// Run something in this isolate.
ExpectTrue("true");
v8::V8::SetFatalErrorHandler(StoringErrorCallback);
last_location = last_message = NULL;
// Still entered, should fail.
isolate->Dispose();
CHECK_NE(last_location, NULL);
CHECK_NE(last_message, NULL);
}
TEST(RunTwoIsolatesOnSingleThread) {
// Run isolate 1.
v8::Isolate* isolate1 = v8::Isolate::New();
isolate1->Enter();
v8::Persistent<v8::Context> context1 = v8::Context::New();
{
v8::Context::Scope cscope(context1);
v8::HandleScope scope;
// Run something in new isolate.
CompileRun("var foo = 'isolate 1';");
ExpectString("function f() { return foo; }; f()", "isolate 1");
}
// Run isolate 2.
v8::Isolate* isolate2 = v8::Isolate::New();
v8::Persistent<v8::Context> context2;
{
v8::Isolate::Scope iscope(isolate2);
context2 = v8::Context::New();
v8::Context::Scope cscope(context2);
v8::HandleScope scope;
// Run something in new isolate.
CompileRun("var foo = 'isolate 2';");
ExpectString("function f() { return foo; }; f()", "isolate 2");
}
{
v8::Context::Scope cscope(context1);
v8::HandleScope scope;
// Now again in isolate 1
ExpectString("function f() { return foo; }; f()", "isolate 1");
}
isolate1->Exit();
// Run some stuff in default isolate.
v8::Persistent<v8::Context> context_default = v8::Context::New();
{
v8::Context::Scope cscope(context_default);
v8::HandleScope scope;
// Variables in other isolates should be not available, verify there
// is an exception.
ExpectTrue("function f() {"
" try {"
" foo;"
" return false;"
" } catch(e) {"
" return true;"
" }"
"};"
"var isDefaultIsolate = true;"
"f()");
}
isolate1->Enter();
{
v8::Isolate::Scope iscope(isolate2);
v8::Context::Scope cscope(context2);
v8::HandleScope scope;
ExpectString("function f() { return foo; }; f()", "isolate 2");
}
{
v8::Context::Scope cscope(context1);
v8::HandleScope scope;
ExpectString("function f() { return foo; }; f()", "isolate 1");
}
{
v8::Isolate::Scope iscope(isolate2);
context2.Dispose();
}
context1.Dispose();
isolate1->Exit();
v8::V8::SetFatalErrorHandler(StoringErrorCallback);
last_location = last_message = NULL;
isolate1->Dispose();
CHECK_EQ(last_location, NULL);
CHECK_EQ(last_message, NULL);
isolate2->Dispose();
CHECK_EQ(last_location, NULL);
CHECK_EQ(last_message, NULL);
// Check that default isolate still runs.
{
v8::Context::Scope cscope(context_default);
v8::HandleScope scope;
ExpectTrue("function f() { return isDefaultIsolate; }; f()");
}
}
static int CalcFibonacci(v8::Isolate* isolate, int limit) {
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope scope;
LocalContext context;
i::ScopedVector<char> code(1024);
i::OS::SNPrintF(code, "function fib(n) {"
" if (n <= 2) return 1;"
" return fib(n-1) + fib(n-2);"
"}"
"fib(%d)", limit);
Local<Value> value = CompileRun(code.start());
CHECK(value->IsNumber());
return static_cast<int>(value->NumberValue());
}
class IsolateThread : public v8::internal::Thread {
public:
IsolateThread(v8::Isolate* isolate, int fib_limit)
: Thread("IsolateThread"),
isolate_(isolate),
fib_limit_(fib_limit),
result_(0) { }
void Run() {
result_ = CalcFibonacci(isolate_, fib_limit_);
}
int result() { return result_; }
private:
v8::Isolate* isolate_;
int fib_limit_;
int result_;
};
TEST(MultipleIsolatesOnIndividualThreads) {
v8::Isolate* isolate1 = v8::Isolate::New();
v8::Isolate* isolate2 = v8::Isolate::New();
IsolateThread thread1(isolate1, 21);
IsolateThread thread2(isolate2, 12);
// Compute some fibonacci numbers on 3 threads in 3 isolates.
thread1.Start();
thread2.Start();
int result1 = CalcFibonacci(v8::Isolate::GetCurrent(), 21);
int result2 = CalcFibonacci(v8::Isolate::GetCurrent(), 12);
thread1.Join();
thread2.Join();
// Compare results. The actual fibonacci numbers for 12 and 21 are taken
// (I'm lazy!) from http://en.wikipedia.org/wiki/Fibonacci_number
CHECK_EQ(result1, 10946);
CHECK_EQ(result2, 144);
CHECK_EQ(result1, thread1.result());
CHECK_EQ(result2, thread2.result());
isolate1->Dispose();
isolate2->Dispose();
}
TEST(IsolateDifferentContexts) {
v8::Isolate* isolate = v8::Isolate::New();
Persistent<v8::Context> context;
{
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope;
context = v8::Context::New();
v8::Context::Scope context_scope(context);
Local<Value> v = CompileRun("2");
CHECK(v->IsNumber());
CHECK_EQ(2, static_cast<int>(v->NumberValue()));
}
{
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope;
context = v8::Context::New();
v8::Context::Scope context_scope(context);
Local<Value> v = CompileRun("22");
CHECK(v->IsNumber());
CHECK_EQ(22, static_cast<int>(v->NumberValue()));
}
}
class InitDefaultIsolateThread : public v8::internal::Thread {
public:
enum TestCase {
IgnoreOOM,
SetResourceConstraints,
SetFatalHandler,
SetCounterFunction,
SetCreateHistogramFunction,
SetAddHistogramSampleFunction
};
explicit InitDefaultIsolateThread(TestCase testCase)
: Thread("InitDefaultIsolateThread"),
testCase_(testCase),
result_(false) { }
void Run() {
switch (testCase_) {
case IgnoreOOM:
v8::V8::IgnoreOutOfMemoryException();
break;
case SetResourceConstraints: {
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);
break;
}
case SetFatalHandler:
v8::V8::SetFatalErrorHandler(NULL);
break;
case SetCounterFunction:
v8::V8::SetCounterFunction(NULL);
break;
case SetCreateHistogramFunction:
v8::V8::SetCreateHistogramFunction(NULL);
break;
case SetAddHistogramSampleFunction:
v8::V8::SetAddHistogramSampleFunction(NULL);
break;
}
result_ = true;
}
bool result() { return result_; }
private:
TestCase testCase_;
bool result_;
};
static void InitializeTestHelper(InitDefaultIsolateThread::TestCase testCase) {
InitDefaultIsolateThread thread(testCase);
thread.Start();
thread.Join();
CHECK_EQ(thread.result(), true);
}
TEST(InitializeDefaultIsolateOnSecondaryThread1) {
InitializeTestHelper(InitDefaultIsolateThread::IgnoreOOM);
}
TEST(InitializeDefaultIsolateOnSecondaryThread2) {
InitializeTestHelper(InitDefaultIsolateThread::SetResourceConstraints);
}
TEST(InitializeDefaultIsolateOnSecondaryThread3) {
InitializeTestHelper(InitDefaultIsolateThread::SetFatalHandler);
}
TEST(InitializeDefaultIsolateOnSecondaryThread4) {
InitializeTestHelper(InitDefaultIsolateThread::SetCounterFunction);
}
TEST(InitializeDefaultIsolateOnSecondaryThread5) {
InitializeTestHelper(InitDefaultIsolateThread::SetCreateHistogramFunction);
}
TEST(InitializeDefaultIsolateOnSecondaryThread6) {
InitializeTestHelper(InitDefaultIsolateThread::SetAddHistogramSampleFunction);
}
TEST(StringCheckMultipleContexts) {
const char* code =
"(function() { return \"a\".charAt(0); })()";
{
// Run the code twice in the first context to initialize the call IC.
v8::HandleScope scope;
LocalContext context1;
ExpectString(code, "a");
ExpectString(code, "a");
}
{
// Change the String.prototype in the second context and check
// that the right function gets called.
v8::HandleScope scope;
LocalContext context2;
CompileRun("String.prototype.charAt = function() { return \"not a\"; }");
ExpectString(code, "not a");
}
}
TEST(NumberCheckMultipleContexts) {
const char* code =
"(function() { return (42).toString(); })()";
{
// Run the code twice in the first context to initialize the call IC.
v8::HandleScope scope;
LocalContext context1;
ExpectString(code, "42");
ExpectString(code, "42");
}
{
// Change the Number.prototype in the second context and check
// that the right function gets called.
v8::HandleScope scope;
LocalContext context2;
CompileRun("Number.prototype.toString = function() { return \"not 42\"; }");
ExpectString(code, "not 42");
}
}
TEST(BooleanCheckMultipleContexts) {
const char* code =
"(function() { return true.toString(); })()";
{
// Run the code twice in the first context to initialize the call IC.
v8::HandleScope scope;
LocalContext context1;
ExpectString(code, "true");
ExpectString(code, "true");
}
{
// Change the Boolean.prototype in the second context and check
// that the right function gets called.
v8::HandleScope scope;
LocalContext context2;
CompileRun("Boolean.prototype.toString = function() { return \"\"; }");
ExpectString(code, "");
}
}
TEST(DontDeleteCellLoadIC) {
const char* function_code =
"function readCell() { while (true) { return cell; } }";
{
// Run the code twice in the first context to initialize the load
// IC for a don't delete cell.
v8::HandleScope scope;
LocalContext context1;
CompileRun("var cell = \"first\";");
ExpectBoolean("delete cell", false);
CompileRun(function_code);
ExpectString("readCell()", "first");
ExpectString("readCell()", "first");
}
{
// Use a deletable cell in the second context.
v8::HandleScope scope;
LocalContext context2;
CompileRun("cell = \"second\";");
CompileRun(function_code);
ExpectString("readCell()", "second");
ExpectBoolean("delete cell", true);
ExpectString("(function() {"
" try {"
" return readCell();"
" } catch(e) {"
" return e.toString();"
" }"
"})()",
"ReferenceError: cell is not defined");
CompileRun("cell = \"new_second\";");
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
ExpectString("readCell()", "new_second");
ExpectString("readCell()", "new_second");
}
}
TEST(DontDeleteCellLoadICForceDelete) {
const char* function_code =
"function readCell() { while (true) { return cell; } }";
// Run the code twice to initialize the load IC for a don't delete
// cell.
v8::HandleScope scope;
LocalContext context;
CompileRun("var cell = \"value\";");
ExpectBoolean("delete cell", false);
CompileRun(function_code);
ExpectString("readCell()", "value");
ExpectString("readCell()", "value");
// Delete the cell using the API and check the inlined code works
// correctly.
CHECK(context->Global()->ForceDelete(v8_str("cell")));
ExpectString("(function() {"
" try {"
" return readCell();"
" } catch(e) {"
" return e.toString();"
" }"
"})()",
"ReferenceError: cell is not defined");
}
TEST(DontDeleteCellLoadICAPI) {
const char* function_code =
"function readCell() { while (true) { return cell; } }";
// Run the code twice to initialize the load IC for a don't delete
// cell created using the API.
v8::HandleScope scope;
LocalContext context;
context->Global()->Set(v8_str("cell"), v8_str("value"), v8::DontDelete);
ExpectBoolean("delete cell", false);
CompileRun(function_code);
ExpectString("readCell()", "value");
ExpectString("readCell()", "value");
// Delete the cell using the API and check the inlined code works
// correctly.
CHECK(context->Global()->ForceDelete(v8_str("cell")));
ExpectString("(function() {"
" try {"
" return readCell();"
" } catch(e) {"
" return e.toString();"
" }"
"})()",
"ReferenceError: cell is not defined");
}
class Visitor42 : public v8::PersistentHandleVisitor {
public:
explicit Visitor42(v8::Persistent<v8::Object> object)
: counter_(0), object_(object) { }
virtual void VisitPersistentHandle(Persistent<Value> value,
uint16_t class_id) {
if (class_id == 42) {
CHECK(value->IsObject());
v8::Persistent<v8::Object> visited =
v8::Persistent<v8::Object>::Cast(value);
CHECK_EQ(42, visited.WrapperClassId());
CHECK_EQ(object_, visited);
++counter_;
}
}
int counter_;
v8::Persistent<v8::Object> object_;
};
TEST(PersistentHandleVisitor) {
v8::HandleScope scope;
LocalContext context;
v8::Persistent<v8::Object> object =
v8::Persistent<v8::Object>::New(v8::Object::New());
CHECK_EQ(0, object.WrapperClassId());
object.SetWrapperClassId(42);
CHECK_EQ(42, object.WrapperClassId());
Visitor42 visitor(object);
v8::V8::VisitHandlesWithClassIds(&visitor);
CHECK_EQ(1, visitor.counter_);
object.Dispose();
}
TEST(WrapperClassId) {
v8::HandleScope scope;
LocalContext context;
v8::Persistent<v8::Object> object =
v8::Persistent<v8::Object>::New(v8::Object::New());
CHECK_EQ(0, object.WrapperClassId());
object.SetWrapperClassId(65535);
CHECK_EQ(65535, object.WrapperClassId());
object.Dispose();
}
TEST(RegExp) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<v8::RegExp> re = v8::RegExp::New(v8_str("foo"), v8::RegExp::kNone);
CHECK(re->IsRegExp());
CHECK(re->GetSource()->Equals(v8_str("foo")));
CHECK_EQ(v8::RegExp::kNone, re->GetFlags());
re = v8::RegExp::New(v8_str("bar"),
static_cast<v8::RegExp::Flags>(v8::RegExp::kIgnoreCase |
v8::RegExp::kGlobal));
CHECK(re->IsRegExp());
CHECK(re->GetSource()->Equals(v8_str("bar")));
CHECK_EQ(v8::RegExp::kIgnoreCase | v8::RegExp::kGlobal,
static_cast<int>(re->GetFlags()));
re = v8::RegExp::New(v8_str("baz"),
static_cast<v8::RegExp::Flags>(v8::RegExp::kIgnoreCase |
v8::RegExp::kMultiline));
CHECK(re->IsRegExp());
CHECK(re->GetSource()->Equals(v8_str("baz")));
CHECK_EQ(v8::RegExp::kIgnoreCase | v8::RegExp::kMultiline,
static_cast<int>(re->GetFlags()));
re = CompileRun("/quux/").As<v8::RegExp>();
CHECK(re->IsRegExp());
CHECK(re->GetSource()->Equals(v8_str("quux")));
CHECK_EQ(v8::RegExp::kNone, re->GetFlags());
re = CompileRun("/quux/gm").As<v8::RegExp>();
CHECK(re->IsRegExp());
CHECK(re->GetSource()->Equals(v8_str("quux")));
CHECK_EQ(v8::RegExp::kGlobal | v8::RegExp::kMultiline,
static_cast<int>(re->GetFlags()));
// Override the RegExp constructor and check the API constructor
// still works.
CompileRun("RegExp = function() {}");
re = v8::RegExp::New(v8_str("foobar"), v8::RegExp::kNone);
CHECK(re->IsRegExp());
CHECK(re->GetSource()->Equals(v8_str("foobar")));
CHECK_EQ(v8::RegExp::kNone, re->GetFlags());
re = v8::RegExp::New(v8_str("foobarbaz"),
static_cast<v8::RegExp::Flags>(v8::RegExp::kIgnoreCase |
v8::RegExp::kMultiline));
CHECK(re->IsRegExp());
CHECK(re->GetSource()->Equals(v8_str("foobarbaz")));
CHECK_EQ(v8::RegExp::kIgnoreCase | v8::RegExp::kMultiline,
static_cast<int>(re->GetFlags()));
context->Global()->Set(v8_str("re"), re);
ExpectTrue("re.test('FoobarbaZ')");
// RegExps are objects on which you can set properties.
re->Set(v8_str("property"), v8::Integer::New(32));
v8::Handle<v8::Value> value(CompileRun("re.property"));
CHECK_EQ(32, value->Int32Value());
v8::TryCatch try_catch;
re = v8::RegExp::New(v8_str("foo["), v8::RegExp::kNone);
CHECK(re.IsEmpty());
CHECK(try_catch.HasCaught());
context->Global()->Set(v8_str("ex"), try_catch.Exception());
ExpectTrue("ex instanceof SyntaxError");
}
THREADED_TEST(Equals) {
v8::HandleScope handleScope;
LocalContext localContext;
v8::Handle<v8::Object> globalProxy = localContext->Global();
v8::Handle<Value> global = globalProxy->GetPrototype();
CHECK(global->StrictEquals(global));
CHECK(!global->StrictEquals(globalProxy));
CHECK(!globalProxy->StrictEquals(global));
CHECK(globalProxy->StrictEquals(globalProxy));
CHECK(global->Equals(global));
CHECK(!global->Equals(globalProxy));
CHECK(!globalProxy->Equals(global));
CHECK(globalProxy->Equals(globalProxy));
}
static v8::Handle<v8::Value> Getter(v8::Local<v8::String> property,
const v8::AccessorInfo& info ) {
return v8_str("42!");
}
static v8::Handle<v8::Array> Enumerator(const v8::AccessorInfo& info) {
v8::Handle<v8::Array> result = v8::Array::New();
result->Set(0, v8_str("universalAnswer"));
return result;
}
TEST(NamedEnumeratorAndForIn) {
v8::HandleScope handle_scope;
LocalContext context;
v8::Context::Scope context_scope(context.local());
v8::Handle<v8::ObjectTemplate> tmpl = v8::ObjectTemplate::New();
tmpl->SetNamedPropertyHandler(Getter, NULL, NULL, NULL, Enumerator);
context->Global()->Set(v8_str("o"), tmpl->NewInstance());
v8::Handle<v8::Array> result = v8::Handle<v8::Array>::Cast(CompileRun(
"var result = []; for (var k in o) result.push(k); result"));
CHECK_EQ(1, result->Length());
CHECK_EQ(v8_str("universalAnswer"), result->Get(0));
}
TEST(DefinePropertyPostDetach) {
v8::HandleScope scope;
LocalContext context;
v8::Handle<v8::Object> proxy = context->Global();
v8::Handle<v8::Function> define_property =
CompileRun("(function() {"
" Object.defineProperty("
" this,"
" 1,"
" { configurable: true, enumerable: true, value: 3 });"
"})").As<Function>();
context->DetachGlobal();
define_property->Call(proxy, 0, NULL);
}
static void InstallContextId(v8::Handle<Context> context, int id) {
Context::Scope scope(context);
CompileRun("Object.prototype").As<Object>()->
Set(v8_str("context_id"), v8::Integer::New(id));
}
static void CheckContextId(v8::Handle<Object> object, int expected) {
CHECK_EQ(expected, object->Get(v8_str("context_id"))->Int32Value());
}
THREADED_TEST(CreationContext) {
HandleScope handle_scope;
Persistent<Context> context1 = Context::New();
InstallContextId(context1, 1);
Persistent<Context> context2 = Context::New();
InstallContextId(context2, 2);
Persistent<Context> context3 = Context::New();
InstallContextId(context3, 3);
Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New();
Local<Object> object1;
Local<Function> func1;
{
Context::Scope scope(context1);
object1 = Object::New();
func1 = tmpl->GetFunction();
}
Local<Object> object2;
Local<Function> func2;
{
Context::Scope scope(context2);
object2 = Object::New();
func2 = tmpl->GetFunction();
}
Local<Object> instance1;
Local<Object> instance2;
{
Context::Scope scope(context3);
instance1 = func1->NewInstance();
instance2 = func2->NewInstance();
}
CHECK(object1->CreationContext() == context1);
CheckContextId(object1, 1);
CHECK(func1->CreationContext() == context1);
CheckContextId(func1, 1);
CHECK(instance1->CreationContext() == context1);
CheckContextId(instance1, 1);
CHECK(object2->CreationContext() == context2);
CheckContextId(object2, 2);
CHECK(func2->CreationContext() == context2);
CheckContextId(func2, 2);
CHECK(instance2->CreationContext() == context2);
CheckContextId(instance2, 2);
{
Context::Scope scope(context1);
CHECK(object1->CreationContext() == context1);
CheckContextId(object1, 1);
CHECK(func1->CreationContext() == context1);
CheckContextId(func1, 1);
CHECK(instance1->CreationContext() == context1);
CheckContextId(instance1, 1);
CHECK(object2->CreationContext() == context2);
CheckContextId(object2, 2);
CHECK(func2->CreationContext() == context2);
CheckContextId(func2, 2);
CHECK(instance2->CreationContext() == context2);
CheckContextId(instance2, 2);
}
{
Context::Scope scope(context2);
CHECK(object1->CreationContext() == context1);
CheckContextId(object1, 1);
CHECK(func1->CreationContext() == context1);
CheckContextId(func1, 1);
CHECK(instance1->CreationContext() == context1);
CheckContextId(instance1, 1);
CHECK(object2->CreationContext() == context2);
CheckContextId(object2, 2);
CHECK(func2->CreationContext() == context2);
CheckContextId(func2, 2);
CHECK(instance2->CreationContext() == context2);
CheckContextId(instance2, 2);
}
context1.Dispose();
context2.Dispose();
context3.Dispose();
}
THREADED_TEST(CreationContextOfJsFunction) {
HandleScope handle_scope;
Persistent<Context> context = Context::New();
InstallContextId(context, 1);
Local<Object> function;
{
Context::Scope scope(context);
function = CompileRun("function foo() {}; foo").As<Object>();
}
CHECK(function->CreationContext() == context);
CheckContextId(function, 1);
context.Dispose();
}
Handle<Value> HasOwnPropertyIndexedPropertyGetter(uint32_t index,
const AccessorInfo& info) {
if (index == 42) return v8_str("yes");
return Handle<v8::Integer>();
}
Handle<Value> HasOwnPropertyNamedPropertyGetter(Local<String> property,
const AccessorInfo& info) {
if (property->Equals(v8_str("foo"))) return v8_str("yes");
return Handle<Value>();
}
Handle<v8::Integer> HasOwnPropertyIndexedPropertyQuery(
uint32_t index, const AccessorInfo& info) {
if (index == 42) return v8_num(1).As<v8::Integer>();
return Handle<v8::Integer>();
}
Handle<v8::Integer> HasOwnPropertyNamedPropertyQuery(
Local<String> property, const AccessorInfo& info) {
if (property->Equals(v8_str("foo"))) return v8_num(1).As<v8::Integer>();
return Handle<v8::Integer>();
}
Handle<v8::Integer> HasOwnPropertyNamedPropertyQuery2(
Local<String> property, const AccessorInfo& info) {
if (property->Equals(v8_str("bar"))) return v8_num(1).As<v8::Integer>();
return Handle<v8::Integer>();
}
Handle<Value> HasOwnPropertyAccessorGetter(Local<String> property,
const AccessorInfo& info) {
return v8_str("yes");
}
TEST(HasOwnProperty) {
v8::HandleScope scope;
LocalContext env;
{ // Check normal properties and defined getters.
Handle<Value> value = CompileRun(
"function Foo() {"
" this.foo = 11;"
" this.__defineGetter__('baz', function() { return 1; });"
"};"
"function Bar() { "
" this.bar = 13;"
" this.__defineGetter__('bla', function() { return 2; });"
"};"
"Bar.prototype = new Foo();"
"new Bar();");
CHECK(value->IsObject());
Handle<Object> object = value->ToObject();
CHECK(object->Has(v8_str("foo")));
CHECK(!object->HasOwnProperty(v8_str("foo")));
CHECK(object->HasOwnProperty(v8_str("bar")));
CHECK(object->Has(v8_str("baz")));
CHECK(!object->HasOwnProperty(v8_str("baz")));
CHECK(object->HasOwnProperty(v8_str("bla")));
}
{ // Check named getter interceptors.
Handle<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(HasOwnPropertyNamedPropertyGetter);
Handle<Object> instance = templ->NewInstance();
CHECK(!instance->HasOwnProperty(v8_str("42")));
CHECK(instance->HasOwnProperty(v8_str("foo")));
CHECK(!instance->HasOwnProperty(v8_str("bar")));
}
{ // Check indexed getter interceptors.
Handle<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(HasOwnPropertyIndexedPropertyGetter);
Handle<Object> instance = templ->NewInstance();
CHECK(instance->HasOwnProperty(v8_str("42")));
CHECK(!instance->HasOwnProperty(v8_str("43")));
CHECK(!instance->HasOwnProperty(v8_str("foo")));
}
{ // Check named query interceptors.
Handle<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(0, 0, HasOwnPropertyNamedPropertyQuery);
Handle<Object> instance = templ->NewInstance();
CHECK(instance->HasOwnProperty(v8_str("foo")));
CHECK(!instance->HasOwnProperty(v8_str("bar")));
}
{ // Check indexed query interceptors.
Handle<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetIndexedPropertyHandler(0, 0, HasOwnPropertyIndexedPropertyQuery);
Handle<Object> instance = templ->NewInstance();
CHECK(instance->HasOwnProperty(v8_str("42")));
CHECK(!instance->HasOwnProperty(v8_str("41")));
}
{ // Check callbacks.
Handle<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessor(v8_str("foo"), HasOwnPropertyAccessorGetter);
Handle<Object> instance = templ->NewInstance();
CHECK(instance->HasOwnProperty(v8_str("foo")));
CHECK(!instance->HasOwnProperty(v8_str("bar")));
}
{ // Check that query wins on disagreement.
Handle<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetNamedPropertyHandler(HasOwnPropertyNamedPropertyGetter,
0,
HasOwnPropertyNamedPropertyQuery2);
Handle<Object> instance = templ->NewInstance();
CHECK(!instance->HasOwnProperty(v8_str("foo")));
CHECK(instance->HasOwnProperty(v8_str("bar")));
}
}
void CheckCodeGenerationAllowed() {
Handle<Value> result = CompileRun("eval('42')");
CHECK_EQ(42, result->Int32Value());
result = CompileRun("(function(e) { return e('42'); })(eval)");
CHECK_EQ(42, result->Int32Value());
result = CompileRun("var f = new Function('return 42'); f()");
CHECK_EQ(42, result->Int32Value());
}
void CheckCodeGenerationDisallowed() {
TryCatch try_catch;
Handle<Value> result = CompileRun("eval('42')");
CHECK(result.IsEmpty());
CHECK(try_catch.HasCaught());
try_catch.Reset();
result = CompileRun("(function(e) { return e('42'); })(eval)");
CHECK(result.IsEmpty());
CHECK(try_catch.HasCaught());
try_catch.Reset();
result = CompileRun("var f = new Function('return 42'); f()");
CHECK(result.IsEmpty());
CHECK(try_catch.HasCaught());
}
bool CodeGenerationAllowed(Local<Context> context) {
ApiTestFuzzer::Fuzz();
return true;
}
bool CodeGenerationDisallowed(Local<Context> context) {
ApiTestFuzzer::Fuzz();
return false;
}
THREADED_TEST(AllowCodeGenFromStrings) {
v8::HandleScope scope;
LocalContext context;
// eval and the Function constructor allowed by default.
CHECK(context->IsCodeGenerationFromStringsAllowed());
CheckCodeGenerationAllowed();
// Disallow eval and the Function constructor.
context->AllowCodeGenerationFromStrings(false);
CHECK(!context->IsCodeGenerationFromStringsAllowed());
CheckCodeGenerationDisallowed();
// Allow again.
context->AllowCodeGenerationFromStrings(true);
CheckCodeGenerationAllowed();
// Disallow but setting a global callback that will allow the calls.
context->AllowCodeGenerationFromStrings(false);
V8::SetAllowCodeGenerationFromStringsCallback(&CodeGenerationAllowed);
CHECK(!context->IsCodeGenerationFromStringsAllowed());
CheckCodeGenerationAllowed();
// Set a callback that disallows the code generation.
V8::SetAllowCodeGenerationFromStringsCallback(&CodeGenerationDisallowed);
CHECK(!context->IsCodeGenerationFromStringsAllowed());
CheckCodeGenerationDisallowed();
}
TEST(SetErrorMessageForCodeGenFromStrings) {
v8::HandleScope scope;
LocalContext context;
TryCatch try_catch;
Handle<String> message = v8_str("Message") ;
Handle<String> expected_message = v8_str("Uncaught EvalError: Message");
V8::SetAllowCodeGenerationFromStringsCallback(&CodeGenerationDisallowed);
context->AllowCodeGenerationFromStrings(false);
context->SetErrorMessageForCodeGenerationFromStrings(message);
Handle<Value> result = CompileRun("eval('42')");
CHECK(result.IsEmpty());
CHECK(try_catch.HasCaught());
Handle<String> actual_message = try_catch.Message()->Get();
CHECK(expected_message->Equals(actual_message));
}
static v8::Handle<Value> NonObjectThis(const v8::Arguments& args) {
return v8::Undefined();
}
THREADED_TEST(CallAPIFunctionOnNonObject) {
v8::HandleScope scope;
LocalContext context;
Handle<FunctionTemplate> templ = v8::FunctionTemplate::New(NonObjectThis);
Handle<Function> function = templ->GetFunction();
context->Global()->Set(v8_str("f"), function);
TryCatch try_catch;
CompileRun("f.call(2)");
}
// Regression test for issue 1470.
THREADED_TEST(ReadOnlyIndexedProperties) {
v8::HandleScope scope;
Local<ObjectTemplate> templ = ObjectTemplate::New();
LocalContext context;
Local<v8::Object> obj = templ->NewInstance();
context->Global()->Set(v8_str("obj"), obj);
obj->Set(v8_str("1"), v8_str("DONT_CHANGE"), v8::ReadOnly);
obj->Set(v8_str("1"), v8_str("foobar"));
CHECK_EQ(v8_str("DONT_CHANGE"), obj->Get(v8_str("1")));
obj->Set(v8_num(2), v8_str("DONT_CHANGE"), v8::ReadOnly);
obj->Set(v8_num(2), v8_str("foobar"));
CHECK_EQ(v8_str("DONT_CHANGE"), obj->Get(v8_num(2)));
// Test non-smi case.
obj->Set(v8_str("2000000000"), v8_str("DONT_CHANGE"), v8::ReadOnly);
obj->Set(v8_str("2000000000"), v8_str("foobar"));
CHECK_EQ(v8_str("DONT_CHANGE"), obj->Get(v8_str("2000000000")));
}
THREADED_TEST(Regress1516) {
v8::HandleScope scope;
LocalContext context;
{ v8::HandleScope temp_scope;
CompileRun("({'a': 0})");
}
int elements;
{ i::MapCache* map_cache =
i::MapCache::cast(i::Isolate::Current()->context()->map_cache());
elements = map_cache->NumberOfElements();
CHECK_LE(1, elements);
}
i::Isolate::Current()->heap()->CollectAllGarbage(
i::Heap::kAbortIncrementalMarkingMask);
{ i::Object* raw_map_cache = i::Isolate::Current()->context()->map_cache();
if (raw_map_cache != i::Isolate::Current()->heap()->undefined_value()) {
i::MapCache* map_cache = i::MapCache::cast(raw_map_cache);
CHECK_GT(elements, map_cache->NumberOfElements());
}
}
}
static bool BlockProtoNamedSecurityTestCallback(Local<v8::Object> global,
Local<Value> name,
v8::AccessType type,
Local<Value> data) {
// Only block read access to __proto__.
if (type == v8::ACCESS_GET &&
name->IsString() &&
name->ToString()->Length() == 9 &&
name->ToString()->Utf8Length() == 9) {
char buffer[10];
CHECK_EQ(10, name->ToString()->WriteUtf8(buffer));
return strncmp(buffer, "__proto__", 9) != 0;
}
return true;
}
THREADED_TEST(Regress93759) {
HandleScope scope;
// Template for object with security check.
Local<ObjectTemplate> no_proto_template = v8::ObjectTemplate::New();
// We don't do indexing, so any callback can be used for that.
no_proto_template->SetAccessCheckCallbacks(
BlockProtoNamedSecurityTestCallback,
IndexedSecurityTestCallback);
// Templates for objects with hidden prototypes and possibly security check.
Local<FunctionTemplate> hidden_proto_template = v8::FunctionTemplate::New();
hidden_proto_template->SetHiddenPrototype(true);
Local<FunctionTemplate> protected_hidden_proto_template =
v8::FunctionTemplate::New();
protected_hidden_proto_template->InstanceTemplate()->SetAccessCheckCallbacks(
BlockProtoNamedSecurityTestCallback,
IndexedSecurityTestCallback);
protected_hidden_proto_template->SetHiddenPrototype(true);
// Context for "foreign" objects used in test.
Persistent<Context> context = v8::Context::New();
context->Enter();
// Plain object, no security check.
Local<Object> simple_object = Object::New();
// Object with explicit security check.
Local<Object> protected_object =
no_proto_template->NewInstance();
// JSGlobalProxy object, always have security check.
Local<Object> proxy_object =
context->Global();
// Global object, the prototype of proxy_object. No security checks.
Local<Object> global_object =
proxy_object->GetPrototype()->ToObject();
// Hidden prototype without security check.
Local<Object> hidden_prototype =
hidden_proto_template->GetFunction()->NewInstance();
Local<Object> object_with_hidden =
Object::New();
object_with_hidden->SetPrototype(hidden_prototype);
// Hidden prototype with security check on the hidden prototype.
Local<Object> protected_hidden_prototype =
protected_hidden_proto_template->GetFunction()->NewInstance();
Local<Object> object_with_protected_hidden =
Object::New();
object_with_protected_hidden->SetPrototype(protected_hidden_prototype);
context->Exit();
// Template for object for second context. Values to test are put on it as
// properties.
Local<ObjectTemplate> global_template = ObjectTemplate::New();
global_template->Set(v8_str("simple"), simple_object);
global_template->Set(v8_str("protected"), protected_object);
global_template->Set(v8_str("global"), global_object);
global_template->Set(v8_str("proxy"), proxy_object);
global_template->Set(v8_str("hidden"), object_with_hidden);
global_template->Set(v8_str("phidden"), object_with_protected_hidden);
LocalContext context2(NULL, global_template);
Local<Value> result1 = CompileRun("Object.getPrototypeOf(simple)");
CHECK(result1->Equals(simple_object->GetPrototype()));
Local<Value> result2 = CompileRun("Object.getPrototypeOf(protected)");
CHECK(result2->Equals(Undefined()));
Local<Value> result3 = CompileRun("Object.getPrototypeOf(global)");
CHECK(result3->Equals(global_object->GetPrototype()));
Local<Value> result4 = CompileRun("Object.getPrototypeOf(proxy)");
CHECK(result4->Equals(Undefined()));
Local<Value> result5 = CompileRun("Object.getPrototypeOf(hidden)");
CHECK(result5->Equals(
object_with_hidden->GetPrototype()->ToObject()->GetPrototype()));
Local<Value> result6 = CompileRun("Object.getPrototypeOf(phidden)");
CHECK(result6->Equals(Undefined()));
context.Dispose();
}
THREADED_TEST(Regress125988) {
v8::HandleScope scope;
Handle<FunctionTemplate> intercept = FunctionTemplate::New();
AddInterceptor(intercept, EmptyInterceptorGetter, EmptyInterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Intercept"), intercept->GetFunction());
CompileRun("var a = new Object();"
"var b = new Intercept();"
"var c = new Object();"
"c.__proto__ = b;"
"b.__proto__ = a;"
"a.x = 23;"
"for (var i = 0; i < 3; i++) c.x;");
ExpectBoolean("c.hasOwnProperty('x')", false);
ExpectInt32("c.x", 23);
CompileRun("a.y = 42;"
"for (var i = 0; i < 3; i++) c.x;");
ExpectBoolean("c.hasOwnProperty('x')", false);
ExpectInt32("c.x", 23);
ExpectBoolean("c.hasOwnProperty('y')", false);
ExpectInt32("c.y", 42);
}
static void TestReceiver(Local<Value> expected_result,
Local<Value> expected_receiver,
const char* code) {
Local<Value> result = CompileRun(code);
CHECK(result->IsObject());
CHECK(expected_receiver->Equals(result->ToObject()->Get(1)));
CHECK(expected_result->Equals(result->ToObject()->Get(0)));
}
THREADED_TEST(ForeignFunctionReceiver) {
HandleScope scope;
// Create two contexts with different "id" properties ('i' and 'o').
// Call a function both from its own context and from a the foreign
// context, and see what "this" is bound to (returning both "this"
// and "this.id" for comparison).
Persistent<Context> foreign_context = v8::Context::New();
foreign_context->Enter();
Local<Value> foreign_function =
CompileRun("function func() { return { 0: this.id, "
" 1: this, "
" toString: function() { "
" return this[0];"
" }"
" };"
"}"
"var id = 'i';"
"func;");
CHECK(foreign_function->IsFunction());
foreign_context->Exit();
LocalContext context;
Local<String> password = v8_str("Password");
// Don't get hit by security checks when accessing foreign_context's
// global receiver (aka. global proxy).
context->SetSecurityToken(password);
foreign_context->SetSecurityToken(password);
Local<String> i = v8_str("i");
Local<String> o = v8_str("o");
Local<String> id = v8_str("id");
CompileRun("function ownfunc() { return { 0: this.id, "
" 1: this, "
" toString: function() { "
" return this[0];"
" }"
" };"
"}"
"var id = 'o';"
"ownfunc");
context->Global()->Set(v8_str("func"), foreign_function);
// Sanity check the contexts.
CHECK(i->Equals(foreign_context->Global()->Get(id)));
CHECK(o->Equals(context->Global()->Get(id)));
// Checking local function's receiver.
// Calling function using its call/apply methods.
TestReceiver(o, context->Global(), "ownfunc.call()");
TestReceiver(o, context->Global(), "ownfunc.apply()");
// Making calls through built-in functions.
TestReceiver(o, context->Global(), "[1].map(ownfunc)[0]");
CHECK(o->Equals(CompileRun("'abcbd'.replace(/b/,ownfunc)[1]")));
CHECK(o->Equals(CompileRun("'abcbd'.replace(/b/g,ownfunc)[1]")));
CHECK(o->Equals(CompileRun("'abcbd'.replace(/b/g,ownfunc)[3]")));
// Calling with environment record as base.
TestReceiver(o, context->Global(), "ownfunc()");
// Calling with no base.
TestReceiver(o, context->Global(), "(1,ownfunc)()");
// Checking foreign function return value.
// Calling function using its call/apply methods.
TestReceiver(i, foreign_context->Global(), "func.call()");
TestReceiver(i, foreign_context->Global(), "func.apply()");
// Calling function using another context's call/apply methods.
TestReceiver(i, foreign_context->Global(),
"Function.prototype.call.call(func)");
TestReceiver(i, foreign_context->Global(),
"Function.prototype.call.apply(func)");
TestReceiver(i, foreign_context->Global(),
"Function.prototype.apply.call(func)");
TestReceiver(i, foreign_context->Global(),
"Function.prototype.apply.apply(func)");
// Making calls through built-in functions.
TestReceiver(i, foreign_context->Global(), "[1].map(func)[0]");
// ToString(func()) is func()[0], i.e., the returned this.id.
CHECK(i->Equals(CompileRun("'abcbd'.replace(/b/,func)[1]")));
CHECK(i->Equals(CompileRun("'abcbd'.replace(/b/g,func)[1]")));
CHECK(i->Equals(CompileRun("'abcbd'.replace(/b/g,func)[3]")));
// TODO(1547): Make the following also return "i".
// Calling with environment record as base.
TestReceiver(o, context->Global(), "func()");
// Calling with no base.
TestReceiver(o, context->Global(), "(1,func)()");
foreign_context.Dispose();
}
uint8_t callback_fired = 0;
void CallCompletedCallback1() {
i::OS::Print("Firing callback 1.\n");
callback_fired ^= 1; // Toggle first bit.
}
void CallCompletedCallback2() {
i::OS::Print("Firing callback 2.\n");
callback_fired ^= 2; // Toggle second bit.
}
Handle<Value> RecursiveCall(const Arguments& args) {
int32_t level = args[0]->Int32Value();
if (level < 3) {
level++;
i::OS::Print("Entering recursion level %d.\n", level);
char script[64];
i::Vector<char> script_vector(script, sizeof(script));
i::OS::SNPrintF(script_vector, "recursion(%d)", level);
CompileRun(script_vector.start());
i::OS::Print("Leaving recursion level %d.\n", level);
CHECK_EQ(0, callback_fired);
} else {
i::OS::Print("Recursion ends.\n");
CHECK_EQ(0, callback_fired);
}
return Undefined();
}
TEST(CallCompletedCallback) {
v8::HandleScope scope;
LocalContext env;
v8::Handle<v8::FunctionTemplate> recursive_runtime =
v8::FunctionTemplate::New(RecursiveCall);
env->Global()->Set(v8_str("recursion"),
recursive_runtime->GetFunction());
// Adding the same callback a second time has no effect.
v8::V8::AddCallCompletedCallback(CallCompletedCallback1);
v8::V8::AddCallCompletedCallback(CallCompletedCallback1);
v8::V8::AddCallCompletedCallback(CallCompletedCallback2);
i::OS::Print("--- Script (1) ---\n");
Local<Script> script =
v8::Script::Compile(v8::String::New("recursion(0)"));
script->Run();
CHECK_EQ(3, callback_fired);
i::OS::Print("\n--- Script (2) ---\n");
callback_fired = 0;
v8::V8::RemoveCallCompletedCallback(CallCompletedCallback1);
script->Run();
CHECK_EQ(2, callback_fired);
i::OS::Print("\n--- Function ---\n");
callback_fired = 0;
Local<Function> recursive_function =
Local<Function>::Cast(env->Global()->Get(v8_str("recursion")));
v8::Handle<Value> args[] = { v8_num(0) };
recursive_function->Call(env->Global(), 1, args);
CHECK_EQ(2, callback_fired);
}
void CallCompletedCallbackNoException() {
v8::HandleScope scope;
CompileRun("1+1;");
}
void CallCompletedCallbackException() {
v8::HandleScope scope;
CompileRun("throw 'second exception';");
}
TEST(CallCompletedCallbackOneException) {
v8::HandleScope scope;
LocalContext env;
v8::V8::AddCallCompletedCallback(CallCompletedCallbackNoException);
CompileRun("throw 'exception';");
}
TEST(CallCompletedCallbackTwoExceptions) {
v8::HandleScope scope;
LocalContext env;
v8::V8::AddCallCompletedCallback(CallCompletedCallbackException);
CompileRun("throw 'first exception';");
}
static int probes_counter = 0;
static int misses_counter = 0;
static int updates_counter = 0;
static int* LookupCounter(const char* name) {
if (strcmp(name, "c:V8.MegamorphicStubCacheProbes") == 0) {
return &probes_counter;
} else if (strcmp(name, "c:V8.MegamorphicStubCacheMisses") == 0) {
return &misses_counter;
} else if (strcmp(name, "c:V8.MegamorphicStubCacheUpdates") == 0) {
return &updates_counter;
}
return NULL;
}
static const char* kMegamorphicTestProgram =
"function ClassA() { };"
"function ClassB() { };"
"ClassA.prototype.foo = function() { };"
"ClassB.prototype.foo = function() { };"
"function fooify(obj) { obj.foo(); };"
"var a = new ClassA();"
"var b = new ClassB();"
"for (var i = 0; i < 10000; i++) {"
" fooify(a);"
" fooify(b);"
"}";
static void StubCacheHelper(bool primary) {
V8::SetCounterFunction(LookupCounter);
USE(kMegamorphicTestProgram);
#ifdef DEBUG
i::FLAG_native_code_counters = true;
if (primary) {
i::FLAG_test_primary_stub_cache = true;
} else {
i::FLAG_test_secondary_stub_cache = true;
}
i::FLAG_crankshaft = false;
v8::HandleScope scope;
LocalContext env;
int initial_probes = probes_counter;
int initial_misses = misses_counter;
int initial_updates = updates_counter;
CompileRun(kMegamorphicTestProgram);
int probes = probes_counter - initial_probes;
int misses = misses_counter - initial_misses;
int updates = updates_counter - initial_updates;
CHECK_LT(updates, 10);
CHECK_LT(misses, 10);
CHECK_GE(probes, 10000);
#endif
}
TEST(SecondaryStubCache) {
StubCacheHelper(true);
}
TEST(PrimaryStubCache) {
StubCacheHelper(false);
}
static int fatal_error_callback_counter = 0;
static void CountingErrorCallback(const char* location, const char* message) {
printf("CountingErrorCallback(\"%s\", \"%s\")\n", location, message);
fatal_error_callback_counter++;
}
TEST(StaticGetters) {
v8::HandleScope scope;
LocalContext context;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
i::Handle<i::Object> undefined_value = FACTORY->undefined_value();
CHECK(*v8::Utils::OpenHandle(*v8::Undefined()) == *undefined_value);
CHECK(*v8::Utils::OpenHandle(*v8::Undefined(isolate)) == *undefined_value);
i::Handle<i::Object> null_value = FACTORY->null_value();
CHECK(*v8::Utils::OpenHandle(*v8::Null()) == *null_value);
CHECK(*v8::Utils::OpenHandle(*v8::Null(isolate)) == *null_value);
i::Handle<i::Object> true_value = FACTORY->true_value();
CHECK(*v8::Utils::OpenHandle(*v8::True()) == *true_value);
CHECK(*v8::Utils::OpenHandle(*v8::True(isolate)) == *true_value);
i::Handle<i::Object> false_value = FACTORY->false_value();
CHECK(*v8::Utils::OpenHandle(*v8::False()) == *false_value);
CHECK(*v8::Utils::OpenHandle(*v8::False(isolate)) == *false_value);
// Test after-death behavior.
CHECK(i::Internals::IsInitialized(isolate));
CHECK_EQ(0, fatal_error_callback_counter);
v8::V8::SetFatalErrorHandler(CountingErrorCallback);
v8::Utils::ReportApiFailure("StaticGetters()", "Kill V8");
i::Isolate::Current()->TearDown();
CHECK(!i::Internals::IsInitialized(isolate));
CHECK_EQ(1, fatal_error_callback_counter);
CHECK(v8::Undefined().IsEmpty());
CHECK_EQ(2, fatal_error_callback_counter);
CHECK(v8::Undefined(isolate).IsEmpty());
CHECK_EQ(3, fatal_error_callback_counter);
CHECK(v8::Null().IsEmpty());
CHECK_EQ(4, fatal_error_callback_counter);
CHECK(v8::Null(isolate).IsEmpty());
CHECK_EQ(5, fatal_error_callback_counter);
CHECK(v8::True().IsEmpty());
CHECK_EQ(6, fatal_error_callback_counter);
CHECK(v8::True(isolate).IsEmpty());
CHECK_EQ(7, fatal_error_callback_counter);
CHECK(v8::False().IsEmpty());
CHECK_EQ(8, fatal_error_callback_counter);
CHECK(v8::False(isolate).IsEmpty());
CHECK_EQ(9, fatal_error_callback_counter);
}
TEST(IsolateEmbedderData) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
CHECK_EQ(NULL, isolate->GetData());
CHECK_EQ(NULL, ISOLATE->GetData());
static void* data1 = reinterpret_cast<void*>(0xacce55ed);
isolate->SetData(data1);
CHECK_EQ(data1, isolate->GetData());
CHECK_EQ(data1, ISOLATE->GetData());
static void* data2 = reinterpret_cast<void*>(0xdecea5ed);
ISOLATE->SetData(data2);
CHECK_EQ(data2, isolate->GetData());
CHECK_EQ(data2, ISOLATE->GetData());
ISOLATE->TearDown();
CHECK_EQ(data2, isolate->GetData());
CHECK_EQ(data2, ISOLATE->GetData());
}
TEST(StringEmpty) {
v8::HandleScope scope;
LocalContext context;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
i::Handle<i::Object> empty_string = FACTORY->empty_symbol();
CHECK(*v8::Utils::OpenHandle(*v8::String::Empty()) == *empty_string);
CHECK(*v8::Utils::OpenHandle(*v8::String::Empty(isolate)) == *empty_string);
// Test after-death behavior.
CHECK(i::Internals::IsInitialized(isolate));
CHECK_EQ(0, fatal_error_callback_counter);
v8::V8::SetFatalErrorHandler(CountingErrorCallback);
v8::Utils::ReportApiFailure("StringEmpty()", "Kill V8");
i::Isolate::Current()->TearDown();
CHECK(!i::Internals::IsInitialized(isolate));
CHECK_EQ(1, fatal_error_callback_counter);
CHECK(v8::String::Empty().IsEmpty());
CHECK_EQ(2, fatal_error_callback_counter);
CHECK(v8::String::Empty(isolate).IsEmpty());
CHECK_EQ(3, fatal_error_callback_counter);
}
static int instance_checked_getter_count = 0;
static Handle<Value> InstanceCheckedGetter(Local<String> name,
const AccessorInfo& info) {
CHECK_EQ(name, v8_str("foo"));
instance_checked_getter_count++;
return v8_num(11);
}
static int instance_checked_setter_count = 0;
static void InstanceCheckedSetter(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
CHECK_EQ(name, v8_str("foo"));
CHECK_EQ(value, v8_num(23));
instance_checked_setter_count++;
}
static void CheckInstanceCheckedResult(int getters,
int setters,
bool expects_callbacks,
TryCatch* try_catch) {
if (expects_callbacks) {
CHECK(!try_catch->HasCaught());
CHECK_EQ(getters, instance_checked_getter_count);
CHECK_EQ(setters, instance_checked_setter_count);
} else {
CHECK(try_catch->HasCaught());
CHECK_EQ(0, instance_checked_getter_count);
CHECK_EQ(0, instance_checked_setter_count);
}
try_catch->Reset();
}
static void CheckInstanceCheckedAccessors(bool expects_callbacks) {
instance_checked_getter_count = 0;
instance_checked_setter_count = 0;
TryCatch try_catch;
// Test path through generic runtime code.
CompileRun("obj.foo");
CheckInstanceCheckedResult(1, 0, expects_callbacks, &try_catch);
CompileRun("obj.foo = 23");
CheckInstanceCheckedResult(1, 1, expects_callbacks, &try_catch);
// Test path through generated LoadIC and StoredIC.
CompileRun("function test_get(o) { o.foo; }"
"test_get(obj);");
CheckInstanceCheckedResult(2, 1, expects_callbacks, &try_catch);
CompileRun("test_get(obj);");
CheckInstanceCheckedResult(3, 1, expects_callbacks, &try_catch);
CompileRun("test_get(obj);");
CheckInstanceCheckedResult(4, 1, expects_callbacks, &try_catch);
CompileRun("function test_set(o) { o.foo = 23; }"
"test_set(obj);");
CheckInstanceCheckedResult(4, 2, expects_callbacks, &try_catch);
CompileRun("test_set(obj);");
CheckInstanceCheckedResult(4, 3, expects_callbacks, &try_catch);
CompileRun("test_set(obj);");
CheckInstanceCheckedResult(4, 4, expects_callbacks, &try_catch);
// Test path through optimized code.
CompileRun("%OptimizeFunctionOnNextCall(test_get);"
"test_get(obj);");
CheckInstanceCheckedResult(5, 4, expects_callbacks, &try_catch);
CompileRun("%OptimizeFunctionOnNextCall(test_set);"
"test_set(obj);");
CheckInstanceCheckedResult(5, 5, expects_callbacks, &try_catch);
// Cleanup so that closures start out fresh in next check.
CompileRun("%DeoptimizeFunction(test_get);"
"%ClearFunctionTypeFeedback(test_get);"
"%DeoptimizeFunction(test_set);"
"%ClearFunctionTypeFeedback(test_set);");
}
THREADED_TEST(InstanceCheckOnInstanceAccessor) {
v8::internal::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
LocalContext context;
Local<FunctionTemplate> templ = FunctionTemplate::New();
Local<ObjectTemplate> inst = templ->InstanceTemplate();
inst->SetAccessor(v8_str("foo"),
InstanceCheckedGetter, InstanceCheckedSetter,
Handle<Value>(),
v8::DEFAULT,
v8::None,
v8::AccessorSignature::New(templ));
context->Global()->Set(v8_str("f"), templ->GetFunction());
printf("Testing positive ...\n");
CompileRun("var obj = new f();");
CHECK(templ->HasInstance(context->Global()->Get(v8_str("obj"))));
CheckInstanceCheckedAccessors(true);
printf("Testing negative ...\n");
CompileRun("var obj = {};"
"obj.__proto__ = new f();");
CHECK(!templ->HasInstance(context->Global()->Get(v8_str("obj"))));
CheckInstanceCheckedAccessors(false);
}
THREADED_TEST(InstanceCheckOnInstanceAccessorWithInterceptor) {
v8::internal::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
LocalContext context;
Local<FunctionTemplate> templ = FunctionTemplate::New();
Local<ObjectTemplate> inst = templ->InstanceTemplate();
AddInterceptor(templ, EmptyInterceptorGetter, EmptyInterceptorSetter);
inst->SetAccessor(v8_str("foo"),
InstanceCheckedGetter, InstanceCheckedSetter,
Handle<Value>(),
v8::DEFAULT,
v8::None,
v8::AccessorSignature::New(templ));
context->Global()->Set(v8_str("f"), templ->GetFunction());
printf("Testing positive ...\n");
CompileRun("var obj = new f();");
CHECK(templ->HasInstance(context->Global()->Get(v8_str("obj"))));
CheckInstanceCheckedAccessors(true);
printf("Testing negative ...\n");
CompileRun("var obj = {};"
"obj.__proto__ = new f();");
CHECK(!templ->HasInstance(context->Global()->Get(v8_str("obj"))));
CheckInstanceCheckedAccessors(false);
}
THREADED_TEST(InstanceCheckOnPrototypeAccessor) {
v8::internal::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
LocalContext context;
Local<FunctionTemplate> templ = FunctionTemplate::New();
Local<ObjectTemplate> proto = templ->PrototypeTemplate();
proto->SetAccessor(v8_str("foo"),
InstanceCheckedGetter, InstanceCheckedSetter,
Handle<Value>(),
v8::DEFAULT,
v8::None,
v8::AccessorSignature::New(templ));
context->Global()->Set(v8_str("f"), templ->GetFunction());
printf("Testing positive ...\n");
CompileRun("var obj = new f();");
CHECK(templ->HasInstance(context->Global()->Get(v8_str("obj"))));
CheckInstanceCheckedAccessors(true);
printf("Testing negative ...\n");
CompileRun("var obj = {};"
"obj.__proto__ = new f();");
CHECK(!templ->HasInstance(context->Global()->Get(v8_str("obj"))));
CheckInstanceCheckedAccessors(false);
printf("Testing positive with modified prototype chain ...\n");
CompileRun("var obj = new f();"
"var pro = {};"
"pro.__proto__ = obj.__proto__;"
"obj.__proto__ = pro;");
CHECK(templ->HasInstance(context->Global()->Get(v8_str("obj"))));
CheckInstanceCheckedAccessors(true);
}
TEST(TryFinallyMessage) {
v8::HandleScope scope;
LocalContext context;
{
// Test that the original error message is not lost if there is a
// recursive call into Javascript is done in the finally block, e.g. to
// initialize an IC. (crbug.com/129171)
TryCatch try_catch;
const char* trigger_ic =
"try { \n"
" throw new Error('test'); \n"
"} finally { \n"
" var x = 0; \n"
" x++; \n" // Trigger an IC initialization here.
"} \n";
CompileRun(trigger_ic);
CHECK(try_catch.HasCaught());
Local<Message> message = try_catch.Message();
CHECK(!message.IsEmpty());
CHECK_EQ(2, message->GetLineNumber());
}
{
// Test that the original exception message is indeed overwritten if
// a new error is thrown in the finally block.
TryCatch try_catch;
const char* throw_again =
"try { \n"
" throw new Error('test'); \n"
"} finally { \n"
" var x = 0; \n"
" x++; \n"
" throw new Error('again'); \n" // This is the new uncaught error.
"} \n";
CompileRun(throw_again);
CHECK(try_catch.HasCaught());
Local<Message> message = try_catch.Message();
CHECK(!message.IsEmpty());
CHECK_EQ(6, message->GetLineNumber());
}
}
static void Helper137002(bool do_store,
bool polymorphic,
bool remove_accessor,
bool interceptor) {
LocalContext context;
Local<ObjectTemplate> templ = ObjectTemplate::New();
if (interceptor) {
templ->SetNamedPropertyHandler(FooGetInterceptor, FooSetInterceptor);
} else {
templ->SetAccessor(v8_str("foo"),
GetterWhichReturns42,
SetterWhichSetsYOnThisTo23);
}
context->Global()->Set(v8_str("obj"), templ->NewInstance());
// Turn monomorphic on slow object with native accessor, then turn
// polymorphic, finally optimize to create negative lookup and fail.
CompileRun(do_store ?
"function f(x) { x.foo = void 0; }" :
"function f(x) { return x.foo; }");
CompileRun("obj.y = void 0;");
if (!interceptor) {
CompileRun("%OptimizeObjectForAddingMultipleProperties(obj, 1);");
}
CompileRun("obj.__proto__ = null;"
"f(obj); f(obj); f(obj);");
if (polymorphic) {
CompileRun("f({});");
}
CompileRun("obj.y = void 0;"
"%OptimizeFunctionOnNextCall(f);");
if (remove_accessor) {
CompileRun("delete obj.foo;");
}
CompileRun("var result = f(obj);");
if (do_store) {
CompileRun("result = obj.y;");
}
if (remove_accessor && !interceptor) {
CHECK(context->Global()->Get(v8_str("result"))->IsUndefined());
} else {
CHECK_EQ(do_store ? 23 : 42,
context->Global()->Get(v8_str("result"))->Int32Value());
}
}
THREADED_TEST(Regress137002a) {
i::FLAG_allow_natives_syntax = true;
i::FLAG_compilation_cache = false;
v8::HandleScope scope;
for (int i = 0; i < 16; i++) {
Helper137002(i & 8, i & 4, i & 2, i & 1);
}
}
THREADED_TEST(Regress137002b) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
LocalContext context;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessor(v8_str("foo"),
GetterWhichReturns42,
SetterWhichSetsYOnThisTo23);
context->Global()->Set(v8_str("obj"), templ->NewInstance());
// Turn monomorphic on slow object with native accessor, then just
// delete the property and fail.
CompileRun("function load(x) { return x.foo; }"
"function store(x) { x.foo = void 0; }"
"function keyed_load(x, key) { return x[key]; }"
// Second version of function has a different source (add void 0)
// so that it does not share code with the first version. This
// ensures that the ICs are monomorphic.
"function load2(x) { void 0; return x.foo; }"
"function store2(x) { void 0; x.foo = void 0; }"
"function keyed_load2(x, key) { void 0; return x[key]; }"
"obj.y = void 0;"
"obj.__proto__ = null;"
"var subobj = {};"
"subobj.y = void 0;"
"subobj.__proto__ = obj;"
"%OptimizeObjectForAddingMultipleProperties(obj, 1);"
// Make the ICs monomorphic.
"load(obj); load(obj);"
"load2(subobj); load2(subobj);"
"store(obj); store(obj);"
"store2(subobj); store2(subobj);"
"keyed_load(obj, 'foo'); keyed_load(obj, 'foo');"
"keyed_load2(subobj, 'foo'); keyed_load2(subobj, 'foo');"
// Actually test the shiny new ICs and better not crash. This
// serves as a regression test for issue 142088 as well.
"load(obj);"
"load2(subobj);"
"store(obj);"
"store2(subobj);"
"keyed_load(obj, 'foo');"
"keyed_load2(subobj, 'foo');"
// Delete the accessor. It better not be called any more now.
"delete obj.foo;"
"obj.y = void 0;"
"subobj.y = void 0;"
"var load_result = load(obj);"
"var load_result2 = load2(subobj);"
"var keyed_load_result = keyed_load(obj, 'foo');"
"var keyed_load_result2 = keyed_load2(subobj, 'foo');"
"store(obj);"
"store2(subobj);"
"var y_from_obj = obj.y;"
"var y_from_subobj = subobj.y;");
CHECK(context->Global()->Get(v8_str("load_result"))->IsUndefined());
CHECK(context->Global()->Get(v8_str("load_result2"))->IsUndefined());
CHECK(context->Global()->Get(v8_str("keyed_load_result"))->IsUndefined());
CHECK(context->Global()->Get(v8_str("keyed_load_result2"))->IsUndefined());
CHECK(context->Global()->Get(v8_str("y_from_obj"))->IsUndefined());
CHECK(context->Global()->Get(v8_str("y_from_subobj"))->IsUndefined());
}
THREADED_TEST(Regress142088) {
i::FLAG_allow_natives_syntax = true;
v8::HandleScope scope;
LocalContext context;
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->SetAccessor(v8_str("foo"),
GetterWhichReturns42,
SetterWhichSetsYOnThisTo23);
context->Global()->Set(v8_str("obj"), templ->NewInstance());
CompileRun("function load(x) { return x.foo; }"
"var o = Object.create(obj);"
"%OptimizeObjectForAddingMultipleProperties(obj, 1);"
"load(o); load(o); load(o); load(o);");
}
THREADED_TEST(Regress137496) {
i::FLAG_expose_gc = true;
v8::HandleScope scope;
LocalContext context;
// Compile a try-finally clause where the finally block causes a GC
// while there still is a message pending for external reporting.
TryCatch try_catch;
try_catch.SetVerbose(true);
CompileRun("try { throw new Error(); } finally { gc(); }");
CHECK(try_catch.HasCaught());
}
THREADED_TEST(Regress149912) {
v8::HandleScope scope;
LocalContext context;
Handle<FunctionTemplate> templ = FunctionTemplate::New();
AddInterceptor(templ, EmptyInterceptorGetter, EmptyInterceptorSetter);
context->Global()->Set(v8_str("Bug"), templ->GetFunction());
CompileRun("Number.prototype.__proto__ = new Bug; var x = 0; x.foo();");
}
THREADED_TEST(Regress157124) {
v8::HandleScope scope;
LocalContext context;
Local<ObjectTemplate> templ = ObjectTemplate::New();
Local<Object> obj = templ->NewInstance();
obj->GetIdentityHash();
obj->DeleteHiddenValue(v8_str("Bug"));
}
#ifndef WIN32
class ThreadInterruptTest {
public:
ThreadInterruptTest() : sem_(NULL), sem_value_(0) { }
~ThreadInterruptTest() { delete sem_; }
void RunTest() {
sem_ = i::OS::CreateSemaphore(0);
InterruptThread i_thread(this);
i_thread.Start();
sem_->Wait();
CHECK_EQ(kExpectedValue, sem_value_);
}
private:
static const int kExpectedValue = 1;
class InterruptThread : public i::Thread {
public:
explicit InterruptThread(ThreadInterruptTest* test)
: Thread("InterruptThread"), test_(test) {}
virtual void Run() {
struct sigaction action;
// Ensure that we'll enter waiting condition
i::OS::Sleep(100);
// Setup signal handler
memset(&action, 0, sizeof(action));
action.sa_handler = SignalHandler;
sigaction(SIGCHLD, &action, NULL);
// Send signal
kill(getpid(), SIGCHLD);
// Ensure that if wait has returned because of error
i::OS::Sleep(100);
// Set value and signal semaphore
test_->sem_value_ = 1;
test_->sem_->Signal();
}
static void SignalHandler(int signal) {
}
private:
ThreadInterruptTest* test_;
};
i::Semaphore* sem_;
volatile int sem_value_;
};
THREADED_TEST(SemaphoreInterruption) {
ThreadInterruptTest().RunTest();
}
#endif // WIN32