7d4e3d35f5
This reverts commit 142dd775b4
.
Reason for revert: TSAN breaks: https://ci.chromium.org/ui/p/v8/builders/ci/V8%20Linux64%20TSAN%20-%20stress-incremental-marking/6113/overview
Original change's description:
> cppgc-js,heap: Implement snapshots for embedder fields
>
> https://crrev.com/c/3293410 added concurrent processing of C++ objects
> found through V8 embedder fields. The CL missed that those embedder
> fields are not read atomically from JS objects. The problem is that
> embedder fields are only aligned to kTaggedSize on builds with pointer
> compression and are as such mis-aligned for atomic ops. This is not a
> problem for on-heap values as the upper 32bits are anyways computed
> from the cage. Is is a problem for generic C++ values though, as they
> are used with Oilpan.
>
> This CL adds the standard marker snapshot protocol for embedder fields.
>
> Marker:
> 1. Snapshot embedder fields
> 2. Try to mark host object
> 3. On success: process snapshot
>
> Main thread:
> 1. On setting embedder fields mark the object black first
> 2. Emit a write barrier for the embedder fields
>
> This will get simpler with the heap sandbox that uses a separate table
> for embedder fields. Once the sandbox is the default configuration, we
> can use it as dependency for the concurrent fast path.
>
> Bug: chromium:1285706
> Change-Id: I6b975ea561be08cda840ef0dd27a11627de93900
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3380983
> Reviewed-by: Dominik Inführ <dinfuehr@chromium.org>
> Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#78604}
Bug: chromium:1285706
Change-Id: If1976c0356f450fc068aa4dcc39fb9a0d5417a40
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3386598
Auto-Submit: Leszek Swirski <leszeks@chromium.org>
Owners-Override: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Cr-Commit-Position: refs/heads/main@{#78605}
29687 lines
1.0 MiB
29687 lines
1.0 MiB
// 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 "test/cctest/test-api.h"
|
|
|
|
#include <climits>
|
|
#include <csignal>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
#if V8_OS_POSIX
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "include/v8-date.h"
|
|
#include "include/v8-extension.h"
|
|
#include "include/v8-fast-api-calls.h"
|
|
#include "include/v8-function.h"
|
|
#include "include/v8-initialization.h"
|
|
#include "include/v8-json.h"
|
|
#include "include/v8-locker.h"
|
|
#include "include/v8-primitive-object.h"
|
|
#include "include/v8-regexp.h"
|
|
#include "include/v8-util.h"
|
|
#include "src/api/api-inl.h"
|
|
#include "src/base/bounds.h"
|
|
#include "src/base/overflowing-math.h"
|
|
#include "src/base/platform/platform.h"
|
|
#include "src/base/strings.h"
|
|
#include "src/codegen/compilation-cache.h"
|
|
#include "src/debug/debug.h"
|
|
#include "src/execution/arguments.h"
|
|
#include "src/execution/execution.h"
|
|
#include "src/execution/futex-emulation.h"
|
|
#include "src/execution/protectors-inl.h"
|
|
#include "src/execution/vm-state.h"
|
|
#include "src/handles/global-handles.h"
|
|
#include "src/heap/heap-inl.h"
|
|
#include "src/heap/incremental-marking.h"
|
|
#include "src/heap/local-allocator.h"
|
|
#include "src/logging/metrics.h"
|
|
#include "src/objects/feedback-vector-inl.h"
|
|
#include "src/objects/feedback-vector.h"
|
|
#include "src/objects/hash-table-inl.h"
|
|
#include "src/objects/js-array-buffer-inl.h"
|
|
#include "src/objects/js-array-inl.h"
|
|
#include "src/objects/js-promise-inl.h"
|
|
#include "src/objects/lookup.h"
|
|
#include "src/objects/map-updater.h"
|
|
#include "src/objects/module-inl.h"
|
|
#include "src/objects/objects-inl.h"
|
|
#include "src/objects/string-inl.h"
|
|
#include "src/objects/synthetic-module-inl.h"
|
|
#include "src/profiler/cpu-profiler.h"
|
|
#include "src/profiler/symbolizer.h"
|
|
#include "src/strings/unicode-inl.h"
|
|
#include "src/utils/utils.h"
|
|
#include "test/cctest/heap/heap-tester.h"
|
|
#include "test/cctest/heap/heap-utils.h"
|
|
#include "test/common/flag-utils.h"
|
|
|
|
#if V8_ENABLE_WEBASSEMBLY
|
|
#include "test/cctest/wasm/wasm-run-utils.h"
|
|
#include "test/common/wasm/test-signatures.h"
|
|
#include "test/common/wasm/wasm-macro-gen.h"
|
|
#endif // V8_ENABLE_WEBASSEMBLY
|
|
|
|
static const bool kLogThreading = false;
|
|
|
|
using ::v8::Array;
|
|
using ::v8::Boolean;
|
|
using ::v8::BooleanObject;
|
|
using ::v8::Context;
|
|
using ::v8::Extension;
|
|
using ::v8::FixedArray;
|
|
using ::v8::Function;
|
|
using ::v8::FunctionTemplate;
|
|
using ::v8::HandleScope;
|
|
using ::v8::Local;
|
|
using ::v8::Maybe;
|
|
using ::v8::Message;
|
|
using ::v8::MessageCallback;
|
|
using ::v8::Module;
|
|
using ::v8::Name;
|
|
using ::v8::None;
|
|
using ::v8::Object;
|
|
using ::v8::ObjectTemplate;
|
|
using ::v8::Persistent;
|
|
using ::v8::PropertyAttribute;
|
|
using ::v8::Script;
|
|
using ::v8::String;
|
|
using ::v8::Symbol;
|
|
using ::v8::TryCatch;
|
|
using ::v8::Undefined;
|
|
using ::v8::V8;
|
|
using ::v8::Value;
|
|
|
|
|
|
#define THREADED_PROFILED_TEST(Name) \
|
|
static void Test##Name(); \
|
|
TEST(Name##WithProfiler) { \
|
|
RunWithProfiler(&Test##Name); \
|
|
} \
|
|
THREADED_TEST(Name)
|
|
|
|
void RunWithProfiler(void (*test)()) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Local<v8::String> profile_name = v8_str("my_profile1");
|
|
v8::CpuProfiler* cpu_profiler = v8::CpuProfiler::New(env->GetIsolate());
|
|
cpu_profiler->StartProfiling(profile_name);
|
|
(*test)();
|
|
reinterpret_cast<i::CpuProfiler*>(cpu_profiler)->DeleteAllProfiles();
|
|
cpu_profiler->Dispose();
|
|
}
|
|
|
|
|
|
static int signature_callback_count;
|
|
static Local<Value> signature_expected_receiver;
|
|
static void IncrementingSignatureCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
signature_callback_count++;
|
|
CHECK(signature_expected_receiver->Equals(
|
|
args.GetIsolate()->GetCurrentContext(),
|
|
args.Holder())
|
|
.FromJust());
|
|
CHECK(signature_expected_receiver->Equals(
|
|
args.GetIsolate()->GetCurrentContext(),
|
|
args.This())
|
|
.FromJust());
|
|
v8::Local<v8::Array> result =
|
|
v8::Array::New(args.GetIsolate(), args.Length());
|
|
for (int i = 0; i < args.Length(); i++) {
|
|
CHECK(result->Set(args.GetIsolate()->GetCurrentContext(),
|
|
v8::Integer::New(args.GetIsolate(), i), args[i])
|
|
.FromJust());
|
|
}
|
|
args.GetReturnValue().Set(result);
|
|
}
|
|
|
|
|
|
static void Returns42(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(42);
|
|
}
|
|
|
|
// Tests that call v8::V8::Dispose() cannot be threaded.
|
|
UNINITIALIZED_TEST(InitializeAndDisposeOnce) {
|
|
CHECK(v8::V8::Initialize());
|
|
CHECK(v8::V8::Dispose());
|
|
}
|
|
|
|
|
|
// Tests that call v8::V8::Dispose() cannot be threaded.
|
|
UNINITIALIZED_TEST(InitializeAndDisposeMultiple) {
|
|
for (int i = 0; i < 3; ++i) CHECK(v8::V8::Dispose());
|
|
for (int i = 0; i < 3; ++i) CHECK(v8::V8::Initialize());
|
|
for (int i = 0; i < 3; ++i) CHECK(v8::V8::Dispose());
|
|
for (int i = 0; i < 3; ++i) CHECK(v8::V8::Initialize());
|
|
for (int i = 0; i < 3; ++i) CHECK(v8::V8::Dispose());
|
|
}
|
|
|
|
THREADED_TEST(Handles) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
Local<Context> local_env;
|
|
{
|
|
LocalContext env;
|
|
local_env = env.local();
|
|
}
|
|
|
|
// Local context should still be live.
|
|
CHECK(!local_env.IsEmpty());
|
|
local_env->Enter();
|
|
|
|
v8::Local<v8::Primitive> undef = v8::Undefined(CcTest::isolate());
|
|
CHECK(!undef.IsEmpty());
|
|
CHECK(undef->IsUndefined());
|
|
|
|
const char* source = "1 + 2 + 3";
|
|
Local<Script> script = v8_compile(source);
|
|
CHECK_EQ(6, v8_run_int32value(script));
|
|
|
|
local_env->Exit();
|
|
}
|
|
|
|
|
|
THREADED_TEST(IsolateOfContext) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
v8::Local<Context> env = Context::New(CcTest::isolate());
|
|
|
|
CHECK(!env->GetIsolate()->InContext());
|
|
CHECK(env->GetIsolate() == CcTest::isolate());
|
|
env->Enter();
|
|
CHECK(env->GetIsolate()->InContext());
|
|
CHECK(env->GetIsolate() == CcTest::isolate());
|
|
env->Exit();
|
|
CHECK(!env->GetIsolate()->InContext());
|
|
CHECK(env->GetIsolate() == CcTest::isolate());
|
|
}
|
|
|
|
static void TestSignatureLooped(const char* operation, Local<Value> receiver,
|
|
v8::Isolate* isolate) {
|
|
v8::base::ScopedVector<char> source(200);
|
|
v8::base::SNPrintF(source,
|
|
"for (var i = 0; i < 10; i++) {"
|
|
" %s"
|
|
"}",
|
|
operation);
|
|
signature_callback_count = 0;
|
|
signature_expected_receiver = receiver;
|
|
bool expected_to_throw = receiver.IsEmpty();
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun(source.begin());
|
|
CHECK_EQ(expected_to_throw, try_catch.HasCaught());
|
|
if (!expected_to_throw) {
|
|
CHECK_EQ(10, signature_callback_count);
|
|
} else {
|
|
CHECK(v8_str("TypeError: Illegal invocation")
|
|
->Equals(isolate->GetCurrentContext(),
|
|
try_catch.Exception()
|
|
->ToString(isolate->GetCurrentContext())
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
static void TestSignatureOptimized(const char* operation, Local<Value> receiver,
|
|
v8::Isolate* isolate) {
|
|
v8::base::ScopedVector<char> source(200);
|
|
v8::base::SNPrintF(source,
|
|
"function test() {"
|
|
" %s"
|
|
"};"
|
|
"%%PrepareFunctionForOptimization(test);"
|
|
"try { test() } catch(e) {}"
|
|
"try { test() } catch(e) {}"
|
|
"%%OptimizeFunctionOnNextCall(test);"
|
|
"test()",
|
|
operation);
|
|
signature_callback_count = 0;
|
|
signature_expected_receiver = receiver;
|
|
bool expected_to_throw = receiver.IsEmpty();
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun(source.begin());
|
|
CHECK_EQ(expected_to_throw, try_catch.HasCaught());
|
|
if (!expected_to_throw) {
|
|
CHECK_EQ(3, signature_callback_count);
|
|
} else {
|
|
CHECK(v8_str("TypeError: Illegal invocation")
|
|
->Equals(isolate->GetCurrentContext(),
|
|
try_catch.Exception()
|
|
->ToString(isolate->GetCurrentContext())
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
static void TestSignature(const char* operation, Local<Value> receiver,
|
|
v8::Isolate* isolate) {
|
|
TestSignatureLooped(operation, receiver, isolate);
|
|
TestSignatureOptimized(operation, receiver, isolate);
|
|
}
|
|
|
|
THREADED_TEST(ReceiverSignature) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
// Setup templates.
|
|
v8::Local<v8::FunctionTemplate> fun = v8::FunctionTemplate::New(isolate);
|
|
v8::Local<v8::Signature> sig = v8::Signature::New(isolate, fun);
|
|
v8::Local<v8::FunctionTemplate> callback_sig = v8::FunctionTemplate::New(
|
|
isolate, IncrementingSignatureCallback, Local<Value>(), sig);
|
|
v8::Local<v8::FunctionTemplate> callback =
|
|
v8::FunctionTemplate::New(isolate, IncrementingSignatureCallback);
|
|
v8::Local<v8::FunctionTemplate> sub_fun = v8::FunctionTemplate::New(isolate);
|
|
sub_fun->Inherit(fun);
|
|
v8::Local<v8::FunctionTemplate> direct_sub_fun =
|
|
v8::FunctionTemplate::New(isolate);
|
|
direct_sub_fun->Inherit(fun);
|
|
v8::Local<v8::FunctionTemplate> unrel_fun =
|
|
v8::FunctionTemplate::New(isolate);
|
|
// Install properties.
|
|
v8::Local<v8::ObjectTemplate> fun_proto = fun->PrototypeTemplate();
|
|
fun_proto->Set(isolate, "prop_sig", callback_sig);
|
|
fun_proto->Set(isolate, "prop", callback);
|
|
fun_proto->SetAccessorProperty(
|
|
v8_str("accessor_sig"), callback_sig, callback_sig);
|
|
fun_proto->SetAccessorProperty(v8_str("accessor"), callback, callback);
|
|
// Instantiate templates.
|
|
Local<Value> fun_instance =
|
|
fun->InstanceTemplate()->NewInstance(env.local()).ToLocalChecked();
|
|
Local<Value> sub_fun_instance =
|
|
sub_fun->InstanceTemplate()->NewInstance(env.local()).ToLocalChecked();
|
|
// Instance template with properties.
|
|
v8::Local<v8::ObjectTemplate> direct_instance_templ =
|
|
direct_sub_fun->InstanceTemplate();
|
|
direct_instance_templ->Set(isolate, "prop_sig", callback_sig);
|
|
direct_instance_templ->Set(isolate, "prop", callback);
|
|
direct_instance_templ->SetAccessorProperty(v8_str("accessor_sig"),
|
|
callback_sig, callback_sig);
|
|
direct_instance_templ->SetAccessorProperty(v8_str("accessor"), callback,
|
|
callback);
|
|
Local<Value> direct_instance =
|
|
direct_instance_templ->NewInstance(env.local()).ToLocalChecked();
|
|
// Setup global variables.
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("Fun"),
|
|
fun->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("UnrelFun"),
|
|
unrel_fun->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("fun_instance"), fun_instance)
|
|
.FromJust());
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("sub_fun_instance"), sub_fun_instance)
|
|
.FromJust());
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("direct_instance"), direct_instance)
|
|
.FromJust());
|
|
CompileRun(
|
|
"var accessor_sig_key = 'accessor_sig';"
|
|
"var accessor_key = 'accessor';"
|
|
"var prop_sig_key = 'prop_sig';"
|
|
"var prop_key = 'prop';"
|
|
""
|
|
"function copy_props(obj) {"
|
|
" var keys = [accessor_sig_key, accessor_key, prop_sig_key, prop_key];"
|
|
" var source = Fun.prototype;"
|
|
" for (var i in keys) {"
|
|
" var key = keys[i];"
|
|
" var desc = Object.getOwnPropertyDescriptor(source, key);"
|
|
" Object.defineProperty(obj, key, desc);"
|
|
" }"
|
|
"}"
|
|
""
|
|
"var plain = {};"
|
|
"copy_props(plain);"
|
|
"var unrelated = new UnrelFun();"
|
|
"copy_props(unrelated);"
|
|
"var inherited = { __proto__: fun_instance };"
|
|
"var inherited_direct = { __proto__: direct_instance };");
|
|
// Test with and without ICs
|
|
const char* test_objects[] = {
|
|
"fun_instance", "sub_fun_instance", "direct_instance", "plain",
|
|
"unrelated", "inherited", "inherited_direct"};
|
|
unsigned bad_signature_start_offset = 3;
|
|
for (unsigned i = 0; i < arraysize(test_objects); i++) {
|
|
v8::base::ScopedVector<char> source(200);
|
|
v8::base::SNPrintF(source, "var test_object = %s; test_object",
|
|
test_objects[i]);
|
|
Local<Value> test_object = CompileRun(source.begin());
|
|
TestSignature("test_object.prop();", test_object, isolate);
|
|
TestSignature("test_object.accessor;", test_object, isolate);
|
|
TestSignature("test_object[accessor_key];", test_object, isolate);
|
|
TestSignature("test_object.accessor = 1;", test_object, isolate);
|
|
TestSignature("test_object[accessor_key] = 1;", test_object, isolate);
|
|
if (i >= bad_signature_start_offset) test_object = Local<Value>();
|
|
TestSignature("test_object.prop_sig();", test_object, isolate);
|
|
TestSignature("test_object.accessor_sig;", test_object, isolate);
|
|
TestSignature("test_object[accessor_sig_key];", test_object, isolate);
|
|
TestSignature("test_object.accessor_sig = 1;", test_object, isolate);
|
|
TestSignature("test_object[accessor_sig_key] = 1;", test_object, isolate);
|
|
}
|
|
}
|
|
|
|
static void DoNothingCallback(const v8::FunctionCallbackInfo<v8::Value>&) {}
|
|
|
|
// Regression test for issue chromium:1188563.
|
|
THREADED_TEST(Regress1188563) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Set up some data for CallHandlerInfo.
|
|
v8::Local<v8::FunctionTemplate> data_constructor_templ =
|
|
v8::FunctionTemplate::New(isolate);
|
|
v8::Local<Function> data_constructor =
|
|
data_constructor_templ->GetFunction(env.local()).ToLocalChecked();
|
|
v8::Local<v8::Object> data =
|
|
data_constructor->NewInstance(env.local()).ToLocalChecked();
|
|
|
|
// Setup templates and instance with accessor property.
|
|
v8::Local<v8::FunctionTemplate> fun = v8::FunctionTemplate::New(isolate);
|
|
v8::Local<v8::FunctionTemplate> callback =
|
|
v8::FunctionTemplate::New(isolate, DoNothingCallback, data);
|
|
v8::Local<v8::ObjectTemplate> instance_templ = fun->InstanceTemplate();
|
|
instance_templ->SetAccessorProperty(v8_str("accessor"), callback, callback);
|
|
Local<Value> test_object =
|
|
instance_templ->NewInstance(env.local()).ToLocalChecked();
|
|
// Setup global variables.
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("test_object"), test_object)
|
|
.FromJust());
|
|
CompileRun(
|
|
"function test() {"
|
|
" test_object.accessor;"
|
|
"};"
|
|
"%PrepareFunctionForOptimization(test);"
|
|
"try { test() } catch(e) {}"
|
|
"try { test() } catch(e) {}"
|
|
"%OptimizeFunctionOnNextCall(test);"
|
|
"test()");
|
|
}
|
|
|
|
THREADED_TEST(HulIgennem) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Primitive> undef = v8::Undefined(isolate);
|
|
Local<String> undef_str = undef->ToString(env.local()).ToLocalChecked();
|
|
char* value = i::NewArray<char>(undef_str->Utf8Length(isolate) + 1);
|
|
undef_str->WriteUtf8(isolate, value);
|
|
CHECK_EQ(0, strcmp(value, "undefined"));
|
|
i::DeleteArray(value);
|
|
}
|
|
|
|
|
|
THREADED_TEST(Access) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<v8::Object> obj = v8::Object::New(isolate);
|
|
Local<Value> foo_before =
|
|
obj->Get(env.local(), v8_str("foo")).ToLocalChecked();
|
|
CHECK(foo_before->IsUndefined());
|
|
Local<String> bar_str = v8_str("bar");
|
|
CHECK(obj->Set(env.local(), v8_str("foo"), bar_str).FromJust());
|
|
Local<Value> foo_after =
|
|
obj->Get(env.local(), v8_str("foo")).ToLocalChecked();
|
|
CHECK(!foo_after->IsUndefined());
|
|
CHECK(foo_after->IsString());
|
|
CHECK(bar_str->Equals(env.local(), foo_after).FromJust());
|
|
|
|
CHECK(obj->Set(env.local(), v8_str("foo"), bar_str).ToChecked());
|
|
bool result;
|
|
CHECK(obj->Set(env.local(), v8_str("foo"), bar_str).To(&result));
|
|
CHECK(result);
|
|
}
|
|
|
|
|
|
THREADED_TEST(AccessElement) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
Local<v8::Object> obj = v8::Object::New(env->GetIsolate());
|
|
Local<Value> before = obj->Get(env.local(), 1).ToLocalChecked();
|
|
CHECK(before->IsUndefined());
|
|
Local<String> bar_str = v8_str("bar");
|
|
CHECK(obj->Set(env.local(), 1, bar_str).FromJust());
|
|
Local<Value> after = obj->Get(env.local(), 1).ToLocalChecked();
|
|
CHECK(!after->IsUndefined());
|
|
CHECK(after->IsString());
|
|
CHECK(bar_str->Equals(env.local(), after).FromJust());
|
|
|
|
Local<v8::Array> value = CompileRun("[\"a\", \"b\"]").As<v8::Array>();
|
|
CHECK(v8_str("a")
|
|
->Equals(env.local(), value->Get(env.local(), 0).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("b")
|
|
->Equals(env.local(), value->Get(env.local(), 1).ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(Script) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
const char* source = "1 + 2 + 3";
|
|
Local<Script> script = v8_compile(source);
|
|
CHECK_EQ(6, v8_run_int32value(script));
|
|
}
|
|
|
|
|
|
class TestResource: public String::ExternalStringResource {
|
|
public:
|
|
explicit TestResource(uint16_t* data, int* counter = nullptr,
|
|
bool owning_data = true)
|
|
: data_(data), length_(0), counter_(counter), owning_data_(owning_data) {
|
|
while (data[length_]) ++length_;
|
|
}
|
|
|
|
~TestResource() override {
|
|
if (owning_data_) i::DeleteArray(data_);
|
|
if (counter_ != nullptr) ++*counter_;
|
|
}
|
|
|
|
const uint16_t* data() const override { return data_; }
|
|
|
|
size_t length() const override { return length_; }
|
|
|
|
private:
|
|
uint16_t* data_;
|
|
size_t length_;
|
|
int* counter_;
|
|
bool owning_data_;
|
|
};
|
|
|
|
|
|
class TestOneByteResource : public String::ExternalOneByteStringResource {
|
|
public:
|
|
explicit TestOneByteResource(const char* data, int* counter = nullptr,
|
|
size_t offset = 0)
|
|
: orig_data_(data),
|
|
data_(data + offset),
|
|
length_(strlen(data) - offset),
|
|
counter_(counter) {}
|
|
|
|
~TestOneByteResource() override {
|
|
i::DeleteArray(orig_data_);
|
|
if (counter_ != nullptr) ++*counter_;
|
|
}
|
|
|
|
const char* data() const override { return data_; }
|
|
|
|
size_t length() const override { return length_; }
|
|
|
|
private:
|
|
const char* orig_data_;
|
|
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);
|
|
{
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
TestResource* resource = new TestResource(two_byte_source, &dispose_count);
|
|
Local<String> source =
|
|
String::NewExternalTwoByte(env->GetIsolate(), resource)
|
|
.ToLocalChecked();
|
|
Local<Script> script = v8_compile(source);
|
|
Local<Value> value = script->Run(env.local()).ToLocalChecked();
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(7, value->Int32Value(env.local()).FromJust());
|
|
CHECK(source->IsExternalTwoByte());
|
|
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);
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(0, dispose_count);
|
|
}
|
|
CcTest::i_isolate()->compilation_cache()->Clear();
|
|
CcTest::CollectAllAvailableGarbage();
|
|
CHECK_EQ(1, dispose_count);
|
|
}
|
|
|
|
|
|
THREADED_TEST(ScriptUsingOneByteStringResource) {
|
|
int dispose_count = 0;
|
|
const char* c_source = "1 + 2 * 3";
|
|
{
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
TestOneByteResource* resource =
|
|
new TestOneByteResource(i::StrDup(c_source), &dispose_count);
|
|
Local<String> source =
|
|
String::NewExternalOneByte(env->GetIsolate(), resource)
|
|
.ToLocalChecked();
|
|
CHECK(source->IsExternalOneByte());
|
|
CHECK(source->IsExternal());
|
|
CHECK(!source->IsExternalTwoByte());
|
|
CHECK_EQ(static_cast<const String::ExternalStringResourceBase*>(resource),
|
|
source->GetExternalOneByteStringResource());
|
|
String::Encoding encoding = String::UNKNOWN_ENCODING;
|
|
CHECK_EQ(static_cast<const String::ExternalStringResourceBase*>(resource),
|
|
source->GetExternalStringResourceBase(&encoding));
|
|
CHECK_EQ(String::ONE_BYTE_ENCODING, encoding);
|
|
Local<Script> script = v8_compile(source);
|
|
Local<Value> value = script->Run(env.local()).ToLocalChecked();
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(7, value->Int32Value(env.local()).FromJust());
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(0, dispose_count);
|
|
}
|
|
CcTest::i_isolate()->compilation_cache()->Clear();
|
|
CcTest::CollectAllAvailableGarbage();
|
|
CHECK_EQ(1, dispose_count);
|
|
}
|
|
|
|
|
|
THREADED_TEST(ScriptMakingExternalString) {
|
|
int dispose_count = 0;
|
|
uint16_t* two_byte_source = AsciiToTwoByteString("1 + 2 * 3");
|
|
{
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
Local<String> source =
|
|
String::NewFromTwoByte(env->GetIsolate(), two_byte_source)
|
|
.ToLocalChecked();
|
|
// Trigger GCs so that the newly allocated string moves to old gen.
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in survivor space now
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in old gen now
|
|
CHECK(!source->IsExternalTwoByte());
|
|
CHECK(!source->IsExternalOneByte());
|
|
CHECK(!source->IsExternal());
|
|
String::Encoding encoding = String::UNKNOWN_ENCODING;
|
|
CHECK(!source->GetExternalStringResourceBase(&encoding));
|
|
CHECK_EQ(String::ONE_BYTE_ENCODING, encoding);
|
|
bool success = source->MakeExternal(new TestResource(two_byte_source,
|
|
&dispose_count));
|
|
CHECK(success);
|
|
Local<Script> script = v8_compile(source);
|
|
Local<Value> value = script->Run(env.local()).ToLocalChecked();
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(7, value->Int32Value(env.local()).FromJust());
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(0, dispose_count);
|
|
}
|
|
CcTest::i_isolate()->compilation_cache()->Clear();
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(1, dispose_count);
|
|
}
|
|
|
|
|
|
THREADED_TEST(ScriptMakingExternalOneByteString) {
|
|
int dispose_count = 0;
|
|
const char* c_source = "1 + 2 * 3";
|
|
{
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
Local<String> source = v8_str(c_source);
|
|
// Trigger GCs so that the newly allocated string moves to old gen.
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in survivor space now
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in old gen now
|
|
bool success = source->MakeExternal(
|
|
new TestOneByteResource(i::StrDup(c_source), &dispose_count));
|
|
CHECK(success);
|
|
Local<Script> script = v8_compile(source);
|
|
Local<Value> value = script->Run(env.local()).ToLocalChecked();
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(7, value->Int32Value(env.local()).FromJust());
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(0, dispose_count);
|
|
}
|
|
CcTest::i_isolate()->compilation_cache()->Clear();
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(1, dispose_count);
|
|
}
|
|
|
|
|
|
TEST(MakingExternalStringConditions) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
|
|
if (!v8::internal::FLAG_single_generation) {
|
|
// Free some space in the new space so that we can check freshness.
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
}
|
|
|
|
uint16_t* two_byte_string = AsciiToTwoByteString("s1");
|
|
Local<String> tiny_local_string =
|
|
String::NewFromTwoByte(env->GetIsolate(), two_byte_string)
|
|
.ToLocalChecked();
|
|
i::DeleteArray(two_byte_string);
|
|
|
|
two_byte_string = AsciiToTwoByteString("s1234");
|
|
Local<String> local_string =
|
|
String::NewFromTwoByte(env->GetIsolate(), two_byte_string)
|
|
.ToLocalChecked();
|
|
i::DeleteArray(two_byte_string);
|
|
|
|
if (!v8::internal::FLAG_single_generation) {
|
|
// We should refuse to externalize new space strings.
|
|
CHECK(!local_string->CanMakeExternal());
|
|
// Trigger GCs so that the newly allocated string moves to old gen.
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in survivor space now
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in old gen now
|
|
}
|
|
// Old space strings should be accepted.
|
|
CHECK(local_string->CanMakeExternal());
|
|
|
|
// Tiny strings are not in-place externalizable when pointer compression is
|
|
// enabled.
|
|
CHECK_EQ(i::kTaggedSize == i::kSystemPointerSize,
|
|
tiny_local_string->CanMakeExternal());
|
|
}
|
|
|
|
|
|
TEST(MakingExternalOneByteStringConditions) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
|
|
if (!v8::internal::FLAG_single_generation) {
|
|
// Free some space in the new space so that we can check freshness.
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
}
|
|
|
|
Local<String> tiny_local_string = v8_str("s");
|
|
Local<String> local_string = v8_str("s1234");
|
|
|
|
if (!v8::internal::FLAG_single_generation) {
|
|
// We should refuse to externalize new space strings.
|
|
CHECK(!local_string->CanMakeExternal());
|
|
// Trigger GCs so that the newly allocated string moves to old gen.
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in survivor space now
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in old gen now
|
|
}
|
|
// Old space strings should be accepted.
|
|
CHECK(local_string->CanMakeExternal());
|
|
|
|
// Tiny strings are not in-place externalizable when pointer compression is
|
|
// enabled.
|
|
CHECK_EQ(i::kTaggedSize == i::kSystemPointerSize,
|
|
tiny_local_string->CanMakeExternal());
|
|
}
|
|
|
|
|
|
TEST(MakingExternalUnalignedOneByteString) {
|
|
i::FLAG_stress_concurrent_allocation = false; // For SimulateFullSpace.
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
|
|
CompileRun("function cons(a, b) { return a + b; }"
|
|
"function slice(a) { return a.substring(1); }");
|
|
// Create a cons string that will land in old pointer space.
|
|
Local<String> cons = Local<String>::Cast(CompileRun(
|
|
"cons('abcdefghijklm', 'nopqrstuvwxyz');"));
|
|
// Create a sliced string that will land in old pointer space.
|
|
Local<String> slice = Local<String>::Cast(CompileRun(
|
|
"slice('abcdefghijklmnopqrstuvwxyz');"));
|
|
|
|
// Trigger GCs so that the newly allocated string moves to old gen.
|
|
i::heap::SimulateFullSpace(CcTest::heap()->old_space());
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in survivor space now
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in old gen now
|
|
|
|
// Turn into external string with unaligned resource data.
|
|
const char* c_cons = "_abcdefghijklmnopqrstuvwxyz";
|
|
bool success = cons->MakeExternal(
|
|
new TestOneByteResource(i::StrDup(c_cons), nullptr, 1));
|
|
CHECK(success);
|
|
const char* c_slice = "_bcdefghijklmnopqrstuvwxyz";
|
|
success = slice->MakeExternal(
|
|
new TestOneByteResource(i::StrDup(c_slice), nullptr, 1));
|
|
CHECK(success);
|
|
|
|
// Trigger GCs and force evacuation.
|
|
CcTest::CollectAllGarbage();
|
|
CcTest::heap()->CollectAllGarbage(i::Heap::kReduceMemoryFootprintMask,
|
|
i::GarbageCollectionReason::kTesting);
|
|
}
|
|
|
|
THREADED_TEST(UsingExternalString) {
|
|
i::Factory* factory = CcTest::i_isolate()->factory();
|
|
{
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
uint16_t* two_byte_string = AsciiToTwoByteString("test string");
|
|
Local<String> string =
|
|
String::NewExternalTwoByte(CcTest::isolate(),
|
|
new TestResource(two_byte_string))
|
|
.ToLocalChecked();
|
|
i::Handle<i::String> istring = v8::Utils::OpenHandle(*string);
|
|
// Trigger GCs so that the newly allocated string moves to old gen.
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in survivor space now
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in old gen now
|
|
i::Handle<i::String> isymbol =
|
|
factory->InternalizeString(istring);
|
|
CHECK(isymbol->IsInternalizedString());
|
|
}
|
|
CcTest::CollectAllGarbage();
|
|
CcTest::CollectAllGarbage();
|
|
}
|
|
|
|
|
|
THREADED_TEST(UsingExternalOneByteString) {
|
|
i::Factory* factory = CcTest::i_isolate()->factory();
|
|
{
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
const char* one_byte_string = "test string";
|
|
Local<String> string =
|
|
String::NewExternalOneByte(
|
|
CcTest::isolate(),
|
|
new TestOneByteResource(i::StrDup(one_byte_string)))
|
|
.ToLocalChecked();
|
|
i::Handle<i::String> istring = v8::Utils::OpenHandle(*string);
|
|
// Trigger GCs so that the newly allocated string moves to old gen.
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in survivor space now
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in old gen now
|
|
i::Handle<i::String> isymbol =
|
|
factory->InternalizeString(istring);
|
|
CHECK(isymbol->IsInternalizedString());
|
|
}
|
|
CcTest::CollectAllGarbage();
|
|
CcTest::CollectAllGarbage();
|
|
}
|
|
|
|
|
|
class RandomLengthResource : public v8::String::ExternalStringResource {
|
|
public:
|
|
explicit RandomLengthResource(int length) : length_(length) {}
|
|
const uint16_t* data() const override { return string_; }
|
|
size_t length() const override { return length_; }
|
|
|
|
private:
|
|
uint16_t string_[10];
|
|
int length_;
|
|
};
|
|
|
|
|
|
class RandomLengthOneByteResource
|
|
: public v8::String::ExternalOneByteStringResource {
|
|
public:
|
|
explicit RandomLengthOneByteResource(int length) : length_(length) {}
|
|
const char* data() const override { return string_; }
|
|
size_t length() const override { return length_; }
|
|
|
|
private:
|
|
char string_[10];
|
|
int length_;
|
|
};
|
|
|
|
|
|
THREADED_TEST(NewExternalForVeryLongString) {
|
|
auto isolate = CcTest::isolate();
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
RandomLengthOneByteResource r(1 << 30);
|
|
v8::MaybeLocal<v8::String> maybe_str =
|
|
v8::String::NewExternalOneByte(isolate, &r);
|
|
CHECK(maybe_str.IsEmpty());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
RandomLengthResource r(1 << 30);
|
|
v8::MaybeLocal<v8::String> maybe_str =
|
|
v8::String::NewExternalTwoByte(isolate, &r);
|
|
CHECK(maybe_str.IsEmpty());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
}
|
|
|
|
TEST(ScavengeExternalString) {
|
|
ManualGCScope manual_gc_scope;
|
|
i::FLAG_stress_compaction = false;
|
|
i::FLAG_gc_global = false;
|
|
int dispose_count = 0;
|
|
bool in_young_generation = false;
|
|
{
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
uint16_t* two_byte_string = AsciiToTwoByteString("test string");
|
|
Local<String> string =
|
|
String::NewExternalTwoByte(
|
|
CcTest::isolate(),
|
|
new TestResource(two_byte_string, &dispose_count))
|
|
.ToLocalChecked();
|
|
i::Handle<i::String> istring = v8::Utils::OpenHandle(*string);
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
in_young_generation = i::Heap::InYoungGeneration(*istring);
|
|
CHECK_IMPLIES(!in_young_generation,
|
|
CcTest::heap()->old_space()->Contains(*istring));
|
|
CHECK_EQ(0, dispose_count);
|
|
}
|
|
CcTest::CollectGarbage(in_young_generation ? i::NEW_SPACE : i::OLD_SPACE);
|
|
CHECK_EQ(1, dispose_count);
|
|
}
|
|
|
|
TEST(ScavengeExternalOneByteString) {
|
|
ManualGCScope manual_gc_scope;
|
|
i::FLAG_stress_compaction = false;
|
|
i::FLAG_gc_global = false;
|
|
int dispose_count = 0;
|
|
bool in_young_generation = false;
|
|
{
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
const char* one_byte_string = "test string";
|
|
Local<String> string =
|
|
String::NewExternalOneByte(
|
|
CcTest::isolate(),
|
|
new TestOneByteResource(i::StrDup(one_byte_string), &dispose_count))
|
|
.ToLocalChecked();
|
|
i::Handle<i::String> istring = v8::Utils::OpenHandle(*string);
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
in_young_generation = i::Heap::InYoungGeneration(*istring);
|
|
CHECK_IMPLIES(!in_young_generation,
|
|
CcTest::heap()->old_space()->Contains(*istring));
|
|
CHECK_EQ(0, dispose_count);
|
|
}
|
|
CcTest::CollectGarbage(in_young_generation ? i::NEW_SPACE : i::OLD_SPACE);
|
|
CHECK_EQ(1, dispose_count);
|
|
}
|
|
|
|
|
|
class TestOneByteResourceWithDisposeControl : public TestOneByteResource {
|
|
public:
|
|
// Only used by non-threaded tests, so it can use static fields.
|
|
static int dispose_calls;
|
|
static int dispose_count;
|
|
|
|
TestOneByteResourceWithDisposeControl(const char* data, bool dispose)
|
|
: TestOneByteResource(data, &dispose_count), dispose_(dispose) {}
|
|
|
|
void Dispose() override {
|
|
++dispose_calls;
|
|
if (dispose_) delete this;
|
|
}
|
|
private:
|
|
bool dispose_;
|
|
};
|
|
|
|
|
|
int TestOneByteResourceWithDisposeControl::dispose_count = 0;
|
|
int TestOneByteResourceWithDisposeControl::dispose_calls = 0;
|
|
|
|
|
|
TEST(ExternalStringWithDisposeHandling) {
|
|
const char* c_source = "1 + 2 * 3";
|
|
|
|
// Use a stack allocated external string resource allocated object.
|
|
TestOneByteResourceWithDisposeControl::dispose_count = 0;
|
|
TestOneByteResourceWithDisposeControl::dispose_calls = 0;
|
|
TestOneByteResourceWithDisposeControl res_stack(i::StrDup(c_source), false);
|
|
{
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
Local<String> source =
|
|
String::NewExternalOneByte(env->GetIsolate(), &res_stack)
|
|
.ToLocalChecked();
|
|
Local<Script> script = v8_compile(source);
|
|
Local<Value> value = script->Run(env.local()).ToLocalChecked();
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(7, value->Int32Value(env.local()).FromJust());
|
|
CcTest::CollectAllAvailableGarbage();
|
|
CHECK_EQ(0, TestOneByteResourceWithDisposeControl::dispose_count);
|
|
}
|
|
CcTest::i_isolate()->compilation_cache()->Clear();
|
|
CcTest::CollectAllAvailableGarbage();
|
|
CHECK_EQ(1, TestOneByteResourceWithDisposeControl::dispose_calls);
|
|
CHECK_EQ(0, TestOneByteResourceWithDisposeControl::dispose_count);
|
|
|
|
// Use a heap allocated external string resource allocated object.
|
|
TestOneByteResourceWithDisposeControl::dispose_count = 0;
|
|
TestOneByteResourceWithDisposeControl::dispose_calls = 0;
|
|
TestOneByteResource* res_heap =
|
|
new TestOneByteResourceWithDisposeControl(i::StrDup(c_source), true);
|
|
{
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
Local<String> source =
|
|
String::NewExternalOneByte(env->GetIsolate(), res_heap)
|
|
.ToLocalChecked();
|
|
Local<Script> script = v8_compile(source);
|
|
Local<Value> value = script->Run(env.local()).ToLocalChecked();
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(7, value->Int32Value(env.local()).FromJust());
|
|
CcTest::CollectAllAvailableGarbage();
|
|
CHECK_EQ(0, TestOneByteResourceWithDisposeControl::dispose_count);
|
|
}
|
|
CcTest::i_isolate()->compilation_cache()->Clear();
|
|
CcTest::CollectAllAvailableGarbage();
|
|
CHECK_EQ(1, TestOneByteResourceWithDisposeControl::dispose_calls);
|
|
CHECK_EQ(1, TestOneByteResourceWithDisposeControl::dispose_count);
|
|
}
|
|
|
|
|
|
THREADED_TEST(StringConcat) {
|
|
{
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
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::NewFromTwoByte(env->GetIsolate(), two_byte_source)
|
|
.ToLocalChecked();
|
|
i::DeleteArray(two_byte_source);
|
|
|
|
Local<String> source = String::Concat(isolate, left, right);
|
|
right = String::NewExternalOneByte(
|
|
env->GetIsolate(),
|
|
new TestOneByteResource(i::StrDup(one_byte_extern_1)))
|
|
.ToLocalChecked();
|
|
source = String::Concat(isolate, source, right);
|
|
right = String::NewExternalTwoByte(
|
|
env->GetIsolate(),
|
|
new TestResource(AsciiToTwoByteString(two_byte_extern_1)))
|
|
.ToLocalChecked();
|
|
source = String::Concat(isolate, source, right);
|
|
right = v8_str(one_byte_string_2);
|
|
source = String::Concat(isolate, source, right);
|
|
|
|
two_byte_source = AsciiToTwoByteString(two_byte_string_2);
|
|
right = String::NewFromTwoByte(env->GetIsolate(), two_byte_source)
|
|
.ToLocalChecked();
|
|
i::DeleteArray(two_byte_source);
|
|
|
|
source = String::Concat(isolate, source, right);
|
|
right = String::NewExternalTwoByte(
|
|
env->GetIsolate(),
|
|
new TestResource(AsciiToTwoByteString(two_byte_extern_2)))
|
|
.ToLocalChecked();
|
|
source = String::Concat(isolate, source, right);
|
|
Local<Script> script = v8_compile(source);
|
|
Local<Value> value = script->Run(env.local()).ToLocalChecked();
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(68, value->Int32Value(env.local()).FromJust());
|
|
}
|
|
CcTest::i_isolate()->compilation_cache()->Clear();
|
|
CcTest::CollectAllGarbage();
|
|
CcTest::CollectAllGarbage();
|
|
}
|
|
|
|
|
|
THREADED_TEST(GlobalProperties) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Local<v8::Object> global = env->Global();
|
|
CHECK(global->Set(env.local(), v8_str("pi"), v8_num(3.1415926)).FromJust());
|
|
Local<Value> pi = global->Get(env.local(), v8_str("pi")).ToLocalChecked();
|
|
CHECK_EQ(3.1415926, pi->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
|
|
static void handle_callback_impl(const v8::FunctionCallbackInfo<Value>& info,
|
|
i::Address callback) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CheckReturnValue(info, callback);
|
|
info.GetReturnValue().Set(v8_str("bad value"));
|
|
info.GetReturnValue().Set(v8_num(102));
|
|
}
|
|
|
|
|
|
static void handle_callback(const v8::FunctionCallbackInfo<Value>& info) {
|
|
return handle_callback_impl(info, FUNCTION_ADDR(handle_callback));
|
|
}
|
|
|
|
|
|
static void handle_callback_2(const v8::FunctionCallbackInfo<Value>& info) {
|
|
return handle_callback_impl(info, FUNCTION_ADDR(handle_callback_2));
|
|
}
|
|
|
|
static void construct_callback(
|
|
const v8::FunctionCallbackInfo<Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CheckReturnValue(info, FUNCTION_ADDR(construct_callback));
|
|
CHECK(
|
|
info.This()
|
|
->Set(info.GetIsolate()->GetCurrentContext(), v8_str("x"), v8_num(1))
|
|
.FromJust());
|
|
CHECK(
|
|
info.This()
|
|
->Set(info.GetIsolate()->GetCurrentContext(), v8_str("y"), v8_num(2))
|
|
.FromJust());
|
|
info.GetReturnValue().Set(v8_str("bad value"));
|
|
info.GetReturnValue().Set(info.This());
|
|
}
|
|
|
|
|
|
static void Return239Callback(
|
|
Local<String> name, const v8::PropertyCallbackInfo<Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CheckReturnValue(info, FUNCTION_ADDR(Return239Callback));
|
|
info.GetReturnValue().Set(v8_str("bad value"));
|
|
info.GetReturnValue().Set(v8_num(239));
|
|
}
|
|
|
|
|
|
template<typename Handler>
|
|
static void TestFunctionTemplateInitializer(Handler handler,
|
|
Handler handler_2) {
|
|
// Test constructor calls.
|
|
{
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(isolate, handler);
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj"), fun).FromJust());
|
|
Local<Script> script = v8_compile("obj()");
|
|
for (int i = 0; i < 30; i++) {
|
|
CHECK_EQ(102, v8_run_int32value(script));
|
|
}
|
|
}
|
|
// Use SetCallHandler to initialize a function template, should work like
|
|
// the previous one.
|
|
{
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(isolate);
|
|
fun_templ->SetCallHandler(handler_2);
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj"), fun).FromJust());
|
|
Local<Script> script = v8_compile("obj()");
|
|
for (int i = 0; i < 30; i++) {
|
|
CHECK_EQ(102, v8_run_int32value(script));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
template<typename Constructor, typename Accessor>
|
|
static void TestFunctionTemplateAccessor(Constructor constructor,
|
|
Accessor accessor) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(isolate, constructor);
|
|
fun_templ->PrototypeTemplate()->Set(
|
|
v8::Symbol::GetToStringTag(isolate), v8_str("funky"),
|
|
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontEnum));
|
|
fun_templ->InstanceTemplate()->SetAccessor(v8_str("m"), accessor);
|
|
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj"), fun).FromJust());
|
|
Local<Value> result = CompileRun("(new obj()).toString()");
|
|
CHECK(v8_str("[object funky]")->Equals(env.local(), result).FromJust());
|
|
CompileRun("var obj_instance = new obj();");
|
|
|
|
Local<Script> script = v8_compile("obj_instance.x");
|
|
for (int i = 0; i < 30; i++) {
|
|
CHECK_EQ(1, v8_run_int32value(script));
|
|
}
|
|
script = v8_compile("obj_instance.m");
|
|
for (int i = 0; i < 30; i++) {
|
|
CHECK_EQ(239, v8_run_int32value(script));
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_PROFILED_TEST(FunctionTemplate) {
|
|
TestFunctionTemplateInitializer(handle_callback, handle_callback_2);
|
|
TestFunctionTemplateAccessor(construct_callback, Return239Callback);
|
|
}
|
|
|
|
static void FunctionCallbackForProxyTest(
|
|
const v8::FunctionCallbackInfo<Value>& info) {
|
|
info.GetReturnValue().Set(info.This());
|
|
}
|
|
|
|
THREADED_TEST(FunctionTemplateWithProxy) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::FunctionTemplate> function_template =
|
|
v8::FunctionTemplate::New(isolate, FunctionCallbackForProxyTest);
|
|
v8::Local<v8::Function> function =
|
|
function_template->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK((*env)->Global()->Set(env.local(), v8_str("f"), function).FromJust());
|
|
v8::Local<v8::Value> proxy =
|
|
CompileRun("var proxy = new Proxy({}, {}); proxy");
|
|
CHECK(proxy->IsProxy());
|
|
|
|
v8::Local<v8::Value> result = CompileRun("f(proxy)");
|
|
CHECK(result->Equals(env.local(), (*env)->Global()).FromJust());
|
|
|
|
result = CompileRun("f.call(proxy)");
|
|
CHECK(result->Equals(env.local(), proxy).FromJust());
|
|
|
|
result = CompileRun("Reflect.apply(f, proxy, [1])");
|
|
CHECK(result->Equals(env.local(), proxy).FromJust());
|
|
}
|
|
|
|
static void SimpleCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CheckReturnValue(info, FUNCTION_ADDR(SimpleCallback));
|
|
info.GetReturnValue().Set(v8_num(51423 + info.Length()));
|
|
}
|
|
|
|
|
|
template<typename Callback>
|
|
static void TestSimpleCallback(Callback callback) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::ObjectTemplate> object_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
object_template->Set(isolate, "callback",
|
|
v8::FunctionTemplate::New(isolate, callback));
|
|
v8::Local<v8::Object> object =
|
|
object_template->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK((*env)
|
|
->Global()
|
|
->Set(env.local(), v8_str("callback_object"), object)
|
|
.FromJust());
|
|
v8::Local<v8::Script> script;
|
|
script = v8_compile("callback_object.callback(17)");
|
|
for (int i = 0; i < 30; i++) {
|
|
CHECK_EQ(51424, v8_run_int32value(script));
|
|
}
|
|
script = v8_compile("callback_object.callback(17, 24)");
|
|
for (int i = 0; i < 30; i++) {
|
|
CHECK_EQ(51425, v8_run_int32value(script));
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_PROFILED_TEST(SimpleCallback) {
|
|
TestSimpleCallback(SimpleCallback);
|
|
}
|
|
|
|
|
|
template<typename T>
|
|
void FastReturnValueCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
|
|
|
|
// constant return values
|
|
static int32_t fast_return_value_int32 = 471;
|
|
static uint32_t fast_return_value_uint32 = 571;
|
|
static const double kFastReturnValueDouble = 2.7;
|
|
// variable return values
|
|
static bool fast_return_value_bool = false;
|
|
enum ReturnValueOddball {
|
|
kNullReturnValue,
|
|
kUndefinedReturnValue,
|
|
kEmptyStringReturnValue
|
|
};
|
|
static ReturnValueOddball fast_return_value_void;
|
|
static bool fast_return_value_object_is_empty = false;
|
|
|
|
// Helper function to avoid compiler error: insufficient contextual information
|
|
// to determine type when applying FUNCTION_ADDR to a template function.
|
|
static i::Address address_of(v8::FunctionCallback callback) {
|
|
return FUNCTION_ADDR(callback);
|
|
}
|
|
|
|
template<>
|
|
void FastReturnValueCallback<int32_t>(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
CheckReturnValue(info, address_of(FastReturnValueCallback<int32_t>));
|
|
info.GetReturnValue().Set(fast_return_value_int32);
|
|
}
|
|
|
|
template<>
|
|
void FastReturnValueCallback<uint32_t>(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
CheckReturnValue(info, address_of(FastReturnValueCallback<uint32_t>));
|
|
info.GetReturnValue().Set(fast_return_value_uint32);
|
|
}
|
|
|
|
template<>
|
|
void FastReturnValueCallback<double>(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
CheckReturnValue(info, address_of(FastReturnValueCallback<double>));
|
|
info.GetReturnValue().Set(kFastReturnValueDouble);
|
|
}
|
|
|
|
template<>
|
|
void FastReturnValueCallback<bool>(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
CheckReturnValue(info, address_of(FastReturnValueCallback<bool>));
|
|
info.GetReturnValue().Set(fast_return_value_bool);
|
|
}
|
|
|
|
template<>
|
|
void FastReturnValueCallback<void>(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
CheckReturnValue(info, address_of(FastReturnValueCallback<void>));
|
|
switch (fast_return_value_void) {
|
|
case kNullReturnValue:
|
|
info.GetReturnValue().SetNull();
|
|
break;
|
|
case kUndefinedReturnValue:
|
|
info.GetReturnValue().SetUndefined();
|
|
break;
|
|
case kEmptyStringReturnValue:
|
|
info.GetReturnValue().SetEmptyString();
|
|
break;
|
|
}
|
|
}
|
|
|
|
template<>
|
|
void FastReturnValueCallback<Object>(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
v8::Local<v8::Object> object;
|
|
if (!fast_return_value_object_is_empty) {
|
|
object = Object::New(info.GetIsolate());
|
|
}
|
|
info.GetReturnValue().Set(object);
|
|
}
|
|
|
|
template <typename T>
|
|
Local<Value> TestFastReturnValues() {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::EscapableHandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> object_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
v8::FunctionCallback callback = &FastReturnValueCallback<T>;
|
|
object_template->Set(isolate, "callback",
|
|
v8::FunctionTemplate::New(isolate, callback));
|
|
v8::Local<v8::Object> object =
|
|
object_template->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK((*env)
|
|
->Global()
|
|
->Set(env.local(), v8_str("callback_object"), object)
|
|
.FromJust());
|
|
return scope.Escape(CompileRun("callback_object.callback()"));
|
|
}
|
|
|
|
|
|
THREADED_PROFILED_TEST(FastReturnValues) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Value> value;
|
|
// check int32_t and uint32_t
|
|
int32_t int_values[] = {
|
|
0, 234, -723,
|
|
i::Smi::kMinValue, i::Smi::kMaxValue
|
|
};
|
|
for (size_t i = 0; i < arraysize(int_values); i++) {
|
|
for (int modifier = -1; modifier <= 1; modifier++) {
|
|
int int_value = v8::base::AddWithWraparound(int_values[i], modifier);
|
|
// check int32_t
|
|
fast_return_value_int32 = int_value;
|
|
value = TestFastReturnValues<int32_t>();
|
|
CHECK(value->IsInt32());
|
|
CHECK_EQ(fast_return_value_int32,
|
|
value->Int32Value(env.local()).FromJust());
|
|
// check uint32_t
|
|
fast_return_value_uint32 = static_cast<uint32_t>(int_value);
|
|
value = TestFastReturnValues<uint32_t>();
|
|
CHECK(value->IsUint32());
|
|
CHECK_EQ(fast_return_value_uint32,
|
|
value->Uint32Value(env.local()).FromJust());
|
|
}
|
|
}
|
|
// check double
|
|
value = TestFastReturnValues<double>();
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(kFastReturnValueDouble,
|
|
value->ToNumber(env.local()).ToLocalChecked()->Value());
|
|
// check bool values
|
|
for (int i = 0; i < 2; i++) {
|
|
fast_return_value_bool = i == 0;
|
|
value = TestFastReturnValues<bool>();
|
|
CHECK(value->IsBoolean());
|
|
CHECK_EQ(fast_return_value_bool, value->BooleanValue(isolate));
|
|
}
|
|
// check oddballs
|
|
ReturnValueOddball oddballs[] = {
|
|
kNullReturnValue,
|
|
kUndefinedReturnValue,
|
|
kEmptyStringReturnValue
|
|
};
|
|
for (size_t i = 0; i < arraysize(oddballs); i++) {
|
|
fast_return_value_void = oddballs[i];
|
|
value = TestFastReturnValues<void>();
|
|
switch (fast_return_value_void) {
|
|
case kNullReturnValue:
|
|
CHECK(value->IsNull());
|
|
break;
|
|
case kUndefinedReturnValue:
|
|
CHECK(value->IsUndefined());
|
|
break;
|
|
case kEmptyStringReturnValue:
|
|
CHECK(value->IsString());
|
|
CHECK_EQ(0, v8::String::Cast(*value)->Length());
|
|
break;
|
|
}
|
|
}
|
|
// check handles
|
|
fast_return_value_object_is_empty = false;
|
|
value = TestFastReturnValues<Object>();
|
|
CHECK(value->IsObject());
|
|
fast_return_value_object_is_empty = true;
|
|
value = TestFastReturnValues<Object>();
|
|
CHECK(value->IsUndefined());
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionTemplateSetLength) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
{
|
|
Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(isolate, handle_callback, Local<v8::Value>(),
|
|
Local<v8::Signature>(), 23);
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj"), fun).FromJust());
|
|
Local<Script> script = v8_compile("obj.length");
|
|
CHECK_EQ(23, v8_run_int32value(script));
|
|
}
|
|
{
|
|
Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(isolate, handle_callback);
|
|
fun_templ->SetLength(22);
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj"), fun).FromJust());
|
|
Local<Script> script = v8_compile("obj.length");
|
|
CHECK_EQ(22, v8_run_int32value(script));
|
|
}
|
|
{
|
|
// Without setting length it defaults to 0.
|
|
Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(isolate, handle_callback);
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj"), fun).FromJust());
|
|
Local<Script> script = v8_compile("obj.length");
|
|
CHECK_EQ(0, v8_run_int32value(script));
|
|
}
|
|
}
|
|
|
|
|
|
static void* expected_ptr;
|
|
static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
void* ptr = v8::External::Cast(*args.Data())->Value();
|
|
CHECK_EQ(expected_ptr, ptr);
|
|
args.GetReturnValue().Set(true);
|
|
}
|
|
|
|
|
|
static void TestExternalPointerWrapping() {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
int* ptr = new int;
|
|
expected_ptr = ptr;
|
|
|
|
v8::Local<v8::Value> data = v8::External::New(isolate, expected_ptr);
|
|
|
|
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
|
CHECK(obj->Set(env.local(), v8_str("func"),
|
|
v8::FunctionTemplate::New(isolate, callback, data)
|
|
->GetFunction(env.local())
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust());
|
|
|
|
CHECK(CompileRun("function foo() {\n"
|
|
" for (var i = 0; i < 13; i++) obj.func();\n"
|
|
"}\n"
|
|
"foo(), true")
|
|
->BooleanValue(isolate));
|
|
|
|
delete ptr;
|
|
}
|
|
|
|
|
|
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) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> base = v8::FunctionTemplate::New(isolate);
|
|
Local<v8::FunctionTemplate> derived = v8::FunctionTemplate::New(isolate);
|
|
Local<v8::FunctionTemplate> other = v8::FunctionTemplate::New(isolate);
|
|
derived->Inherit(base);
|
|
|
|
Local<v8::Function> base_function =
|
|
base->GetFunction(env.local()).ToLocalChecked();
|
|
Local<v8::Function> derived_function =
|
|
derived->GetFunction(env.local()).ToLocalChecked();
|
|
Local<v8::Function> other_function =
|
|
other->GetFunction(env.local()).ToLocalChecked();
|
|
|
|
Local<v8::Object> base_instance =
|
|
base_function->NewInstance(env.local()).ToLocalChecked();
|
|
Local<v8::Object> derived_instance =
|
|
derived_function->NewInstance(env.local()).ToLocalChecked();
|
|
Local<v8::Object> derived_instance2 =
|
|
derived_function->NewInstance(env.local()).ToLocalChecked();
|
|
Local<v8::Object> other_instance =
|
|
other_function->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(
|
|
derived_instance2->Set(env.local(), v8_str("__proto__"), derived_instance)
|
|
.FromJust());
|
|
CHECK(other_instance->Set(env.local(), v8_str("__proto__"), derived_instance2)
|
|
.FromJust());
|
|
|
|
// base_instance is only an instance of base.
|
|
CHECK(base_instance->Equals(env.local(),
|
|
base_instance->FindInstanceInPrototypeChain(base))
|
|
.FromJust());
|
|
CHECK(base_instance->FindInstanceInPrototypeChain(derived).IsEmpty());
|
|
CHECK(base_instance->FindInstanceInPrototypeChain(other).IsEmpty());
|
|
|
|
// derived_instance is an instance of base and derived.
|
|
CHECK(derived_instance->Equals(env.local(),
|
|
derived_instance->FindInstanceInPrototypeChain(
|
|
base))
|
|
.FromJust());
|
|
CHECK(derived_instance->Equals(env.local(),
|
|
derived_instance->FindInstanceInPrototypeChain(
|
|
derived))
|
|
.FromJust());
|
|
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(derived_instance2->Equals(
|
|
env.local(),
|
|
other_instance->FindInstanceInPrototypeChain(base))
|
|
.FromJust());
|
|
CHECK(derived_instance2->Equals(env.local(),
|
|
other_instance->FindInstanceInPrototypeChain(
|
|
derived))
|
|
.FromJust());
|
|
CHECK(other_instance->Equals(
|
|
env.local(),
|
|
other_instance->FindInstanceInPrototypeChain(other))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(TinyInteger) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
int32_t value = 239;
|
|
Local<v8::Integer> value_obj = v8::Integer::New(isolate, value);
|
|
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
|
|
}
|
|
|
|
|
|
THREADED_TEST(BigSmiInteger) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
|
|
int32_t value = i::Smi::kMaxValue;
|
|
// We cannot add one to a Smi::kMaxValue without wrapping.
|
|
if (i::SmiValuesAre31Bits()) {
|
|
CHECK(i::Smi::IsValid(value));
|
|
CHECK(!i::Smi::IsValid(value + 1));
|
|
|
|
Local<v8::Integer> value_obj = v8::Integer::New(isolate, value);
|
|
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(BigInteger) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
|
|
// We cannot add one to a Smi::kMaxValue without wrapping.
|
|
if (i::SmiValuesAre31Bits()) {
|
|
// 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_GT(value, i::Smi::kMaxValue);
|
|
CHECK(!i::Smi::IsValid(value));
|
|
|
|
Local<v8::Integer> value_obj = v8::Integer::New(isolate, value);
|
|
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(TinyUnsignedInteger) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
|
|
uint32_t value = 239;
|
|
|
|
Local<v8::Integer> value_obj = v8::Integer::NewFromUnsigned(isolate, value);
|
|
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
|
|
}
|
|
|
|
|
|
THREADED_TEST(BigUnsignedSmiInteger) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
|
|
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(isolate, value);
|
|
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
|
|
}
|
|
|
|
|
|
THREADED_TEST(BigUnsignedInteger) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
|
|
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(isolate, value);
|
|
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
|
|
}
|
|
|
|
|
|
THREADED_TEST(OutOfSignedRangeUnsignedInteger) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
|
|
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(isolate, value);
|
|
CHECK_EQ(static_cast<int64_t>(value), value_obj->Value());
|
|
}
|
|
|
|
|
|
THREADED_TEST(IsNativeError) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Local<Value> syntax_error = CompileRun(
|
|
"var out = 0; try { eval(\"#\"); } catch(x) { out = x; } out; ");
|
|
CHECK(syntax_error->IsNativeError());
|
|
v8::Local<Value> not_error = CompileRun("{a:42}");
|
|
CHECK(!not_error->IsNativeError());
|
|
v8::Local<Value> not_object = CompileRun("42");
|
|
CHECK(!not_object->IsNativeError());
|
|
}
|
|
|
|
|
|
THREADED_TEST(IsGeneratorFunctionOrObject) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
|
|
CompileRun("function *gen() { yield 1; }\nfunction func() {}");
|
|
v8::Local<Value> gen = CompileRun("gen");
|
|
v8::Local<Value> genObj = CompileRun("gen()");
|
|
v8::Local<Value> object = CompileRun("{a:42}");
|
|
v8::Local<Value> func = CompileRun("func");
|
|
|
|
CHECK(gen->IsGeneratorFunction());
|
|
CHECK(gen->IsFunction());
|
|
CHECK(!gen->IsGeneratorObject());
|
|
|
|
CHECK(!genObj->IsGeneratorFunction());
|
|
CHECK(!genObj->IsFunction());
|
|
CHECK(genObj->IsGeneratorObject());
|
|
|
|
CHECK(!object->IsGeneratorFunction());
|
|
CHECK(!object->IsFunction());
|
|
CHECK(!object->IsGeneratorObject());
|
|
|
|
CHECK(!func->IsGeneratorFunction());
|
|
CHECK(func->IsFunction());
|
|
CHECK(!func->IsGeneratorObject());
|
|
}
|
|
|
|
THREADED_TEST(IsAsyncFunction) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
CompileRun("async function foo() {}");
|
|
v8::Local<Value> foo = CompileRun("foo");
|
|
|
|
CHECK(foo->IsAsyncFunction());
|
|
CHECK(foo->IsFunction());
|
|
CHECK(!foo->IsGeneratorFunction());
|
|
CHECK(!foo->IsGeneratorObject());
|
|
|
|
CompileRun("function bar() {}");
|
|
v8::Local<Value> bar = CompileRun("bar");
|
|
|
|
CHECK(!bar->IsAsyncFunction());
|
|
CHECK(bar->IsFunction());
|
|
}
|
|
|
|
THREADED_TEST(ArgumentsObject) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Local<Value> arguments_object =
|
|
CompileRun("var out = 0; (function(){ out = arguments; })(1,2,3); out;");
|
|
CHECK(arguments_object->IsArgumentsObject());
|
|
v8::Local<Value> array = CompileRun("[1,2,3]");
|
|
CHECK(!array->IsArgumentsObject());
|
|
v8::Local<Value> object = CompileRun("{a:42}");
|
|
CHECK(!object->IsArgumentsObject());
|
|
}
|
|
|
|
|
|
THREADED_TEST(IsMapOrSet) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Local<Value> map = CompileRun("new Map()");
|
|
v8::Local<Value> set = CompileRun("new Set()");
|
|
v8::Local<Value> weak_map = CompileRun("new WeakMap()");
|
|
v8::Local<Value> weak_set = CompileRun("new WeakSet()");
|
|
CHECK(map->IsMap());
|
|
CHECK(set->IsSet());
|
|
CHECK(weak_map->IsWeakMap());
|
|
CHECK(weak_set->IsWeakSet());
|
|
|
|
CHECK(!map->IsSet());
|
|
CHECK(!map->IsWeakMap());
|
|
CHECK(!map->IsWeakSet());
|
|
|
|
CHECK(!set->IsMap());
|
|
CHECK(!set->IsWeakMap());
|
|
CHECK(!set->IsWeakSet());
|
|
|
|
CHECK(!weak_map->IsMap());
|
|
CHECK(!weak_map->IsSet());
|
|
CHECK(!weak_map->IsWeakSet());
|
|
|
|
CHECK(!weak_set->IsMap());
|
|
CHECK(!weak_set->IsSet());
|
|
CHECK(!weak_set->IsWeakMap());
|
|
|
|
v8::Local<Value> object = CompileRun("{a:42}");
|
|
CHECK(!object->IsMap());
|
|
CHECK(!object->IsSet());
|
|
CHECK(!object->IsWeakMap());
|
|
CHECK(!object->IsWeakSet());
|
|
}
|
|
|
|
|
|
THREADED_TEST(StringObject) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Local<Value> boxed_string = CompileRun("new String(\"test\")");
|
|
CHECK(boxed_string->IsStringObject());
|
|
v8::Local<Value> unboxed_string = CompileRun("\"test\"");
|
|
CHECK(!unboxed_string->IsStringObject());
|
|
v8::Local<Value> boxed_not_string = CompileRun("new Number(42)");
|
|
CHECK(!boxed_not_string->IsStringObject());
|
|
v8::Local<Value> not_object = CompileRun("0");
|
|
CHECK(!not_object->IsStringObject());
|
|
v8::Local<v8::StringObject> as_boxed = boxed_string.As<v8::StringObject>();
|
|
CHECK(!as_boxed.IsEmpty());
|
|
Local<v8::String> the_string = as_boxed->ValueOf();
|
|
CHECK(!the_string.IsEmpty());
|
|
ExpectObject("\"test\"", the_string);
|
|
v8::Local<v8::Value> new_boxed_string =
|
|
v8::StringObject::New(CcTest::isolate(), the_string);
|
|
CHECK(new_boxed_string->IsStringObject());
|
|
as_boxed = new_boxed_string.As<v8::StringObject>();
|
|
the_string = as_boxed->ValueOf();
|
|
CHECK(!the_string.IsEmpty());
|
|
ExpectObject("\"test\"", the_string);
|
|
}
|
|
|
|
|
|
TEST(StringObjectDelete) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
v8::Local<Value> boxed_string = CompileRun("new String(\"test\")");
|
|
CHECK(boxed_string->IsStringObject());
|
|
v8::Local<v8::Object> str_obj = boxed_string.As<v8::Object>();
|
|
CHECK(!str_obj->Delete(context.local(), 2).FromJust());
|
|
CHECK(!str_obj->Delete(context.local(), v8_num(2)).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(NumberObject) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Local<Value> boxed_number = CompileRun("new Number(42)");
|
|
CHECK(boxed_number->IsNumberObject());
|
|
v8::Local<Value> unboxed_number = CompileRun("42");
|
|
CHECK(!unboxed_number->IsNumberObject());
|
|
v8::Local<Value> boxed_not_number = CompileRun("new Boolean(false)");
|
|
CHECK(!boxed_not_number->IsNumberObject());
|
|
v8::Local<v8::NumberObject> as_boxed = boxed_number.As<v8::NumberObject>();
|
|
CHECK(!as_boxed.IsEmpty());
|
|
double the_number = as_boxed->ValueOf();
|
|
CHECK_EQ(42.0, the_number);
|
|
v8::Local<v8::Value> new_boxed_number =
|
|
v8::NumberObject::New(env->GetIsolate(), 43);
|
|
CHECK(new_boxed_number->IsNumberObject());
|
|
as_boxed = new_boxed_number.As<v8::NumberObject>();
|
|
the_number = as_boxed->ValueOf();
|
|
CHECK_EQ(43.0, the_number);
|
|
}
|
|
|
|
THREADED_TEST(BigIntObject) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context(env.local());
|
|
v8::Local<Value> boxed_bigint = CompileRun("new Object(42n)");
|
|
CHECK(!boxed_bigint->IsBigInt());
|
|
CHECK(boxed_bigint->IsBigIntObject());
|
|
v8::Local<Value> unboxed_bigint = CompileRun("42n");
|
|
CHECK(unboxed_bigint->IsBigInt());
|
|
CHECK(!unboxed_bigint->IsBigIntObject());
|
|
v8::Local<v8::BigIntObject> as_boxed = boxed_bigint.As<v8::BigIntObject>();
|
|
CHECK(!as_boxed.IsEmpty());
|
|
v8::Local<v8::BigInt> unpacked = as_boxed->ValueOf();
|
|
CHECK(!unpacked.IsEmpty());
|
|
v8::Local<v8::Value> new_boxed_bigint = v8::BigIntObject::New(isolate, 43);
|
|
CHECK(new_boxed_bigint->IsBigIntObject());
|
|
v8::Local<v8::Value> new_unboxed_bigint = v8::BigInt::New(isolate, 44);
|
|
CHECK(new_unboxed_bigint->IsBigInt());
|
|
|
|
// Test functionality inherited from v8::Value.
|
|
CHECK(unboxed_bigint->BooleanValue(isolate));
|
|
v8::Local<v8::String> string =
|
|
unboxed_bigint->ToString(context).ToLocalChecked();
|
|
CHECK_EQ(0, strcmp("42", *v8::String::Utf8Value(isolate, string)));
|
|
|
|
// IntegerValue throws.
|
|
CHECK(unboxed_bigint->IntegerValue(context).IsNothing());
|
|
}
|
|
|
|
THREADED_TEST(BooleanObject) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Local<Value> boxed_boolean = CompileRun("new Boolean(true)");
|
|
CHECK(boxed_boolean->IsBooleanObject());
|
|
v8::Local<Value> unboxed_boolean = CompileRun("true");
|
|
CHECK(!unboxed_boolean->IsBooleanObject());
|
|
v8::Local<Value> boxed_not_boolean = CompileRun("new Number(42)");
|
|
CHECK(!boxed_not_boolean->IsBooleanObject());
|
|
v8::Local<v8::BooleanObject> as_boxed = boxed_boolean.As<v8::BooleanObject>();
|
|
CHECK(!as_boxed.IsEmpty());
|
|
bool the_boolean = as_boxed->ValueOf();
|
|
CHECK(the_boolean);
|
|
v8::Local<v8::Value> boxed_true =
|
|
v8::BooleanObject::New(env->GetIsolate(), true);
|
|
v8::Local<v8::Value> boxed_false =
|
|
v8::BooleanObject::New(env->GetIsolate(), false);
|
|
CHECK(boxed_true->IsBooleanObject());
|
|
CHECK(boxed_false->IsBooleanObject());
|
|
as_boxed = boxed_true.As<v8::BooleanObject>();
|
|
CHECK(as_boxed->ValueOf());
|
|
as_boxed = boxed_false.As<v8::BooleanObject>();
|
|
CHECK(!as_boxed->ValueOf());
|
|
}
|
|
|
|
|
|
THREADED_TEST(PrimitiveAndWrappedBooleans) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<Value> primitive_false = Boolean::New(isolate, false);
|
|
CHECK(primitive_false->IsBoolean());
|
|
CHECK(!primitive_false->IsBooleanObject());
|
|
CHECK(!primitive_false->BooleanValue(isolate));
|
|
CHECK(!primitive_false->IsTrue());
|
|
CHECK(primitive_false->IsFalse());
|
|
|
|
Local<Value> false_value = BooleanObject::New(isolate, false);
|
|
CHECK(!false_value->IsBoolean());
|
|
CHECK(false_value->IsBooleanObject());
|
|
CHECK(false_value->BooleanValue(isolate));
|
|
CHECK(!false_value->IsTrue());
|
|
CHECK(!false_value->IsFalse());
|
|
|
|
Local<BooleanObject> false_boolean_object = false_value.As<BooleanObject>();
|
|
CHECK(!false_boolean_object->IsBoolean());
|
|
CHECK(false_boolean_object->IsBooleanObject());
|
|
CHECK(false_boolean_object->BooleanValue(isolate));
|
|
CHECK(!false_boolean_object->ValueOf());
|
|
CHECK(!false_boolean_object->IsTrue());
|
|
CHECK(!false_boolean_object->IsFalse());
|
|
|
|
Local<Value> primitive_true = Boolean::New(isolate, true);
|
|
CHECK(primitive_true->IsBoolean());
|
|
CHECK(!primitive_true->IsBooleanObject());
|
|
CHECK(primitive_true->BooleanValue(isolate));
|
|
CHECK(primitive_true->IsTrue());
|
|
CHECK(!primitive_true->IsFalse());
|
|
|
|
Local<Value> true_value = BooleanObject::New(isolate, true);
|
|
CHECK(!true_value->IsBoolean());
|
|
CHECK(true_value->IsBooleanObject());
|
|
CHECK(true_value->BooleanValue(isolate));
|
|
CHECK(!true_value->IsTrue());
|
|
CHECK(!true_value->IsFalse());
|
|
|
|
Local<BooleanObject> true_boolean_object = true_value.As<BooleanObject>();
|
|
CHECK(!true_boolean_object->IsBoolean());
|
|
CHECK(true_boolean_object->IsBooleanObject());
|
|
CHECK(true_boolean_object->BooleanValue(isolate));
|
|
CHECK(true_boolean_object->ValueOf());
|
|
CHECK(!true_boolean_object->IsTrue());
|
|
CHECK(!true_boolean_object->IsFalse());
|
|
}
|
|
|
|
|
|
THREADED_TEST(Number) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
double PI = 3.1415926;
|
|
Local<v8::Number> pi_obj = v8::Number::New(env->GetIsolate(), PI);
|
|
CHECK_EQ(PI, pi_obj->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(ToNumber) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<String> str = v8_str("3.1415926");
|
|
CHECK_EQ(3.1415926, str->NumberValue(env.local()).FromJust());
|
|
v8::Local<v8::Boolean> t = v8::True(isolate);
|
|
CHECK_EQ(1.0, t->NumberValue(env.local()).FromJust());
|
|
v8::Local<v8::Boolean> f = v8::False(isolate);
|
|
CHECK_EQ(0.0, f->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(Date) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
double PI = 3.1415926;
|
|
Local<Value> date = v8::Date::New(env.local(), PI).ToLocalChecked();
|
|
CHECK_EQ(3.0, date->NumberValue(env.local()).FromJust());
|
|
CHECK(date.As<v8::Date>()
|
|
->Set(env.local(), v8_str("property"),
|
|
v8::Integer::New(env->GetIsolate(), 42))
|
|
.FromJust());
|
|
CHECK_EQ(42, date.As<v8::Date>()
|
|
->Get(env.local(), v8_str("property"))
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(Boolean) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Boolean> t = v8::True(isolate);
|
|
CHECK(t->Value());
|
|
v8::Local<v8::Boolean> f = v8::False(isolate);
|
|
CHECK(!f->Value());
|
|
v8::Local<v8::Primitive> u = v8::Undefined(isolate);
|
|
CHECK(!u->BooleanValue(isolate));
|
|
v8::Local<v8::Primitive> n = v8::Null(isolate);
|
|
CHECK(!n->BooleanValue(isolate));
|
|
v8::Local<String> str1 = v8_str("");
|
|
CHECK(!str1->BooleanValue(isolate));
|
|
v8::Local<String> str2 = v8_str("x");
|
|
CHECK(str2->BooleanValue(isolate));
|
|
CHECK(!v8::Number::New(isolate, 0)->BooleanValue(isolate));
|
|
CHECK(v8::Number::New(isolate, -1)->BooleanValue(isolate));
|
|
CHECK(v8::Number::New(isolate, 1)->BooleanValue(isolate));
|
|
CHECK(v8::Number::New(isolate, 42)->BooleanValue(isolate));
|
|
CHECK(!v8_compile("NaN")
|
|
->Run(env.local())
|
|
.ToLocalChecked()
|
|
->BooleanValue(isolate));
|
|
}
|
|
|
|
|
|
static void DummyCallHandler(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
args.GetReturnValue().Set(v8_num(13.4));
|
|
}
|
|
|
|
|
|
static void GetM(Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
info.GetReturnValue().Set(v8_num(876));
|
|
}
|
|
|
|
|
|
THREADED_TEST(GlobalPrototype) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::FunctionTemplate> func_templ =
|
|
v8::FunctionTemplate::New(isolate);
|
|
func_templ->PrototypeTemplate()->Set(
|
|
isolate, "dummy", v8::FunctionTemplate::New(isolate, DummyCallHandler));
|
|
v8::Local<ObjectTemplate> templ = func_templ->InstanceTemplate();
|
|
templ->Set(isolate, "x", v8_num(200));
|
|
templ->SetAccessor(v8_str("m"), GetM);
|
|
LocalContext env(nullptr, templ);
|
|
v8::Local<Script> script(v8_compile("dummy()"));
|
|
v8::Local<Value> result(script->Run(env.local()).ToLocalChecked());
|
|
CHECK_EQ(13.4, result->NumberValue(env.local()).FromJust());
|
|
CHECK_EQ(200, v8_run_int32value(v8_compile("x")));
|
|
CHECK_EQ(876, v8_run_int32value(v8_compile("m")));
|
|
}
|
|
|
|
|
|
THREADED_TEST(ObjectTemplate) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<v8::FunctionTemplate> acc =
|
|
v8::FunctionTemplate::New(isolate, Returns42);
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("acc"),
|
|
acc->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
Local<v8::FunctionTemplate> fun = v8::FunctionTemplate::New(isolate);
|
|
v8::Local<v8::String> class_name = v8_str("the_class_name");
|
|
fun->SetClassName(class_name);
|
|
Local<ObjectTemplate> templ1 = ObjectTemplate::New(isolate, fun);
|
|
templ1->Set(isolate, "x", v8_num(10));
|
|
templ1->Set(isolate, "y", v8_num(13));
|
|
templ1->Set(isolate, "foo", acc);
|
|
Local<v8::Object> instance1 =
|
|
templ1->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(class_name->StrictEquals(instance1->GetConstructorName()));
|
|
CHECK(env->Global()->Set(env.local(), v8_str("p"), instance1).FromJust());
|
|
CHECK(CompileRun("(p.x == 10)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(p.y == 13)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(p.foo() == 42)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(p.foo == acc)")->BooleanValue(isolate));
|
|
// Ensure that foo become a data field.
|
|
CompileRun("p.foo = function() {}");
|
|
Local<v8::FunctionTemplate> fun2 = v8::FunctionTemplate::New(isolate);
|
|
fun2->PrototypeTemplate()->Set(isolate, "nirk", v8_num(123));
|
|
Local<ObjectTemplate> templ2 = fun2->InstanceTemplate();
|
|
templ2->Set(isolate, "a", v8_num(12));
|
|
templ2->Set(isolate, "b", templ1);
|
|
templ2->Set(isolate, "bar", acc);
|
|
templ2->SetAccessorProperty(v8_str("acc"), acc);
|
|
Local<v8::Object> instance2 =
|
|
templ2->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("q"), instance2).FromJust());
|
|
CHECK(CompileRun("(q.nirk == 123)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q.a == 12)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q.b.x == 10)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q.b.y == 13)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q.b.foo() == 42)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q.b.foo === acc)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q.b !== p)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q.acc == 42)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q.bar() == 42)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q.bar == acc)")->BooleanValue(isolate));
|
|
|
|
instance2 = templ2->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("q2"), instance2).FromJust());
|
|
CHECK(CompileRun("(q2.nirk == 123)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q2.a == 12)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q2.b.x == 10)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q2.b.y == 13)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q2.b.foo() == 42)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q2.b.foo === acc)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q2.acc == 42)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q2.bar() == 42)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(q2.bar === acc)")->BooleanValue(isolate));
|
|
|
|
CHECK(CompileRun("(q.b !== q2.b)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("q.b.x = 17; (q2.b.x == 10)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("desc1 = Object.getOwnPropertyDescriptor(q, 'acc');"
|
|
"(desc1.get === acc)")
|
|
->BooleanValue(isolate));
|
|
CHECK(CompileRun("desc2 = Object.getOwnPropertyDescriptor(q2, 'acc');"
|
|
"(desc2.get === acc)")
|
|
->BooleanValue(isolate));
|
|
}
|
|
|
|
THREADED_TEST(IntegerValue) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
CHECK_EQ(0, CompileRun("undefined")->IntegerValue(env.local()).FromJust());
|
|
}
|
|
|
|
static void GetNirk(Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
info.GetReturnValue().Set(v8_num(900));
|
|
}
|
|
|
|
static void GetRino(Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
info.GetReturnValue().Set(v8_num(560));
|
|
}
|
|
|
|
enum ObjectInstantiationMode {
|
|
// Create object using ObjectTemplate::NewInstance.
|
|
ObjectTemplate_NewInstance,
|
|
// Create object using FunctionTemplate::NewInstance on constructor.
|
|
Constructor_GetFunction_NewInstance,
|
|
// Create object using new operator on constructor.
|
|
Constructor_GetFunction_New
|
|
};
|
|
|
|
// Test object instance creation using a function template with an instance
|
|
// template inherited from another function template with accessors and data
|
|
// properties in prototype template.
|
|
static void TestObjectTemplateInheritedWithPrototype(
|
|
ObjectInstantiationMode mode) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> fun_A = v8::FunctionTemplate::New(isolate);
|
|
fun_A->SetClassName(v8_str("A"));
|
|
v8::Local<v8::ObjectTemplate> prototype_templ = fun_A->PrototypeTemplate();
|
|
prototype_templ->Set(isolate, "a", v8_num(113));
|
|
prototype_templ->SetNativeDataProperty(v8_str("nirk"), GetNirk);
|
|
prototype_templ->Set(isolate, "b", v8_num(153));
|
|
|
|
Local<v8::FunctionTemplate> fun_B = v8::FunctionTemplate::New(isolate);
|
|
v8::Local<v8::String> class_name = v8_str("B");
|
|
fun_B->SetClassName(class_name);
|
|
fun_B->Inherit(fun_A);
|
|
prototype_templ = fun_B->PrototypeTemplate();
|
|
prototype_templ->Set(isolate, "c", v8_num(713));
|
|
prototype_templ->SetNativeDataProperty(v8_str("rino"), GetRino);
|
|
prototype_templ->Set(isolate, "d", v8_num(753));
|
|
|
|
Local<ObjectTemplate> templ = fun_B->InstanceTemplate();
|
|
templ->Set(isolate, "x", v8_num(10));
|
|
templ->Set(isolate, "y", v8_num(13));
|
|
|
|
// Perform several iterations to trigger creation from cached boilerplate.
|
|
for (int i = 0; i < 3; i++) {
|
|
Local<v8::Object> instance;
|
|
switch (mode) {
|
|
case ObjectTemplate_NewInstance:
|
|
instance = templ->NewInstance(env.local()).ToLocalChecked();
|
|
break;
|
|
|
|
case Constructor_GetFunction_NewInstance: {
|
|
Local<v8::Function> function_B =
|
|
fun_B->GetFunction(env.local()).ToLocalChecked();
|
|
instance = function_B->NewInstance(env.local()).ToLocalChecked();
|
|
break;
|
|
}
|
|
case Constructor_GetFunction_New: {
|
|
Local<v8::Function> function_B =
|
|
fun_B->GetFunction(env.local()).ToLocalChecked();
|
|
if (i == 0) {
|
|
CHECK(env->Global()
|
|
->Set(env.local(), class_name, function_B)
|
|
.FromJust());
|
|
}
|
|
instance =
|
|
CompileRun("new B()")->ToObject(env.local()).ToLocalChecked();
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
CHECK(class_name->StrictEquals(instance->GetConstructorName()));
|
|
CHECK(env->Global()->Set(env.local(), v8_str("o"), instance).FromJust());
|
|
|
|
CHECK_EQ(10, CompileRun("o.x")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(13, CompileRun("o.y")->IntegerValue(env.local()).FromJust());
|
|
|
|
CHECK_EQ(113, CompileRun("o.a")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(900, CompileRun("o.nirk")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(153, CompileRun("o.b")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(713, CompileRun("o.c")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(560, CompileRun("o.rino")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(753, CompileRun("o.d")->IntegerValue(env.local()).FromJust());
|
|
}
|
|
}
|
|
|
|
THREADED_TEST(TestObjectTemplateInheritedWithAccessorsInPrototype1) {
|
|
TestObjectTemplateInheritedWithPrototype(ObjectTemplate_NewInstance);
|
|
}
|
|
|
|
THREADED_TEST(TestObjectTemplateInheritedWithAccessorsInPrototype2) {
|
|
TestObjectTemplateInheritedWithPrototype(Constructor_GetFunction_NewInstance);
|
|
}
|
|
|
|
THREADED_TEST(TestObjectTemplateInheritedWithAccessorsInPrototype3) {
|
|
TestObjectTemplateInheritedWithPrototype(Constructor_GetFunction_New);
|
|
}
|
|
|
|
// Test object instance creation using a function template without an instance
|
|
// template inherited from another function template.
|
|
static void TestObjectTemplateInheritedWithoutInstanceTemplate(
|
|
ObjectInstantiationMode mode) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> fun_A = v8::FunctionTemplate::New(isolate);
|
|
fun_A->SetClassName(v8_str("A"));
|
|
|
|
Local<ObjectTemplate> templ_A = fun_A->InstanceTemplate();
|
|
templ_A->SetNativeDataProperty(v8_str("nirk"), GetNirk);
|
|
templ_A->SetNativeDataProperty(v8_str("rino"), GetRino);
|
|
|
|
Local<v8::FunctionTemplate> fun_B = v8::FunctionTemplate::New(isolate);
|
|
v8::Local<v8::String> class_name = v8_str("B");
|
|
fun_B->SetClassName(class_name);
|
|
fun_B->Inherit(fun_A);
|
|
|
|
// Perform several iterations to trigger creation from cached boilerplate.
|
|
for (int i = 0; i < 3; i++) {
|
|
Local<v8::Object> instance;
|
|
switch (mode) {
|
|
case Constructor_GetFunction_NewInstance: {
|
|
Local<v8::Function> function_B =
|
|
fun_B->GetFunction(env.local()).ToLocalChecked();
|
|
instance = function_B->NewInstance(env.local()).ToLocalChecked();
|
|
break;
|
|
}
|
|
case Constructor_GetFunction_New: {
|
|
Local<v8::Function> function_B =
|
|
fun_B->GetFunction(env.local()).ToLocalChecked();
|
|
if (i == 0) {
|
|
CHECK(env->Global()
|
|
->Set(env.local(), class_name, function_B)
|
|
.FromJust());
|
|
}
|
|
instance =
|
|
CompileRun("new B()")->ToObject(env.local()).ToLocalChecked();
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
CHECK(class_name->StrictEquals(instance->GetConstructorName()));
|
|
CHECK(env->Global()->Set(env.local(), v8_str("o"), instance).FromJust());
|
|
|
|
CHECK_EQ(900, CompileRun("o.nirk")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(560, CompileRun("o.rino")->IntegerValue(env.local()).FromJust());
|
|
}
|
|
}
|
|
|
|
THREADED_TEST(TestDataTypeChecks) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::Data> values[] = {
|
|
v8::Undefined(isolate),
|
|
v8::Null(isolate),
|
|
v8::True(isolate),
|
|
v8::Integer::New(isolate, 10),
|
|
v8::Number::New(isolate, 3.14),
|
|
v8::BigInt::NewFromUnsigned(isolate, 10),
|
|
v8::Symbol::New(isolate),
|
|
v8::String::NewFromUtf8Literal(isolate, "hello"),
|
|
};
|
|
for (auto x : values) {
|
|
CHECK(!x->IsModule());
|
|
CHECK(x->IsValue());
|
|
CHECK(!x->IsPrivate());
|
|
CHECK(!x->IsObjectTemplate());
|
|
CHECK(!x->IsFunctionTemplate());
|
|
v8::Local<v8::Value>::Cast(x);
|
|
x.As<v8::Value>();
|
|
}
|
|
|
|
v8::ScriptOrigin origin(isolate, v8_str(""), 0, 0, false, -1,
|
|
Local<v8::Value>(), false, false, true);
|
|
v8::ScriptCompiler::Source source(v8::String::NewFromUtf8Literal(isolate, ""),
|
|
origin);
|
|
v8::Local<v8::Data> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
|
|
CHECK(module->IsModule());
|
|
CHECK(!module->IsValue());
|
|
CHECK(!module->IsPrivate());
|
|
CHECK(!module->IsObjectTemplate());
|
|
CHECK(!module->IsFunctionTemplate());
|
|
v8::Local<v8::Module>::Cast(module);
|
|
module.As<v8::Module>();
|
|
|
|
v8::Local<v8::Data> p = v8::Private::New(isolate);
|
|
CHECK(!p->IsModule());
|
|
CHECK(!p->IsValue());
|
|
CHECK(p->IsPrivate());
|
|
CHECK(!p->IsObjectTemplate());
|
|
CHECK(!p->IsFunctionTemplate());
|
|
CHECK(!(*reinterpret_cast<Local<Value>*>(&p))->IsSymbol());
|
|
v8::Local<v8::Private>::Cast(p);
|
|
|
|
v8::Local<v8::Data> otmpl = v8::ObjectTemplate::New(isolate);
|
|
CHECK(!otmpl->IsModule());
|
|
CHECK(!otmpl->IsValue());
|
|
CHECK(!otmpl->IsPrivate());
|
|
CHECK(otmpl->IsObjectTemplate());
|
|
CHECK(!otmpl->IsFunctionTemplate());
|
|
|
|
v8::Local<v8::Data> ftmpl = v8::FunctionTemplate::New(isolate);
|
|
CHECK(!ftmpl->IsModule());
|
|
CHECK(!ftmpl->IsValue());
|
|
CHECK(!ftmpl->IsPrivate());
|
|
CHECK(!ftmpl->IsObjectTemplate());
|
|
CHECK(ftmpl->IsFunctionTemplate());
|
|
}
|
|
|
|
THREADED_TEST(TestObjectTemplateInheritedWithPrototype1) {
|
|
TestObjectTemplateInheritedWithoutInstanceTemplate(
|
|
Constructor_GetFunction_NewInstance);
|
|
}
|
|
|
|
THREADED_TEST(TestObjectTemplateInheritedWithPrototype2) {
|
|
TestObjectTemplateInheritedWithoutInstanceTemplate(
|
|
Constructor_GetFunction_New);
|
|
}
|
|
|
|
THREADED_TEST(TestObjectTemplateClassInheritance) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> fun_A = v8::FunctionTemplate::New(isolate);
|
|
fun_A->SetClassName(v8_str("A"));
|
|
|
|
Local<ObjectTemplate> templ_A = fun_A->InstanceTemplate();
|
|
templ_A->SetNativeDataProperty(v8_str("nirk"), GetNirk);
|
|
templ_A->SetNativeDataProperty(v8_str("rino"), GetRino);
|
|
|
|
Local<v8::FunctionTemplate> fun_B = v8::FunctionTemplate::New(isolate);
|
|
v8::Local<v8::String> class_name = v8_str("B");
|
|
fun_B->SetClassName(class_name);
|
|
fun_B->Inherit(fun_A);
|
|
|
|
v8::Local<v8::String> subclass_name = v8_str("C");
|
|
v8::Local<v8::Object> b_proto;
|
|
v8::Local<v8::Object> c_proto;
|
|
// Perform several iterations to make sure the cache doesn't break
|
|
// subclassing.
|
|
for (int i = 0; i < 3; i++) {
|
|
Local<v8::Function> function_B =
|
|
fun_B->GetFunction(env.local()).ToLocalChecked();
|
|
if (i == 0) {
|
|
CHECK(env->Global()->Set(env.local(), class_name, function_B).FromJust());
|
|
CompileRun("class C extends B {}");
|
|
b_proto =
|
|
CompileRun("B.prototype")->ToObject(env.local()).ToLocalChecked();
|
|
c_proto =
|
|
CompileRun("C.prototype")->ToObject(env.local()).ToLocalChecked();
|
|
CHECK(b_proto->Equals(env.local(), c_proto->GetPrototype()).FromJust());
|
|
}
|
|
Local<v8::Object> instance =
|
|
CompileRun("new C()")->ToObject(env.local()).ToLocalChecked();
|
|
CHECK(c_proto->Equals(env.local(), instance->GetPrototype()).FromJust());
|
|
|
|
CHECK(subclass_name->StrictEquals(instance->GetConstructorName()));
|
|
CHECK(env->Global()->Set(env.local(), v8_str("o"), instance).FromJust());
|
|
|
|
CHECK_EQ(900, CompileRun("o.nirk")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(560, CompileRun("o.rino")->IntegerValue(env.local()).FromJust());
|
|
}
|
|
}
|
|
|
|
static void NamedPropertyGetterWhichReturns42(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(v8_num(42));
|
|
}
|
|
|
|
THREADED_TEST(TestObjectTemplateReflectConstruct) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> fun_B = v8::FunctionTemplate::New(isolate);
|
|
fun_B->InstanceTemplate()->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(NamedPropertyGetterWhichReturns42));
|
|
v8::Local<v8::String> class_name = v8_str("B");
|
|
fun_B->SetClassName(class_name);
|
|
|
|
v8::Local<v8::String> subclass_name = v8_str("C");
|
|
v8::Local<v8::Object> b_proto;
|
|
v8::Local<v8::Object> c_proto;
|
|
// Perform several iterations to make sure the cache doesn't break
|
|
// subclassing.
|
|
for (int i = 0; i < 3; i++) {
|
|
Local<v8::Function> function_B =
|
|
fun_B->GetFunction(env.local()).ToLocalChecked();
|
|
if (i == 0) {
|
|
CHECK(env->Global()->Set(env.local(), class_name, function_B).FromJust());
|
|
CompileRun("function C() {}");
|
|
c_proto =
|
|
CompileRun("C.prototype")->ToObject(env.local()).ToLocalChecked();
|
|
}
|
|
Local<v8::Object> instance = CompileRun("Reflect.construct(B, [], C)")
|
|
->ToObject(env.local())
|
|
.ToLocalChecked();
|
|
CHECK(c_proto->Equals(env.local(), instance->GetPrototype()).FromJust());
|
|
|
|
CHECK(subclass_name->StrictEquals(instance->GetConstructorName()));
|
|
CHECK(env->Global()->Set(env.local(), v8_str("o"), instance).FromJust());
|
|
|
|
CHECK_EQ(42, CompileRun("o.nirk")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(42, CompileRun("o.rino")->IntegerValue(env.local()).FromJust());
|
|
}
|
|
}
|
|
|
|
static void GetFlabby(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
args.GetReturnValue().Set(v8_num(17.2));
|
|
}
|
|
|
|
|
|
static void GetKnurd(Local<String> property,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
info.GetReturnValue().Set(v8_num(15.2));
|
|
}
|
|
|
|
|
|
THREADED_TEST(DescriptorInheritance) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::FunctionTemplate> super = v8::FunctionTemplate::New(isolate);
|
|
super->PrototypeTemplate()->Set(isolate, "flabby",
|
|
v8::FunctionTemplate::New(isolate,
|
|
GetFlabby));
|
|
super->PrototypeTemplate()->Set(isolate, "PI", v8_num(3.14));
|
|
|
|
super->InstanceTemplate()->SetAccessor(v8_str("knurd"), GetKnurd);
|
|
|
|
v8::Local<v8::FunctionTemplate> base1 = v8::FunctionTemplate::New(isolate);
|
|
base1->Inherit(super);
|
|
base1->PrototypeTemplate()->Set(isolate, "v1", v8_num(20.1));
|
|
|
|
v8::Local<v8::FunctionTemplate> base2 = v8::FunctionTemplate::New(isolate);
|
|
base2->Inherit(super);
|
|
base2->PrototypeTemplate()->Set(isolate, "v2", v8_num(10.1));
|
|
|
|
LocalContext env;
|
|
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("s"),
|
|
super->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("base1"),
|
|
base1->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("base2"),
|
|
base2->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// Checks right __proto__ chain.
|
|
CHECK(CompileRun("base1.prototype.__proto__ == s.prototype")
|
|
->BooleanValue(isolate));
|
|
CHECK(CompileRun("base2.prototype.__proto__ == s.prototype")
|
|
->BooleanValue(isolate));
|
|
|
|
CHECK(v8_compile("s.prototype.PI == 3.14")
|
|
->Run(env.local())
|
|
.ToLocalChecked()
|
|
->BooleanValue(isolate));
|
|
|
|
// Instance accessor should not be visible on function object or its prototype
|
|
CHECK(CompileRun("s.knurd == undefined")->BooleanValue(isolate));
|
|
CHECK(CompileRun("s.prototype.knurd == undefined")->BooleanValue(isolate));
|
|
CHECK(
|
|
CompileRun("base1.prototype.knurd == undefined")->BooleanValue(isolate));
|
|
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("obj"), base1->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK_EQ(17.2,
|
|
CompileRun("obj.flabby()")->NumberValue(env.local()).FromJust());
|
|
CHECK(CompileRun("'flabby' in obj")->BooleanValue(isolate));
|
|
CHECK_EQ(15.2, CompileRun("obj.knurd")->NumberValue(env.local()).FromJust());
|
|
CHECK(CompileRun("'knurd' in obj")->BooleanValue(isolate));
|
|
CHECK_EQ(20.1, CompileRun("obj.v1")->NumberValue(env.local()).FromJust());
|
|
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("obj2"), base2->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK_EQ(17.2,
|
|
CompileRun("obj2.flabby()")->NumberValue(env.local()).FromJust());
|
|
CHECK(CompileRun("'flabby' in obj2")->BooleanValue(isolate));
|
|
CHECK_EQ(15.2, CompileRun("obj2.knurd")->NumberValue(env.local()).FromJust());
|
|
CHECK(CompileRun("'knurd' in obj2")->BooleanValue(isolate));
|
|
CHECK_EQ(10.1, CompileRun("obj2.v2")->NumberValue(env.local()).FromJust());
|
|
|
|
// base1 and base2 cannot cross reference to each's prototype
|
|
CHECK(CompileRun("obj.v2")->IsUndefined());
|
|
CHECK(CompileRun("obj2.v1")->IsUndefined());
|
|
}
|
|
|
|
THREADED_TEST(DescriptorInheritance2) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::FunctionTemplate> fun_A = v8::FunctionTemplate::New(isolate);
|
|
fun_A->SetClassName(v8_str("A"));
|
|
fun_A->InstanceTemplate()->SetNativeDataProperty(v8_str("knurd1"), GetKnurd);
|
|
fun_A->InstanceTemplate()->SetNativeDataProperty(v8_str("nirk1"), GetNirk);
|
|
fun_A->InstanceTemplate()->SetNativeDataProperty(v8_str("rino1"), GetRino);
|
|
|
|
v8::Local<v8::FunctionTemplate> fun_B = v8::FunctionTemplate::New(isolate);
|
|
fun_B->SetClassName(v8_str("B"));
|
|
fun_B->Inherit(fun_A);
|
|
|
|
v8::Local<v8::FunctionTemplate> fun_C = v8::FunctionTemplate::New(isolate);
|
|
fun_C->SetClassName(v8_str("C"));
|
|
fun_C->Inherit(fun_B);
|
|
fun_C->InstanceTemplate()->SetNativeDataProperty(v8_str("knurd2"), GetKnurd);
|
|
fun_C->InstanceTemplate()->SetNativeDataProperty(v8_str("nirk2"), GetNirk);
|
|
fun_C->InstanceTemplate()->SetNativeDataProperty(v8_str("rino2"), GetRino);
|
|
|
|
v8::Local<v8::FunctionTemplate> fun_D = v8::FunctionTemplate::New(isolate);
|
|
fun_D->SetClassName(v8_str("D"));
|
|
fun_D->Inherit(fun_C);
|
|
|
|
v8::Local<v8::FunctionTemplate> fun_E = v8::FunctionTemplate::New(isolate);
|
|
fun_E->SetClassName(v8_str("E"));
|
|
fun_E->Inherit(fun_D);
|
|
fun_E->InstanceTemplate()->SetNativeDataProperty(v8_str("knurd3"), GetKnurd);
|
|
fun_E->InstanceTemplate()->SetNativeDataProperty(v8_str("nirk3"), GetNirk);
|
|
fun_E->InstanceTemplate()->SetNativeDataProperty(v8_str("rino3"), GetRino);
|
|
|
|
v8::Local<v8::FunctionTemplate> fun_F = v8::FunctionTemplate::New(isolate);
|
|
fun_F->SetClassName(v8_str("F"));
|
|
fun_F->Inherit(fun_E);
|
|
v8::Local<v8::ObjectTemplate> templ = fun_F->InstanceTemplate();
|
|
const int kDataPropertiesNumber = 100;
|
|
for (int i = 0; i < kDataPropertiesNumber; i++) {
|
|
v8::Local<v8::Value> val = v8_num(i);
|
|
v8::Local<v8::String> val_str = val->ToString(env.local()).ToLocalChecked();
|
|
v8::Local<v8::String> name = String::Concat(isolate, v8_str("p"), val_str);
|
|
|
|
templ->Set(name, val);
|
|
templ->Set(val_str, val);
|
|
}
|
|
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("F"),
|
|
fun_F->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
v8::Local<v8::Script> script = v8_compile("o = new F()");
|
|
|
|
for (int i = 0; i < 100; i++) {
|
|
v8::HandleScope inner_scope(isolate);
|
|
script->Run(env.local()).ToLocalChecked();
|
|
}
|
|
v8::Local<v8::Object> object = script->Run(env.local())
|
|
.ToLocalChecked()
|
|
->ToObject(env.local())
|
|
.ToLocalChecked();
|
|
|
|
CHECK_EQ(15.2, CompileRun("o.knurd1")->NumberValue(env.local()).FromJust());
|
|
CHECK_EQ(15.2, CompileRun("o.knurd2")->NumberValue(env.local()).FromJust());
|
|
CHECK_EQ(15.2, CompileRun("o.knurd3")->NumberValue(env.local()).FromJust());
|
|
|
|
CHECK_EQ(900, CompileRun("o.nirk1")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(900, CompileRun("o.nirk2")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(900, CompileRun("o.nirk3")->IntegerValue(env.local()).FromJust());
|
|
|
|
CHECK_EQ(560, CompileRun("o.rino1")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(560, CompileRun("o.rino2")->IntegerValue(env.local()).FromJust());
|
|
CHECK_EQ(560, CompileRun("o.rino3")->IntegerValue(env.local()).FromJust());
|
|
|
|
for (int i = 0; i < kDataPropertiesNumber; i++) {
|
|
v8::Local<v8::Value> val = v8_num(i);
|
|
v8::Local<v8::String> val_str = val->ToString(env.local()).ToLocalChecked();
|
|
v8::Local<v8::String> name = String::Concat(isolate, v8_str("p"), val_str);
|
|
|
|
CHECK_EQ(i, object->Get(env.local(), name)
|
|
.ToLocalChecked()
|
|
->IntegerValue(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(i, object->Get(env.local(), val)
|
|
.ToLocalChecked()
|
|
->IntegerValue(env.local())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
// Helper functions for Interceptor/Accessor interaction tests
|
|
|
|
void SimpleAccessorGetter(Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
Local<Object> self = info.This().As<Object>();
|
|
info.GetReturnValue().Set(
|
|
self->Get(info.GetIsolate()->GetCurrentContext(),
|
|
String::Concat(info.GetIsolate(), v8_str("accessor_"), name))
|
|
.ToLocalChecked());
|
|
}
|
|
|
|
void SimpleAccessorSetter(Local<String> name, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<void>& info) {
|
|
Local<Object> self = info.This().As<Object>();
|
|
CHECK(self->Set(info.GetIsolate()->GetCurrentContext(),
|
|
String::Concat(info.GetIsolate(), v8_str("accessor_"), name),
|
|
value)
|
|
.FromJust());
|
|
}
|
|
|
|
void SymbolAccessorGetter(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(name->IsSymbol());
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
Local<Symbol> sym = name.As<Symbol>();
|
|
if (sym->Description(isolate)->IsUndefined()) return;
|
|
SimpleAccessorGetter(Local<String>::Cast(sym->Description(isolate)), info);
|
|
}
|
|
|
|
void SymbolAccessorSetter(Local<Name> name, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<void>& info) {
|
|
CHECK(name->IsSymbol());
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
Local<Symbol> sym = name.As<Symbol>();
|
|
if (sym->Description(isolate)->IsUndefined()) return;
|
|
SimpleAccessorSetter(Local<String>::Cast(sym->Description(isolate)), value,
|
|
info);
|
|
}
|
|
|
|
void SymbolAccessorGetterReturnsDefault(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(name->IsSymbol());
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
Local<Symbol> sym = name.As<Symbol>();
|
|
if (sym->Description(isolate)->IsUndefined()) return;
|
|
info.GetReturnValue().Set(info.Data());
|
|
}
|
|
|
|
static void ThrowingSymbolAccessorGetter(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(info.GetIsolate()->ThrowException(name));
|
|
}
|
|
|
|
|
|
THREADED_TEST(AccessorIsPreservedOnAttributeChange) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
i::Isolate* i_isolate = CcTest::i_isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
v8::Local<v8::Value> res = CompileRun("var a = []; a;");
|
|
i::Handle<i::JSReceiver> a(v8::Utils::OpenHandle(v8::Object::Cast(*res)));
|
|
CHECK_EQ(1, a->map().instance_descriptors(i_isolate).number_of_descriptors());
|
|
CompileRun("Object.defineProperty(a, 'length', { writable: false });");
|
|
CHECK_EQ(0, a->map().instance_descriptors(i_isolate).number_of_descriptors());
|
|
// But we should still have an AccessorInfo.
|
|
i::Handle<i::String> name = i_isolate->factory()->length_string();
|
|
i::LookupIterator it(i_isolate, a, name,
|
|
i::LookupIterator::OWN_SKIP_INTERCEPTOR);
|
|
CHECK_EQ(i::LookupIterator::ACCESSOR, it.state());
|
|
CHECK(it.GetAccessors()->IsAccessorInfo());
|
|
}
|
|
|
|
|
|
THREADED_TEST(UndefinedIsNotEnumerable) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Local<Value> result = CompileRun("this.propertyIsEnumerable(undefined)");
|
|
CHECK(result->IsFalse());
|
|
}
|
|
|
|
|
|
v8::Local<Script> call_recursively_script;
|
|
static const int kTargetRecursionDepth = 100; // near maximum
|
|
|
|
static void CallScriptRecursivelyCall(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
|
|
int depth = args.This()
|
|
->Get(context, v8_str("depth"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust();
|
|
if (depth == kTargetRecursionDepth) return;
|
|
CHECK(args.This()
|
|
->Set(context, v8_str("depth"),
|
|
v8::Integer::New(args.GetIsolate(), depth + 1))
|
|
.FromJust());
|
|
args.GetReturnValue().Set(
|
|
call_recursively_script->Run(context).ToLocalChecked());
|
|
}
|
|
|
|
|
|
static void CallFunctionRecursivelyCall(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
|
|
int depth = args.This()
|
|
->Get(context, v8_str("depth"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust();
|
|
if (depth == kTargetRecursionDepth) {
|
|
printf("[depth = %d]\n", depth);
|
|
return;
|
|
}
|
|
CHECK(args.This()
|
|
->Set(context, v8_str("depth"),
|
|
v8::Integer::New(args.GetIsolate(), depth + 1))
|
|
.FromJust());
|
|
v8::Local<Value> function =
|
|
args.This()
|
|
->Get(context, v8_str("callFunctionRecursively"))
|
|
.ToLocalChecked();
|
|
args.GetReturnValue().Set(function.As<Function>()
|
|
->Call(context, args.This(), 0, nullptr)
|
|
.ToLocalChecked());
|
|
}
|
|
|
|
|
|
THREADED_TEST(DeepCrossLanguageRecursion) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> global = ObjectTemplate::New(isolate);
|
|
global->Set(isolate, "callScriptRecursively",
|
|
v8::FunctionTemplate::New(isolate, CallScriptRecursivelyCall));
|
|
global->Set(isolate, "callFunctionRecursively",
|
|
v8::FunctionTemplate::New(isolate, CallFunctionRecursivelyCall));
|
|
LocalContext env(nullptr, global);
|
|
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("depth"), v8::Integer::New(isolate, 0))
|
|
.FromJust());
|
|
call_recursively_script = v8_compile("callScriptRecursively()");
|
|
call_recursively_script->Run(env.local()).ToLocalChecked();
|
|
call_recursively_script = v8::Local<Script>();
|
|
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("depth"), v8::Integer::New(isolate, 0))
|
|
.FromJust());
|
|
CompileRun("callFunctionRecursively()");
|
|
}
|
|
|
|
|
|
static void ThrowingPropertyHandlerGet(
|
|
Local<Name> key, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
// Since this interceptor is used on "with" objects, the runtime will look up
|
|
// @@unscopables. Punt.
|
|
if (key->IsSymbol()) return;
|
|
ApiTestFuzzer::Fuzz();
|
|
info.GetReturnValue().Set(info.GetIsolate()->ThrowException(key));
|
|
}
|
|
|
|
|
|
static void ThrowingPropertyHandlerSet(
|
|
Local<Name> key, Local<Value>,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetIsolate()->ThrowException(key);
|
|
info.GetReturnValue().SetUndefined(); // not the same as empty handle
|
|
}
|
|
|
|
|
|
THREADED_TEST(CallbackExceptionRegression) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
|
|
obj->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
ThrowingPropertyHandlerGet, ThrowingPropertyHandlerSet));
|
|
LocalContext env;
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("obj"),
|
|
obj->NewInstance(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
v8::Local<Value> otto =
|
|
CompileRun("try { with (obj) { otto; } } catch (e) { e; }");
|
|
CHECK(v8_str("otto")->Equals(env.local(), otto).FromJust());
|
|
v8::Local<Value> netto =
|
|
CompileRun("try { with (obj) { netto = 4; } } catch (e) { e; }");
|
|
CHECK(v8_str("netto")->Equals(env.local(), netto).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionPrototype) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<v8::FunctionTemplate> Foo = v8::FunctionTemplate::New(isolate);
|
|
Foo->PrototypeTemplate()->Set(isolate, "plak", v8_num(321));
|
|
LocalContext env;
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("Foo"),
|
|
Foo->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
Local<Script> script = v8_compile("Foo.prototype.plak");
|
|
CHECK_EQ(v8_run_int32value(script), 321);
|
|
}
|
|
|
|
THREADED_TEST(InternalFields) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
Local<v8::ObjectTemplate> instance_templ = templ->InstanceTemplate();
|
|
instance_templ->SetInternalFieldCount(1);
|
|
Local<v8::Object> obj = templ->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked();
|
|
CHECK_EQ(1, obj->InternalFieldCount());
|
|
CHECK(obj->GetInternalField(0)->IsUndefined());
|
|
obj->SetInternalField(0, v8_num(17));
|
|
CHECK_EQ(17, obj->GetInternalField(0)->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
TEST(InternalFieldsSubclassing) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
for (int nof_embedder_fields = 0;
|
|
nof_embedder_fields < i::JSObject::kMaxEmbedderFields;
|
|
nof_embedder_fields++) {
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
Local<v8::ObjectTemplate> instance_templ = templ->InstanceTemplate();
|
|
instance_templ->SetInternalFieldCount(nof_embedder_fields);
|
|
Local<Function> constructor =
|
|
templ->GetFunction(env.local()).ToLocalChecked();
|
|
// Check that instances have the correct NOF properties.
|
|
Local<v8::Object> obj =
|
|
constructor->NewInstance(env.local()).ToLocalChecked();
|
|
|
|
i::Handle<i::JSObject> i_obj =
|
|
i::Handle<i::JSObject>::cast(v8::Utils::OpenHandle(*obj));
|
|
CHECK_EQ(nof_embedder_fields, obj->InternalFieldCount());
|
|
CHECK_EQ(0, i_obj->map().GetInObjectProperties());
|
|
// Check writing and reading internal fields.
|
|
for (int j = 0; j < nof_embedder_fields; j++) {
|
|
CHECK(obj->GetInternalField(j)->IsUndefined());
|
|
int value = 17 + j;
|
|
obj->SetInternalField(j, v8_num(value));
|
|
}
|
|
for (int j = 0; j < nof_embedder_fields; j++) {
|
|
int value = 17 + j;
|
|
CHECK_EQ(value,
|
|
obj->GetInternalField(j)->Int32Value(env.local()).FromJust());
|
|
}
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("BaseClass"), constructor)
|
|
.FromJust());
|
|
// Create various levels of subclasses to stress instance size calculation.
|
|
const int kMaxNofProperties =
|
|
i::JSObject::kMaxInObjectProperties -
|
|
nof_embedder_fields * i::kEmbedderDataSlotSizeInTaggedSlots;
|
|
// Select only a few values to speed up the test.
|
|
int sizes[] = {0,
|
|
1,
|
|
2,
|
|
3,
|
|
4,
|
|
5,
|
|
6,
|
|
kMaxNofProperties / 4,
|
|
kMaxNofProperties / 2,
|
|
kMaxNofProperties - 2,
|
|
kMaxNofProperties - 1,
|
|
kMaxNofProperties + 1,
|
|
kMaxNofProperties + 2,
|
|
kMaxNofProperties * 2,
|
|
kMaxNofProperties * 2};
|
|
for (size_t i = 0; i < arraysize(sizes); i++) {
|
|
int nof_properties = sizes[i];
|
|
bool in_object_only = nof_properties <= kMaxNofProperties;
|
|
std::ostringstream src;
|
|
// Assembler source string for a subclass with {nof_properties}
|
|
// in-object properties.
|
|
src << "(function() {\n"
|
|
<< " class SubClass extends BaseClass {\n"
|
|
<< " constructor() {\n"
|
|
<< " super();\n";
|
|
// Set {nof_properties} instance properties in the constructor.
|
|
for (int j = 0; j < nof_properties; j++) {
|
|
src << " this.property" << j << " = " << j << ";\n";
|
|
}
|
|
src << " }\n"
|
|
<< " };\n"
|
|
<< " let instance;\n"
|
|
<< " for (let i = 0; i < 3; i++) {\n"
|
|
<< " instance = new SubClass();\n"
|
|
<< " }"
|
|
<< " return instance;\n"
|
|
<< "})();";
|
|
Local<v8::Object> value = CompileRun(src.str().c_str()).As<v8::Object>();
|
|
|
|
i::Handle<i::JSObject> i_value =
|
|
i::Handle<i::JSObject>::cast(v8::Utils::OpenHandle(*value));
|
|
#ifdef VERIFY_HEAP
|
|
i_value->HeapObjectVerify(i_isolate);
|
|
i_value->map().HeapObjectVerify(i_isolate);
|
|
i_value->map().FindRootMap(i_isolate).HeapObjectVerify(i_isolate);
|
|
#endif
|
|
CHECK_EQ(nof_embedder_fields, value->InternalFieldCount());
|
|
if (in_object_only) {
|
|
CHECK_LE(nof_properties, i_value->map().GetInObjectProperties());
|
|
} else {
|
|
CHECK_LE(i_value->map().GetInObjectProperties(), kMaxNofProperties);
|
|
}
|
|
|
|
// Make sure we get the precise property count.
|
|
i::MapUpdater::CompleteInobjectSlackTracking(
|
|
i_isolate, i_value->map().FindRootMap(i_isolate));
|
|
// TODO(cbruni): fix accounting to make this condition true.
|
|
// CHECK_EQ(0, i_value->map()->UnusedPropertyFields());
|
|
if (in_object_only) {
|
|
CHECK_EQ(nof_properties, i_value->map().GetInObjectProperties());
|
|
} else {
|
|
CHECK_LE(i_value->map().GetInObjectProperties(), kMaxNofProperties);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
THREADED_TEST(InternalFieldsOfRegularObjects) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
const char* sources[] = {"new Object()", "{ a: 'a property' }", "arguments"};
|
|
for (size_t i = 0; i < arraysize(sources); ++i) {
|
|
v8::base::ScopedVector<char> source(128);
|
|
v8::base::SNPrintF(source, "(function() { return %s })()", sources[i]);
|
|
v8::Local<v8::Object> obj = CompileRun(source.begin()).As<v8::Object>();
|
|
CHECK_EQ(0, obj->InternalFieldCount());
|
|
}
|
|
}
|
|
|
|
THREADED_TEST(GlobalObjectInternalFields) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(isolate);
|
|
global_template->SetInternalFieldCount(1);
|
|
LocalContext env(nullptr, global_template);
|
|
v8::Local<v8::Object> global_proxy = env->Global();
|
|
v8::Local<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(env.local()).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(GlobalObjectHasRealIndexedProperty) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
|
|
v8::Local<v8::Object> global = env->Global();
|
|
CHECK(global->Set(env.local(), 0, v8_str("value")).FromJust());
|
|
CHECK(global->HasRealIndexedProperty(env.local(), 0).FromJust());
|
|
}
|
|
|
|
static void CheckAlignedPointerInInternalField(Local<v8::Object> obj,
|
|
void* value) {
|
|
CHECK(HAS_SMI_TAG(reinterpret_cast<i::Address>(value)));
|
|
obj->SetAlignedPointerInInternalField(0, value);
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(value, obj->GetAlignedPointerFromInternalField(0));
|
|
}
|
|
|
|
THREADED_TEST(InternalFieldsAlignedPointers) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
Local<v8::ObjectTemplate> instance_templ = templ->InstanceTemplate();
|
|
instance_templ->SetInternalFieldCount(1);
|
|
Local<v8::Object> obj = templ->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked();
|
|
CHECK_EQ(1, obj->InternalFieldCount());
|
|
|
|
CheckAlignedPointerInInternalField(obj, nullptr);
|
|
|
|
int* heap_allocated = new int[100];
|
|
CheckAlignedPointerInInternalField(obj, heap_allocated);
|
|
delete[] heap_allocated;
|
|
|
|
int stack_allocated[100];
|
|
CheckAlignedPointerInInternalField(obj, stack_allocated);
|
|
|
|
// The aligned pointer must have the top bits be zero on 64-bit machines (at
|
|
// least if the sandboxed external pointers are enabled).
|
|
void* huge = reinterpret_cast<void*>(0x0000fffffffffffe);
|
|
CheckAlignedPointerInInternalField(obj, huge);
|
|
|
|
v8::Global<v8::Object> persistent(isolate, obj);
|
|
CHECK_EQ(1, Object::InternalFieldCount(persistent));
|
|
CHECK_EQ(huge, Object::GetAlignedPointerFromInternalField(persistent, 0));
|
|
}
|
|
|
|
THREADED_TEST(SetAlignedPointerInInternalFields) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
Local<v8::ObjectTemplate> instance_templ = templ->InstanceTemplate();
|
|
instance_templ->SetInternalFieldCount(2);
|
|
Local<v8::Object> obj = templ->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked();
|
|
CHECK_EQ(2, obj->InternalFieldCount());
|
|
|
|
int* heap_allocated_1 = new int[100];
|
|
int* heap_allocated_2 = new int[100];
|
|
int indices[] = {0, 1};
|
|
void* values[] = {heap_allocated_1, heap_allocated_2};
|
|
|
|
obj->SetAlignedPointerInInternalFields(2, indices, values);
|
|
CcTest::CollectAllGarbage();
|
|
{
|
|
v8::SealHandleScope no_handle_leak(isolate);
|
|
CHECK_EQ(heap_allocated_1, obj->GetAlignedPointerFromInternalField(0));
|
|
CHECK_EQ(heap_allocated_2, obj->GetAlignedPointerFromInternalField(1));
|
|
}
|
|
|
|
indices[0] = 1;
|
|
indices[1] = 0;
|
|
obj->SetAlignedPointerInInternalFields(2, indices, values);
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(heap_allocated_2, obj->GetAlignedPointerFromInternalField(0));
|
|
CHECK_EQ(heap_allocated_1, obj->GetAlignedPointerFromInternalField(1));
|
|
|
|
delete[] heap_allocated_1;
|
|
delete[] heap_allocated_2;
|
|
}
|
|
|
|
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);
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(value, (*env)->GetAlignedPointerFromEmbedderData(index));
|
|
}
|
|
|
|
|
|
static void* AlignedTestPointer(int i) {
|
|
return reinterpret_cast<void*>(i * 1234);
|
|
}
|
|
|
|
|
|
THREADED_TEST(EmbedderDataAlignedPointers) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
|
|
CheckAlignedPointerInEmbedderData(&env, 0, nullptr);
|
|
CHECK_EQ(1, (*env)->GetNumberOfEmbedderDataFields());
|
|
|
|
int* heap_allocated = new int[100];
|
|
CheckAlignedPointerInEmbedderData(&env, 1, heap_allocated);
|
|
CHECK_EQ(2, (*env)->GetNumberOfEmbedderDataFields());
|
|
delete[] heap_allocated;
|
|
|
|
int stack_allocated[100];
|
|
CheckAlignedPointerInEmbedderData(&env, 2, stack_allocated);
|
|
CHECK_EQ(3, (*env)->GetNumberOfEmbedderDataFields());
|
|
|
|
// The aligned pointer must have the top bits be zero on 64-bit machines (at
|
|
// least if the sandboxed external pointers are enabled).
|
|
void* huge = reinterpret_cast<void*>(0x0000fffffffffffe);
|
|
CheckAlignedPointerInEmbedderData(&env, 3, huge);
|
|
CHECK_EQ(4, (*env)->GetNumberOfEmbedderDataFields());
|
|
|
|
// Test growing of the embedder data's backing store.
|
|
for (int i = 0; i < 100; i++) {
|
|
env->SetAlignedPointerInEmbedderData(i, AlignedTestPointer(i));
|
|
}
|
|
CcTest::CollectAllGarbage();
|
|
for (int i = 0; i < 100; i++) {
|
|
v8::SealHandleScope no_handle_leak(env->GetIsolate());
|
|
CHECK_EQ(AlignedTestPointer(i), env->GetAlignedPointerFromEmbedderData(i));
|
|
}
|
|
}
|
|
|
|
static void CheckEmbedderData(LocalContext* env, int index,
|
|
v8::Local<Value> data) {
|
|
(*env)->SetEmbedderData(index, data);
|
|
CHECK((*env)->GetEmbedderData(index)->StrictEquals(data));
|
|
}
|
|
|
|
|
|
THREADED_TEST(EmbedderData) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
CHECK_EQ(0, (*env)->GetNumberOfEmbedderDataFields());
|
|
CheckEmbedderData(&env, 3, v8_str("The quick brown fox jumps"));
|
|
CHECK_EQ(4, (*env)->GetNumberOfEmbedderDataFields());
|
|
CheckEmbedderData(&env, 2, v8_str("over the lazy dog."));
|
|
CHECK_EQ(4, (*env)->GetNumberOfEmbedderDataFields());
|
|
CheckEmbedderData(&env, 1, v8::Number::New(isolate, 1.2345));
|
|
CHECK_EQ(4, (*env)->GetNumberOfEmbedderDataFields());
|
|
CheckEmbedderData(&env, 0, v8::Boolean::New(isolate, true));
|
|
CHECK_EQ(4, (*env)->GetNumberOfEmbedderDataFields());
|
|
CheckEmbedderData(&env, 211, v8::Boolean::New(isolate, true));
|
|
CHECK_EQ(212, (*env)->GetNumberOfEmbedderDataFields());
|
|
}
|
|
|
|
|
|
THREADED_TEST(IdentityHash) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Ensure that the test starts with an fresh heap to test whether the hash
|
|
// code is based on the address.
|
|
CcTest::CollectAllGarbage();
|
|
Local<v8::Object> obj = v8::Object::New(isolate);
|
|
int hash = obj->GetIdentityHash();
|
|
int hash1 = obj->GetIdentityHash();
|
|
CHECK_EQ(hash, hash1);
|
|
int hash2 = v8::Object::New(isolate)->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);
|
|
CcTest::CollectAllGarbage();
|
|
int hash3 = v8::Object::New(isolate)->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(isolate);
|
|
Local<v8::Object> o2 = v8::Object::New(isolate);
|
|
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(isolate);
|
|
Local<v8::Object> o2 = v8::Object::New(isolate);
|
|
CHECK_NE(o1->GetIdentityHash(), o2->GetIdentityHash());
|
|
}
|
|
}
|
|
|
|
|
|
void GlobalProxyIdentityHash(bool set_in_js) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
Local<Object> global_proxy = env->Global();
|
|
i::Handle<i::Object> i_global_proxy = v8::Utils::OpenHandle(*global_proxy);
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("global"), global_proxy)
|
|
.FromJust());
|
|
int32_t hash1;
|
|
if (set_in_js) {
|
|
CompileRun("var m = new Set(); m.add(global);");
|
|
i::Object original_hash = i_global_proxy->GetHash();
|
|
CHECK(original_hash.IsSmi());
|
|
hash1 = i::Smi::ToInt(original_hash);
|
|
} else {
|
|
hash1 = i_global_proxy->GetOrCreateHash(i_isolate).value();
|
|
}
|
|
// Hash should be retained after being detached.
|
|
env->DetachGlobal();
|
|
int hash2 = global_proxy->GetIdentityHash();
|
|
CHECK_EQ(hash1, hash2);
|
|
{
|
|
// Re-attach global proxy to a new context, hash should stay the same.
|
|
LocalContext env2(nullptr, Local<ObjectTemplate>(), global_proxy);
|
|
int hash3 = global_proxy->GetIdentityHash();
|
|
CHECK_EQ(hash1, hash3);
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(GlobalProxyIdentityHash) {
|
|
GlobalProxyIdentityHash(true);
|
|
GlobalProxyIdentityHash(false);
|
|
}
|
|
|
|
|
|
TEST(SymbolIdentityHash) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
{
|
|
Local<v8::Symbol> symbol = v8::Symbol::New(isolate);
|
|
int hash = symbol->GetIdentityHash();
|
|
int hash1 = symbol->GetIdentityHash();
|
|
CHECK_EQ(hash, hash1);
|
|
CcTest::CollectAllGarbage();
|
|
int hash3 = symbol->GetIdentityHash();
|
|
CHECK_EQ(hash, hash3);
|
|
}
|
|
|
|
{
|
|
v8::Local<v8::Symbol> js_symbol =
|
|
CompileRun("Symbol('foo')").As<v8::Symbol>();
|
|
int hash = js_symbol->GetIdentityHash();
|
|
int hash1 = js_symbol->GetIdentityHash();
|
|
CHECK_EQ(hash, hash1);
|
|
CcTest::CollectAllGarbage();
|
|
int hash3 = js_symbol->GetIdentityHash();
|
|
CHECK_EQ(hash, hash3);
|
|
}
|
|
}
|
|
|
|
|
|
TEST(StringIdentityHash) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::String> str = v8_str("str1");
|
|
int hash = str->GetIdentityHash();
|
|
int hash1 = str->GetIdentityHash();
|
|
CHECK_EQ(hash, hash1);
|
|
CcTest::CollectAllGarbage();
|
|
int hash3 = str->GetIdentityHash();
|
|
CHECK_EQ(hash, hash3);
|
|
|
|
Local<v8::String> str2 = v8_str("str1");
|
|
int hash4 = str2->GetIdentityHash();
|
|
CHECK_EQ(hash, hash4);
|
|
}
|
|
|
|
|
|
THREADED_TEST(SymbolProperties) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
|
v8::Local<v8::Symbol> sym1 = v8::Symbol::New(isolate);
|
|
v8::Local<v8::Symbol> sym2 = v8::Symbol::New(isolate, v8_str("my-symbol"));
|
|
v8::Local<v8::Symbol> sym3 = v8::Symbol::New(isolate, v8_str("sym3"));
|
|
v8::Local<v8::Symbol> sym4 = v8::Symbol::New(isolate, v8_str("native"));
|
|
|
|
CcTest::CollectAllGarbage();
|
|
|
|
// Check basic symbol functionality.
|
|
CHECK(sym1->IsSymbol());
|
|
CHECK(sym2->IsSymbol());
|
|
CHECK(!obj->IsSymbol());
|
|
|
|
CHECK(sym1->Equals(env.local(), sym1).FromJust());
|
|
CHECK(sym2->Equals(env.local(), sym2).FromJust());
|
|
CHECK(!sym1->Equals(env.local(), sym2).FromJust());
|
|
CHECK(!sym2->Equals(env.local(), sym1).FromJust());
|
|
CHECK(sym1->StrictEquals(sym1));
|
|
CHECK(sym2->StrictEquals(sym2));
|
|
CHECK(!sym1->StrictEquals(sym2));
|
|
CHECK(!sym2->StrictEquals(sym1));
|
|
|
|
CHECK(sym2->Description(isolate)
|
|
->Equals(env.local(), v8_str("my-symbol"))
|
|
.FromJust());
|
|
|
|
v8::Local<v8::Value> sym_val = sym2;
|
|
CHECK(sym_val->IsSymbol());
|
|
CHECK(sym_val->Equals(env.local(), sym2).FromJust());
|
|
CHECK(sym_val->StrictEquals(sym2));
|
|
CHECK(v8::Symbol::Cast(*sym_val)->Equals(env.local(), sym2).FromJust());
|
|
|
|
v8::Local<v8::Value> sym_obj = v8::SymbolObject::New(isolate, sym2);
|
|
CHECK(sym_obj->IsSymbolObject());
|
|
CHECK(!sym2->IsSymbolObject());
|
|
CHECK(!obj->IsSymbolObject());
|
|
CHECK(sym_obj->Equals(env.local(), sym2).FromJust());
|
|
CHECK(!sym_obj->StrictEquals(sym2));
|
|
CHECK(v8::SymbolObject::Cast(*sym_obj)
|
|
->Equals(env.local(), sym_obj)
|
|
.FromJust());
|
|
CHECK(v8::SymbolObject::Cast(*sym_obj)
|
|
->ValueOf()
|
|
->Equals(env.local(), sym2)
|
|
.FromJust());
|
|
|
|
// Make sure delete of a non-existent symbol property works.
|
|
CHECK(obj->Delete(env.local(), sym1).FromJust());
|
|
CHECK(!obj->Has(env.local(), sym1).FromJust());
|
|
|
|
CHECK(
|
|
obj->Set(env.local(), sym1, v8::Integer::New(isolate, 1503)).FromJust());
|
|
CHECK(obj->Has(env.local(), sym1).FromJust());
|
|
CHECK_EQ(1503, obj->Get(env.local(), sym1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK(
|
|
obj->Set(env.local(), sym1, v8::Integer::New(isolate, 2002)).FromJust());
|
|
CHECK(obj->Has(env.local(), sym1).FromJust());
|
|
CHECK_EQ(2002, obj->Get(env.local(), sym1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(v8::None, obj->GetPropertyAttributes(env.local(), sym1).FromJust());
|
|
|
|
CHECK_EQ(0u,
|
|
obj->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
unsigned num_props =
|
|
obj->GetPropertyNames(env.local()).ToLocalChecked()->Length();
|
|
CHECK(obj->Set(env.local(), v8_str("bla"), v8::Integer::New(isolate, 20))
|
|
.FromJust());
|
|
CHECK_EQ(1u,
|
|
obj->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
CHECK_EQ(num_props + 1,
|
|
obj->GetPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
|
|
CcTest::CollectAllGarbage();
|
|
|
|
CHECK(obj->SetAccessor(env.local(), sym3, SymbolAccessorGetter,
|
|
SymbolAccessorSetter)
|
|
.FromJust());
|
|
CHECK(obj->Get(env.local(), sym3).ToLocalChecked()->IsUndefined());
|
|
CHECK(obj->Set(env.local(), sym3, v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(obj->Get(env.local(), sym3)
|
|
.ToLocalChecked()
|
|
->Equals(env.local(), v8::Integer::New(isolate, 42))
|
|
.FromJust());
|
|
CHECK(obj->Get(env.local(), v8_str("accessor_sym3"))
|
|
.ToLocalChecked()
|
|
->Equals(env.local(), v8::Integer::New(isolate, 42))
|
|
.FromJust());
|
|
|
|
CHECK(obj->SetNativeDataProperty(env.local(), sym4, SymbolAccessorGetter)
|
|
.FromJust());
|
|
CHECK(obj->Get(env.local(), sym4).ToLocalChecked()->IsUndefined());
|
|
CHECK(obj->Set(env.local(), v8_str("accessor_native"),
|
|
v8::Integer::New(isolate, 123))
|
|
.FromJust());
|
|
CHECK_EQ(123, obj->Get(env.local(), sym4)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK(obj->Set(env.local(), sym4, v8::Integer::New(isolate, 314)).FromJust());
|
|
CHECK(obj->Get(env.local(), sym4)
|
|
.ToLocalChecked()
|
|
->Equals(env.local(), v8::Integer::New(isolate, 314))
|
|
.FromJust());
|
|
CHECK(obj->Delete(env.local(), v8_str("accessor_native")).FromJust());
|
|
|
|
// Add another property and delete it afterwards to force the object in
|
|
// slow case.
|
|
CHECK(
|
|
obj->Set(env.local(), sym2, v8::Integer::New(isolate, 2008)).FromJust());
|
|
CHECK_EQ(2002, obj->Get(env.local(), sym1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(2008, obj->Get(env.local(), sym2)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(2002, obj->Get(env.local(), sym1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(2u,
|
|
obj->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
|
|
CHECK(obj->Has(env.local(), sym1).FromJust());
|
|
CHECK(obj->Has(env.local(), sym2).FromJust());
|
|
CHECK(obj->Has(env.local(), sym3).FromJust());
|
|
CHECK(obj->Has(env.local(), v8_str("accessor_sym3")).FromJust());
|
|
CHECK(obj->Delete(env.local(), sym2).FromJust());
|
|
CHECK(obj->Has(env.local(), sym1).FromJust());
|
|
CHECK(!obj->Has(env.local(), sym2).FromJust());
|
|
CHECK(obj->Has(env.local(), sym3).FromJust());
|
|
CHECK(obj->Has(env.local(), v8_str("accessor_sym3")).FromJust());
|
|
CHECK_EQ(2002, obj->Get(env.local(), sym1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK(obj->Get(env.local(), sym3)
|
|
.ToLocalChecked()
|
|
->Equals(env.local(), v8::Integer::New(isolate, 42))
|
|
.FromJust());
|
|
CHECK(obj->Get(env.local(), v8_str("accessor_sym3"))
|
|
.ToLocalChecked()
|
|
->Equals(env.local(), v8::Integer::New(isolate, 42))
|
|
.FromJust());
|
|
CHECK_EQ(2u,
|
|
obj->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
|
|
// Symbol properties are inherited.
|
|
v8::Local<v8::Object> child = v8::Object::New(isolate);
|
|
CHECK(child->SetPrototype(env.local(), obj).FromJust());
|
|
CHECK(child->Has(env.local(), sym1).FromJust());
|
|
CHECK_EQ(2002, child->Get(env.local(), sym1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK(obj->Get(env.local(), sym3)
|
|
.ToLocalChecked()
|
|
->Equals(env.local(), v8::Integer::New(isolate, 42))
|
|
.FromJust());
|
|
CHECK(obj->Get(env.local(), v8_str("accessor_sym3"))
|
|
.ToLocalChecked()
|
|
->Equals(env.local(), v8::Integer::New(isolate, 42))
|
|
.FromJust());
|
|
CHECK_EQ(0u,
|
|
child->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
}
|
|
|
|
|
|
THREADED_TEST(SymbolTemplateProperties) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::FunctionTemplate> foo = v8::FunctionTemplate::New(isolate);
|
|
v8::Local<v8::Name> name = v8::Symbol::New(isolate);
|
|
CHECK(!name.IsEmpty());
|
|
foo->PrototypeTemplate()->Set(name, v8::FunctionTemplate::New(isolate));
|
|
v8::Local<v8::Object> new_instance =
|
|
foo->InstanceTemplate()->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(!new_instance.IsEmpty());
|
|
CHECK(new_instance->Has(env.local(), name).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(PrivatePropertiesOnProxies) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::Object> target = CompileRun("({})").As<v8::Object>();
|
|
v8::Local<v8::Object> handler = CompileRun("({})").As<v8::Object>();
|
|
|
|
v8::Local<v8::Proxy> proxy =
|
|
v8::Proxy::New(env.local(), target, handler).ToLocalChecked();
|
|
|
|
v8::Local<v8::Private> priv1 = v8::Private::New(isolate);
|
|
v8::Local<v8::Private> priv2 =
|
|
v8::Private::New(isolate, v8_str("my-private"));
|
|
|
|
CcTest::CollectAllGarbage();
|
|
|
|
CHECK(priv2->Name()
|
|
->Equals(env.local(),
|
|
v8::String::NewFromUtf8Literal(isolate, "my-private"))
|
|
.FromJust());
|
|
|
|
// Make sure delete of a non-existent private symbol property works.
|
|
proxy->DeletePrivate(env.local(), priv1).FromJust();
|
|
CHECK(!proxy->HasPrivate(env.local(), priv1).FromJust());
|
|
|
|
CHECK(proxy->SetPrivate(env.local(), priv1, v8::Integer::New(isolate, 1503))
|
|
.FromJust());
|
|
CHECK(proxy->HasPrivate(env.local(), priv1).FromJust());
|
|
CHECK_EQ(1503, proxy->GetPrivate(env.local(), priv1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK(proxy->SetPrivate(env.local(), priv1, v8::Integer::New(isolate, 2002))
|
|
.FromJust());
|
|
CHECK(proxy->HasPrivate(env.local(), priv1).FromJust());
|
|
CHECK_EQ(2002, proxy->GetPrivate(env.local(), priv1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
|
|
CHECK_EQ(0u,
|
|
proxy->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
unsigned num_props =
|
|
proxy->GetPropertyNames(env.local()).ToLocalChecked()->Length();
|
|
CHECK(proxy
|
|
->Set(env.local(), v8::String::NewFromUtf8Literal(isolate, "bla"),
|
|
v8::Integer::New(isolate, 20))
|
|
.FromJust());
|
|
CHECK_EQ(1u,
|
|
proxy->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
CHECK_EQ(num_props + 1,
|
|
proxy->GetPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
|
|
CcTest::CollectAllGarbage();
|
|
|
|
// Add another property and delete it afterwards to force the object in
|
|
// slow case.
|
|
CHECK(proxy->SetPrivate(env.local(), priv2, v8::Integer::New(isolate, 2008))
|
|
.FromJust());
|
|
CHECK_EQ(2002, proxy->GetPrivate(env.local(), priv1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(2008, proxy->GetPrivate(env.local(), priv2)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(2002, proxy->GetPrivate(env.local(), priv1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(1u,
|
|
proxy->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
|
|
CHECK(proxy->HasPrivate(env.local(), priv1).FromJust());
|
|
CHECK(proxy->HasPrivate(env.local(), priv2).FromJust());
|
|
CHECK(proxy->DeletePrivate(env.local(), priv2).FromJust());
|
|
CHECK(proxy->HasPrivate(env.local(), priv1).FromJust());
|
|
CHECK(!proxy->HasPrivate(env.local(), priv2).FromJust());
|
|
CHECK_EQ(2002, proxy->GetPrivate(env.local(), priv1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(1u,
|
|
proxy->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
|
|
// Private properties are not inherited (for the time being).
|
|
v8::Local<v8::Object> child = v8::Object::New(isolate);
|
|
CHECK(child->SetPrototype(env.local(), proxy).FromJust());
|
|
CHECK(!child->HasPrivate(env.local(), priv1).FromJust());
|
|
CHECK_EQ(0u,
|
|
child->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
}
|
|
|
|
|
|
THREADED_TEST(PrivateProperties) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
|
v8::Local<v8::Private> priv1 = v8::Private::New(isolate);
|
|
v8::Local<v8::Private> priv2 =
|
|
v8::Private::New(isolate, v8_str("my-private"));
|
|
|
|
CcTest::CollectAllGarbage();
|
|
|
|
CHECK(priv2->Name()
|
|
->Equals(env.local(),
|
|
v8::String::NewFromUtf8Literal(isolate, "my-private"))
|
|
.FromJust());
|
|
|
|
// Make sure delete of a non-existent private symbol property works.
|
|
obj->DeletePrivate(env.local(), priv1).FromJust();
|
|
CHECK(!obj->HasPrivate(env.local(), priv1).FromJust());
|
|
|
|
CHECK(obj->SetPrivate(env.local(), priv1, v8::Integer::New(isolate, 1503))
|
|
.FromJust());
|
|
CHECK(obj->HasPrivate(env.local(), priv1).FromJust());
|
|
CHECK_EQ(1503, obj->GetPrivate(env.local(), priv1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK(obj->SetPrivate(env.local(), priv1, v8::Integer::New(isolate, 2002))
|
|
.FromJust());
|
|
CHECK(obj->HasPrivate(env.local(), priv1).FromJust());
|
|
CHECK_EQ(2002, obj->GetPrivate(env.local(), priv1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
|
|
CHECK_EQ(0u,
|
|
obj->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
unsigned num_props =
|
|
obj->GetPropertyNames(env.local()).ToLocalChecked()->Length();
|
|
CHECK(obj->Set(env.local(), v8::String::NewFromUtf8Literal(isolate, "bla"),
|
|
v8::Integer::New(isolate, 20))
|
|
.FromJust());
|
|
CHECK_EQ(1u,
|
|
obj->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
CHECK_EQ(num_props + 1,
|
|
obj->GetPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
|
|
CcTest::CollectAllGarbage();
|
|
|
|
// Add another property and delete it afterwards to force the object in
|
|
// slow case.
|
|
CHECK(obj->SetPrivate(env.local(), priv2, v8::Integer::New(isolate, 2008))
|
|
.FromJust());
|
|
CHECK_EQ(2002, obj->GetPrivate(env.local(), priv1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(2008, obj->GetPrivate(env.local(), priv2)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(2002, obj->GetPrivate(env.local(), priv1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(1u,
|
|
obj->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
|
|
CHECK(obj->HasPrivate(env.local(), priv1).FromJust());
|
|
CHECK(obj->HasPrivate(env.local(), priv2).FromJust());
|
|
CHECK(obj->DeletePrivate(env.local(), priv2).FromJust());
|
|
CHECK(obj->HasPrivate(env.local(), priv1).FromJust());
|
|
CHECK(!obj->HasPrivate(env.local(), priv2).FromJust());
|
|
CHECK_EQ(2002, obj->GetPrivate(env.local(), priv1)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(1u,
|
|
obj->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
|
|
// Private properties are not inherited (for the time being).
|
|
v8::Local<v8::Object> child = v8::Object::New(isolate);
|
|
CHECK(child->SetPrototype(env.local(), obj).FromJust());
|
|
CHECK(!child->HasPrivate(env.local(), priv1).FromJust());
|
|
CHECK_EQ(0u,
|
|
child->GetOwnPropertyNames(env.local()).ToLocalChecked()->Length());
|
|
}
|
|
|
|
|
|
THREADED_TEST(GlobalSymbols) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<String> name = v8_str("my-symbol");
|
|
v8::Local<v8::Symbol> glob = v8::Symbol::For(isolate, name);
|
|
v8::Local<v8::Symbol> glob2 = v8::Symbol::For(isolate, name);
|
|
CHECK(glob2->SameValue(glob));
|
|
|
|
v8::Local<v8::Symbol> glob_api = v8::Symbol::ForApi(isolate, name);
|
|
v8::Local<v8::Symbol> glob_api2 = v8::Symbol::ForApi(isolate, name);
|
|
CHECK(glob_api2->SameValue(glob_api));
|
|
CHECK(!glob_api->SameValue(glob));
|
|
|
|
v8::Local<v8::Symbol> sym = v8::Symbol::New(isolate, name);
|
|
CHECK(!sym->SameValue(glob));
|
|
|
|
CompileRun("var sym2 = Symbol.for('my-symbol')");
|
|
v8::Local<Value> sym2 =
|
|
env->Global()->Get(env.local(), v8_str("sym2")).ToLocalChecked();
|
|
CHECK(sym2->SameValue(glob));
|
|
CHECK(!sym2->SameValue(glob_api));
|
|
}
|
|
|
|
THREADED_TEST(GlobalSymbolsNoContext) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<String> name = v8_str("my-symbol");
|
|
v8::Local<v8::Symbol> glob = v8::Symbol::For(isolate, name);
|
|
v8::Local<v8::Symbol> glob2 = v8::Symbol::For(isolate, name);
|
|
CHECK(glob2->SameValue(glob));
|
|
|
|
v8::Local<v8::Symbol> glob_api = v8::Symbol::ForApi(isolate, name);
|
|
v8::Local<v8::Symbol> glob_api2 = v8::Symbol::ForApi(isolate, name);
|
|
CHECK(glob_api2->SameValue(glob_api));
|
|
CHECK(!glob_api->SameValue(glob));
|
|
}
|
|
|
|
static void CheckWellKnownSymbol(v8::Local<v8::Symbol>(*getter)(v8::Isolate*),
|
|
const char* name) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::Symbol> symbol = getter(isolate);
|
|
std::string script = std::string("var sym = ") + name;
|
|
CompileRun(script.c_str());
|
|
v8::Local<Value> value =
|
|
env->Global()->Get(env.local(), v8_str("sym")).ToLocalChecked();
|
|
|
|
CHECK(!value.IsEmpty());
|
|
CHECK(!symbol.IsEmpty());
|
|
CHECK(value->SameValue(symbol));
|
|
}
|
|
|
|
|
|
THREADED_TEST(WellKnownSymbols) {
|
|
CheckWellKnownSymbol(v8::Symbol::GetIterator, "Symbol.iterator");
|
|
CheckWellKnownSymbol(v8::Symbol::GetAsyncIterator, "Symbol.asyncIterator");
|
|
CheckWellKnownSymbol(v8::Symbol::GetUnscopables, "Symbol.unscopables");
|
|
CheckWellKnownSymbol(v8::Symbol::GetHasInstance, "Symbol.hasInstance");
|
|
CheckWellKnownSymbol(v8::Symbol::GetIsConcatSpreadable,
|
|
"Symbol.isConcatSpreadable");
|
|
CheckWellKnownSymbol(v8::Symbol::GetMatch, "Symbol.match");
|
|
CheckWellKnownSymbol(v8::Symbol::GetReplace, "Symbol.replace");
|
|
CheckWellKnownSymbol(v8::Symbol::GetSearch, "Symbol.search");
|
|
CheckWellKnownSymbol(v8::Symbol::GetSplit, "Symbol.split");
|
|
CheckWellKnownSymbol(v8::Symbol::GetToPrimitive, "Symbol.toPrimitive");
|
|
CheckWellKnownSymbol(v8::Symbol::GetToStringTag, "Symbol.toStringTag");
|
|
}
|
|
|
|
|
|
THREADED_TEST(GlobalPrivates) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<String> name = v8_str("my-private");
|
|
v8::Local<v8::Private> glob = v8::Private::ForApi(isolate, name);
|
|
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
|
CHECK(obj->SetPrivate(env.local(), glob, v8::Integer::New(isolate, 3))
|
|
.FromJust());
|
|
|
|
v8::Local<v8::Private> glob2 = v8::Private::ForApi(isolate, name);
|
|
CHECK(obj->HasPrivate(env.local(), glob2).FromJust());
|
|
|
|
v8::Local<v8::Private> priv = v8::Private::New(isolate, name);
|
|
CHECK(!obj->HasPrivate(env.local(), priv).FromJust());
|
|
|
|
CompileRun("var intern = %CreatePrivateSymbol('my-private')");
|
|
v8::Local<Value> intern =
|
|
env->Global()->Get(env.local(), v8_str("intern")).ToLocalChecked();
|
|
CHECK(!obj->Has(env.local(), intern).FromJust());
|
|
}
|
|
|
|
THREADED_TEST(HiddenProperties) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::Object> obj = v8::Object::New(env->GetIsolate());
|
|
v8::Local<v8::Private> key =
|
|
v8::Private::ForApi(isolate, v8_str("api-test::hidden-key"));
|
|
v8::Local<v8::String> empty = v8_str("");
|
|
v8::Local<v8::String> prop_name = v8_str("prop_name");
|
|
|
|
CcTest::CollectAllGarbage();
|
|
|
|
// Make sure delete of a non-existent hidden value works
|
|
obj->DeletePrivate(env.local(), key).FromJust();
|
|
|
|
CHECK(obj->SetPrivate(env.local(), key, v8::Integer::New(isolate, 1503))
|
|
.FromJust());
|
|
CHECK_EQ(1503, obj->GetPrivate(env.local(), key)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK(obj->SetPrivate(env.local(), key, v8::Integer::New(isolate, 2002))
|
|
.FromJust());
|
|
CHECK_EQ(2002, obj->GetPrivate(env.local(), key)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
|
|
CcTest::CollectAllGarbage();
|
|
|
|
// Make sure we do not find the hidden property.
|
|
CHECK(!obj->Has(env.local(), empty).FromJust());
|
|
CHECK_EQ(2002, obj->GetPrivate(env.local(), key)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK(obj->Get(env.local(), empty).ToLocalChecked()->IsUndefined());
|
|
CHECK_EQ(2002, obj->GetPrivate(env.local(), key)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK(
|
|
obj->Set(env.local(), empty, v8::Integer::New(isolate, 2003)).FromJust());
|
|
CHECK_EQ(2002, obj->GetPrivate(env.local(), key)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(2003, obj->Get(env.local(), empty)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
|
|
CcTest::CollectAllGarbage();
|
|
|
|
// Add another property and delete it afterwards to force the object in
|
|
// slow case.
|
|
CHECK(obj->Set(env.local(), prop_name, v8::Integer::New(isolate, 2008))
|
|
.FromJust());
|
|
CHECK_EQ(2002, obj->GetPrivate(env.local(), key)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(2008, obj->Get(env.local(), prop_name)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(2002, obj->GetPrivate(env.local(), key)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK(obj->Delete(env.local(), prop_name).FromJust());
|
|
CHECK_EQ(2002, obj->GetPrivate(env.local(), key)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
|
|
CcTest::CollectAllGarbage();
|
|
|
|
CHECK(obj->SetPrivate(env.local(), key, v8::Integer::New(isolate, 2002))
|
|
.FromJust());
|
|
CHECK(obj->DeletePrivate(env.local(), key).FromJust());
|
|
CHECK(!obj->HasPrivate(env.local(), key).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(Regress97784) {
|
|
// Regression test for crbug.com/97784
|
|
// Messing with the Object.prototype should not have effect on
|
|
// hidden properties.
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
|
|
v8::Local<v8::Object> obj = v8::Object::New(env->GetIsolate());
|
|
v8::Local<v8::Private> key =
|
|
v8::Private::New(env->GetIsolate(), v8_str("hidden"));
|
|
|
|
CompileRun(
|
|
"set_called = false;"
|
|
"Object.defineProperty("
|
|
" Object.prototype,"
|
|
" 'hidden',"
|
|
" {get: function() { return 45; },"
|
|
" set: function() { set_called = true; }})");
|
|
|
|
CHECK(!obj->HasPrivate(env.local(), key).FromJust());
|
|
// 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->SetPrivate(env.local(), key, v8::Integer::New(env->GetIsolate(), 42))
|
|
.FromJust());
|
|
ExpectFalse("set_called");
|
|
CHECK_EQ(42, obj->GetPrivate(env.local(), key)
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(External) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
int x = 3;
|
|
Local<v8::External> ext = v8::External::New(CcTest::isolate(), &x);
|
|
LocalContext env;
|
|
CHECK(env->Global()->Set(env.local(), v8_str("ext"), ext).FromJust());
|
|
Local<Value> reext_obj = CompileRun("this.ext");
|
|
v8::Local<v8::External> reext = reext_obj.As<v8::External>();
|
|
int* ptr = static_cast<int*>(reext->Value());
|
|
CHECK_EQ(3, x);
|
|
*ptr = 10;
|
|
CHECK_EQ(x, 10);
|
|
|
|
{
|
|
i::Handle<i::Object> obj = v8::Utils::OpenHandle(*ext);
|
|
CHECK_EQ(i::HeapObject::cast(*obj).map(), CcTest::heap()->external_map());
|
|
CHECK(ext->IsExternal());
|
|
CHECK(!CompileRun("new Set().add(this.ext)").IsEmpty());
|
|
CHECK_EQ(i::HeapObject::cast(*obj).map(), CcTest::heap()->external_map());
|
|
CHECK(ext->IsExternal());
|
|
}
|
|
|
|
// Make sure unaligned pointers are wrapped properly.
|
|
char* data = i::StrDup("0123456789");
|
|
Local<v8::Value> zero = v8::External::New(CcTest::isolate(), &data[0]);
|
|
Local<v8::Value> one = v8::External::New(CcTest::isolate(), &data[1]);
|
|
Local<v8::Value> two = v8::External::New(CcTest::isolate(), &data[2]);
|
|
Local<v8::Value> three = v8::External::New(CcTest::isolate(), &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::Isolate* isolate = CcTest::isolate();
|
|
v8::Persistent<String> global;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
global.Reset(isolate, v8_str("str"));
|
|
}
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
CHECK_EQ(3, v8::Local<String>::New(isolate, global)->Length());
|
|
}
|
|
global.Reset();
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
global.Reset(isolate, v8_str("str"));
|
|
}
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
CHECK_EQ(3, v8::Local<String>::New(isolate, global)->Length());
|
|
}
|
|
global.Reset();
|
|
}
|
|
|
|
|
|
THREADED_TEST(ResettingGlobalHandle) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Persistent<String> global;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
global.Reset(isolate, v8_str("str"));
|
|
}
|
|
v8::internal::GlobalHandles* global_handles =
|
|
reinterpret_cast<v8::internal::Isolate*>(isolate)->global_handles();
|
|
size_t initial_handle_count = global_handles->handles_count();
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
CHECK_EQ(3, v8::Local<String>::New(isolate, global)->Length());
|
|
}
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
global.Reset(isolate, v8_str("longer"));
|
|
}
|
|
CHECK_EQ(global_handles->handles_count(), initial_handle_count);
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
CHECK_EQ(6, v8::Local<String>::New(isolate, global)->Length());
|
|
}
|
|
global.Reset();
|
|
CHECK_EQ(global_handles->handles_count(), initial_handle_count - 1);
|
|
}
|
|
|
|
|
|
THREADED_TEST(ResettingGlobalHandleToEmpty) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Persistent<String> global;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
global.Reset(isolate, v8_str("str"));
|
|
}
|
|
v8::internal::GlobalHandles* global_handles =
|
|
reinterpret_cast<v8::internal::Isolate*>(isolate)->global_handles();
|
|
size_t initial_handle_count = global_handles->handles_count();
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
CHECK_EQ(3, v8::Local<String>::New(isolate, global)->Length());
|
|
}
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
Local<String> empty;
|
|
global.Reset(isolate, empty);
|
|
}
|
|
CHECK(global.IsEmpty());
|
|
CHECK_EQ(global_handles->handles_count(), initial_handle_count - 1);
|
|
}
|
|
|
|
|
|
template <class T>
|
|
static v8::Global<T> PassUnique(v8::Global<T> unique) {
|
|
return unique.Pass();
|
|
}
|
|
|
|
|
|
template <class T>
|
|
static v8::Global<T> ReturnUnique(v8::Isolate* isolate,
|
|
const v8::Persistent<T>& global) {
|
|
v8::Global<String> unique(isolate, global);
|
|
return unique.Pass();
|
|
}
|
|
|
|
|
|
THREADED_TEST(Global) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Persistent<String> global;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
global.Reset(isolate, v8_str("str"));
|
|
}
|
|
v8::internal::GlobalHandles* global_handles =
|
|
reinterpret_cast<v8::internal::Isolate*>(isolate)->global_handles();
|
|
size_t initial_handle_count = global_handles->handles_count();
|
|
{
|
|
v8::Global<String> unique(isolate, global);
|
|
CHECK_EQ(initial_handle_count + 1, global_handles->handles_count());
|
|
// Test assignment via Pass
|
|
{
|
|
v8::Global<String> copy = unique.Pass();
|
|
CHECK(unique.IsEmpty());
|
|
CHECK(copy == global);
|
|
CHECK_EQ(initial_handle_count + 1, global_handles->handles_count());
|
|
unique = copy.Pass();
|
|
}
|
|
// Test ctor via Pass
|
|
{
|
|
v8::Global<String> copy(unique.Pass());
|
|
CHECK(unique.IsEmpty());
|
|
CHECK(copy == global);
|
|
CHECK_EQ(initial_handle_count + 1, global_handles->handles_count());
|
|
unique = copy.Pass();
|
|
}
|
|
// Test pass through function call
|
|
{
|
|
v8::Global<String> copy = PassUnique(unique.Pass());
|
|
CHECK(unique.IsEmpty());
|
|
CHECK(copy == global);
|
|
CHECK_EQ(initial_handle_count + 1, global_handles->handles_count());
|
|
unique = copy.Pass();
|
|
}
|
|
CHECK_EQ(initial_handle_count + 1, global_handles->handles_count());
|
|
}
|
|
// Test pass from function call
|
|
{
|
|
v8::Global<String> unique = ReturnUnique(isolate, global);
|
|
CHECK(unique == global);
|
|
CHECK_EQ(initial_handle_count + 1, global_handles->handles_count());
|
|
}
|
|
CHECK_EQ(initial_handle_count, global_handles->handles_count());
|
|
global.Reset();
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
|
class TwoPassCallbackData;
|
|
void FirstPassCallback(const v8::WeakCallbackInfo<TwoPassCallbackData>& data);
|
|
void SecondPassCallback(const v8::WeakCallbackInfo<TwoPassCallbackData>& data);
|
|
|
|
struct GCCallbackMetadata {
|
|
int instance_counter = 0;
|
|
int depth = 0;
|
|
v8::Persistent<v8::Context> context;
|
|
|
|
GCCallbackMetadata() {
|
|
auto isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
context.Reset(isolate, CcTest::NewContext());
|
|
}
|
|
|
|
~GCCallbackMetadata() {
|
|
CHECK_EQ(0, instance_counter);
|
|
CHECK_EQ(0, depth);
|
|
}
|
|
|
|
struct DepthCheck {
|
|
explicit DepthCheck(GCCallbackMetadata* counters) : counters(counters) {
|
|
CHECK_EQ(counters->depth, 0);
|
|
counters->depth++;
|
|
}
|
|
|
|
~DepthCheck() {
|
|
counters->depth--;
|
|
CHECK_EQ(counters->depth, 0);
|
|
}
|
|
|
|
GCCallbackMetadata* counters;
|
|
};
|
|
};
|
|
|
|
class TwoPassCallbackData {
|
|
public:
|
|
TwoPassCallbackData(v8::Isolate* isolate, GCCallbackMetadata* metadata)
|
|
: first_pass_called_(false),
|
|
second_pass_called_(false),
|
|
trigger_gc_(false),
|
|
metadata_(metadata) {
|
|
HandleScope scope(isolate);
|
|
v8::base::ScopedVector<char> buffer(40);
|
|
v8::base::SNPrintF(buffer, "%p", static_cast<void*>(this));
|
|
auto string =
|
|
v8::String::NewFromUtf8(isolate, buffer.begin()).ToLocalChecked();
|
|
cell_.Reset(isolate, string);
|
|
metadata_->instance_counter++;
|
|
}
|
|
|
|
~TwoPassCallbackData() {
|
|
CHECK(first_pass_called_);
|
|
CHECK(second_pass_called_);
|
|
CHECK(cell_.IsEmpty());
|
|
metadata_->instance_counter--;
|
|
}
|
|
|
|
void FirstPass() {
|
|
CHECK(!first_pass_called_);
|
|
CHECK(!second_pass_called_);
|
|
CHECK(!cell_.IsEmpty());
|
|
cell_.Reset();
|
|
first_pass_called_ = true;
|
|
}
|
|
|
|
void SecondPass(v8::Isolate* isolate) {
|
|
ApiTestFuzzer::Fuzz();
|
|
|
|
GCCallbackMetadata::DepthCheck depth_check(metadata_);
|
|
CHECK(first_pass_called_);
|
|
CHECK(!second_pass_called_);
|
|
CHECK(cell_.IsEmpty());
|
|
second_pass_called_ = true;
|
|
|
|
GCCallbackMetadata* metadata = metadata_;
|
|
bool trigger_gc = trigger_gc_;
|
|
delete this;
|
|
|
|
{
|
|
// Make sure that running JS works inside the second pass callback.
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Context::Scope context_scope(metadata->context.Get(isolate));
|
|
v8::Local<v8::Value> value = CompileRun("(function() { return 42 })()");
|
|
CHECK(value->IsInt32());
|
|
CHECK_EQ(value.As<v8::Int32>()->Value(), 42);
|
|
}
|
|
|
|
if (!trigger_gc) return;
|
|
auto data_2 = new TwoPassCallbackData(isolate, metadata);
|
|
data_2->SetWeak();
|
|
CcTest::CollectAllGarbage();
|
|
}
|
|
|
|
void SetWeak() {
|
|
cell_.SetWeak(this, FirstPassCallback, v8::WeakCallbackType::kParameter);
|
|
}
|
|
|
|
void MarkTriggerGc() { trigger_gc_ = true; }
|
|
|
|
private:
|
|
bool first_pass_called_;
|
|
bool second_pass_called_;
|
|
bool trigger_gc_;
|
|
v8::Global<v8::String> cell_;
|
|
GCCallbackMetadata* metadata_;
|
|
};
|
|
|
|
|
|
void SecondPassCallback(const v8::WeakCallbackInfo<TwoPassCallbackData>& data) {
|
|
data.GetParameter()->SecondPass(data.GetIsolate());
|
|
}
|
|
|
|
|
|
void FirstPassCallback(const v8::WeakCallbackInfo<TwoPassCallbackData>& data) {
|
|
data.GetParameter()->FirstPass();
|
|
data.SetSecondPassCallback(SecondPassCallback);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
TEST(TwoPassPhantomCallbacks) {
|
|
auto isolate = CcTest::isolate();
|
|
GCCallbackMetadata metadata;
|
|
const size_t kLength = 20;
|
|
for (size_t i = 0; i < kLength; ++i) {
|
|
auto data = new TwoPassCallbackData(isolate, &metadata);
|
|
data->SetWeak();
|
|
}
|
|
CHECK_EQ(static_cast<int>(kLength), metadata.instance_counter);
|
|
CcTest::CollectAllGarbage();
|
|
EmptyMessageQueues(isolate);
|
|
}
|
|
|
|
|
|
TEST(TwoPassPhantomCallbacksNestedGc) {
|
|
auto isolate = CcTest::isolate();
|
|
GCCallbackMetadata metadata;
|
|
const size_t kLength = 20;
|
|
TwoPassCallbackData* array[kLength];
|
|
for (size_t i = 0; i < kLength; ++i) {
|
|
array[i] = new TwoPassCallbackData(isolate, &metadata);
|
|
array[i]->SetWeak();
|
|
}
|
|
array[5]->MarkTriggerGc();
|
|
array[10]->MarkTriggerGc();
|
|
array[15]->MarkTriggerGc();
|
|
CHECK_EQ(static_cast<int>(kLength), metadata.instance_counter);
|
|
CcTest::CollectAllGarbage();
|
|
EmptyMessageQueues(isolate);
|
|
}
|
|
|
|
// The string creation API methods forbid executing JS code while they are
|
|
// on the stack. Make sure that when such a string creation triggers GC,
|
|
// the second pass callback can still execute JS as per its API contract.
|
|
TEST(TwoPassPhantomCallbacksTriggeredByStringAlloc) {
|
|
auto isolate = CcTest::isolate();
|
|
GCCallbackMetadata metadata;
|
|
auto data = new TwoPassCallbackData(isolate, &metadata);
|
|
data->SetWeak();
|
|
CHECK_EQ(metadata.instance_counter, 1);
|
|
|
|
v8::base::ScopedVector<uint8_t> source(200000);
|
|
v8::HandleScope handle_scope(isolate);
|
|
// Creating a few large strings suffices to trigger GC.
|
|
while (metadata.instance_counter == 1) {
|
|
USE(v8::String::NewFromOneByte(isolate, source.begin(),
|
|
v8::NewStringType::kNormal,
|
|
static_cast<int>(source.size())));
|
|
}
|
|
EmptyMessageQueues(isolate);
|
|
}
|
|
|
|
namespace {
|
|
|
|
void* IntKeyToVoidPointer(int key) { return reinterpret_cast<void*>(key << 1); }
|
|
|
|
|
|
Local<v8::Object> NewObjectForIntKey(
|
|
v8::Isolate* isolate, const v8::Global<v8::ObjectTemplate>& templ,
|
|
int key) {
|
|
auto local = Local<v8::ObjectTemplate>::New(isolate, templ);
|
|
auto obj = local->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
|
|
obj->SetAlignedPointerInInternalField(0, IntKeyToVoidPointer(key));
|
|
return obj;
|
|
}
|
|
|
|
|
|
template <typename K, typename V>
|
|
class PhantomStdMapTraits : public v8::StdMapTraits<K, V> {
|
|
public:
|
|
using MapType = typename v8::GlobalValueMap<K, V, PhantomStdMapTraits<K, V>>;
|
|
static const v8::PersistentContainerCallbackType kCallbackType =
|
|
v8::kWeakWithInternalFields;
|
|
struct WeakCallbackDataType {
|
|
MapType* map;
|
|
K key;
|
|
};
|
|
static WeakCallbackDataType* WeakCallbackParameter(MapType* map, const K& key,
|
|
Local<V> value) {
|
|
WeakCallbackDataType* data = new WeakCallbackDataType;
|
|
data->map = map;
|
|
data->key = key;
|
|
return data;
|
|
}
|
|
static MapType* MapFromWeakCallbackInfo(
|
|
const v8::WeakCallbackInfo<WeakCallbackDataType>& data) {
|
|
return data.GetParameter()->map;
|
|
}
|
|
static K KeyFromWeakCallbackInfo(
|
|
const v8::WeakCallbackInfo<WeakCallbackDataType>& data) {
|
|
return data.GetParameter()->key;
|
|
}
|
|
static void DisposeCallbackData(WeakCallbackDataType* data) { delete data; }
|
|
static void Dispose(v8::Isolate* isolate, v8::Global<V> value, K key) {
|
|
CHECK_EQ(IntKeyToVoidPointer(key),
|
|
v8::Object::GetAlignedPointerFromInternalField(value, 0));
|
|
}
|
|
static void OnWeakCallback(
|
|
const v8::WeakCallbackInfo<WeakCallbackDataType>&) {}
|
|
static void DisposeWeak(
|
|
const v8::WeakCallbackInfo<WeakCallbackDataType>& info) {
|
|
K key = KeyFromWeakCallbackInfo(info);
|
|
CHECK_EQ(IntKeyToVoidPointer(key), info.GetInternalField(0));
|
|
DisposeCallbackData(info.GetParameter());
|
|
}
|
|
};
|
|
|
|
|
|
template <typename Map>
|
|
void TestGlobalValueMap() {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::Global<ObjectTemplate> templ;
|
|
{
|
|
HandleScope scope(isolate);
|
|
auto t = ObjectTemplate::New(isolate);
|
|
t->SetInternalFieldCount(1);
|
|
templ.Reset(isolate, t);
|
|
}
|
|
Map map(isolate);
|
|
v8::internal::GlobalHandles* global_handles =
|
|
reinterpret_cast<v8::internal::Isolate*>(isolate)->global_handles();
|
|
size_t initial_handle_count = global_handles->handles_count();
|
|
CHECK_EQ(0, static_cast<int>(map.Size()));
|
|
{
|
|
HandleScope scope(isolate);
|
|
Local<v8::Object> obj = map.Get(7);
|
|
CHECK(obj.IsEmpty());
|
|
Local<v8::Object> expected = v8::Object::New(isolate);
|
|
map.Set(7, expected);
|
|
CHECK_EQ(1, static_cast<int>(map.Size()));
|
|
obj = map.Get(7);
|
|
CHECK(expected->Equals(env.local(), obj).FromJust());
|
|
{
|
|
typename Map::PersistentValueReference ref = map.GetReference(7);
|
|
CHECK(expected->Equals(env.local(), ref.NewLocal(isolate)).FromJust());
|
|
}
|
|
v8::Global<v8::Object> removed = map.Remove(7);
|
|
CHECK_EQ(0, static_cast<int>(map.Size()));
|
|
CHECK(expected == removed);
|
|
removed = map.Remove(7);
|
|
CHECK(removed.IsEmpty());
|
|
map.Set(8, expected);
|
|
CHECK_EQ(1, static_cast<int>(map.Size()));
|
|
map.Set(8, expected);
|
|
CHECK_EQ(1, static_cast<int>(map.Size()));
|
|
{
|
|
typename Map::PersistentValueReference ref;
|
|
Local<v8::Object> expected2 = NewObjectForIntKey(isolate, templ, 8);
|
|
removed = map.Set(8, v8::Global<v8::Object>(isolate, expected2), &ref);
|
|
CHECK_EQ(1, static_cast<int>(map.Size()));
|
|
CHECK(expected == removed);
|
|
CHECK(expected2->Equals(env.local(), ref.NewLocal(isolate)).FromJust());
|
|
}
|
|
}
|
|
CHECK_EQ(initial_handle_count + 1, global_handles->handles_count());
|
|
if (map.IsWeak()) {
|
|
CcTest::PreciseCollectAllGarbage();
|
|
} else {
|
|
map.Clear();
|
|
}
|
|
CHECK_EQ(0, static_cast<int>(map.Size()));
|
|
CHECK_EQ(initial_handle_count, global_handles->handles_count());
|
|
{
|
|
HandleScope scope(isolate);
|
|
Local<v8::Object> value = NewObjectForIntKey(isolate, templ, 9);
|
|
map.Set(9, value);
|
|
map.Clear();
|
|
}
|
|
CHECK_EQ(0, static_cast<int>(map.Size()));
|
|
CHECK_EQ(initial_handle_count, global_handles->handles_count());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
TEST(GlobalValueMap) {
|
|
// Default case, w/o weak callbacks:
|
|
TestGlobalValueMap<v8::StdGlobalValueMap<int, v8::Object>>();
|
|
|
|
// Custom traits with weak callbacks:
|
|
using WeakMap =
|
|
v8::GlobalValueMap<int, v8::Object, PhantomStdMapTraits<int, v8::Object>>;
|
|
TestGlobalValueMap<WeakMap>();
|
|
}
|
|
|
|
|
|
TEST(PersistentValueVector) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::internal::GlobalHandles* global_handles =
|
|
reinterpret_cast<v8::internal::Isolate*>(isolate)->global_handles();
|
|
size_t handle_count = global_handles->handles_count();
|
|
HandleScope scope(isolate);
|
|
|
|
v8::PersistentValueVector<v8::Object> vector(isolate);
|
|
|
|
Local<v8::Object> obj1 = v8::Object::New(isolate);
|
|
Local<v8::Object> obj2 = v8::Object::New(isolate);
|
|
v8::Global<v8::Object> obj3(isolate, v8::Object::New(isolate));
|
|
|
|
CHECK(vector.IsEmpty());
|
|
CHECK_EQ(0, static_cast<int>(vector.Size()));
|
|
|
|
vector.ReserveCapacity(3);
|
|
CHECK(vector.IsEmpty());
|
|
|
|
vector.Append(obj1);
|
|
vector.Append(obj2);
|
|
vector.Append(obj1);
|
|
vector.Append(obj3.Pass());
|
|
vector.Append(obj1);
|
|
|
|
CHECK(!vector.IsEmpty());
|
|
CHECK_EQ(5, static_cast<int>(vector.Size()));
|
|
CHECK(obj3.IsEmpty());
|
|
CHECK(obj1->Equals(env.local(), vector.Get(0)).FromJust());
|
|
CHECK(obj1->Equals(env.local(), vector.Get(2)).FromJust());
|
|
CHECK(obj1->Equals(env.local(), vector.Get(4)).FromJust());
|
|
CHECK(obj2->Equals(env.local(), vector.Get(1)).FromJust());
|
|
|
|
CHECK_EQ(5 + handle_count, global_handles->handles_count());
|
|
|
|
vector.Clear();
|
|
CHECK(vector.IsEmpty());
|
|
CHECK_EQ(0, static_cast<int>(vector.Size()));
|
|
CHECK_EQ(handle_count, global_handles->handles_count());
|
|
}
|
|
|
|
|
|
THREADED_TEST(GlobalHandleUpcast) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<String> local = v8::Local<String>::New(isolate, v8_str("str"));
|
|
v8::Persistent<String> global_string(isolate, local);
|
|
v8::Persistent<Value>& global_value =
|
|
v8::Persistent<Value>::Cast(global_string);
|
|
CHECK(v8::Local<v8::Value>::New(isolate, global_value)->IsString());
|
|
CHECK(global_string == v8::Persistent<String>::Cast(global_value));
|
|
global_string.Reset();
|
|
}
|
|
|
|
|
|
THREADED_TEST(HandleEquality) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Persistent<String> global1;
|
|
v8::Persistent<String> global2;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
global1.Reset(isolate, v8_str("str"));
|
|
global2.Reset(isolate, v8_str("str2"));
|
|
}
|
|
CHECK(global1 == global1);
|
|
CHECK(!(global1 != global1));
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
Local<String> local1 = Local<String>::New(isolate, global1);
|
|
Local<String> local2 = Local<String>::New(isolate, global2);
|
|
|
|
CHECK(global1 == local1);
|
|
CHECK(!(global1 != local1));
|
|
CHECK(local1 == global1);
|
|
CHECK(!(local1 != global1));
|
|
|
|
CHECK(!(global1 == local2));
|
|
CHECK(global1 != local2);
|
|
CHECK(!(local2 == global1));
|
|
CHECK(local2 != global1);
|
|
|
|
CHECK(!(local1 == local2));
|
|
CHECK(local1 != local2);
|
|
|
|
Local<String> anotherLocal1 = Local<String>::New(isolate, global1);
|
|
CHECK(local1 == anotherLocal1);
|
|
CHECK(!(local1 != anotherLocal1));
|
|
}
|
|
global1.Reset();
|
|
global2.Reset();
|
|
}
|
|
|
|
THREADED_TEST(HandleEqualityPrimitives) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
// Local::operator== works like strict equality except for primitives.
|
|
CHECK_NE(v8_str("str"), v8_str("str"));
|
|
CHECK_NE(v8::Number::New(CcTest::isolate(), 0.5),
|
|
v8::Number::New(CcTest::isolate(), 0.5));
|
|
CHECK_EQ(v8::Number::New(CcTest::isolate(), 1),
|
|
v8::Number::New(CcTest::isolate(), 1));
|
|
}
|
|
|
|
THREADED_TEST(LocalHandle) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
v8::Local<String> local =
|
|
v8::Local<String>::New(CcTest::isolate(), v8_str("str"));
|
|
CHECK_EQ(3, local->Length());
|
|
}
|
|
|
|
|
|
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_;
|
|
};
|
|
|
|
|
|
template <typename T>
|
|
struct WeakCallCounterAndPersistent {
|
|
explicit WeakCallCounterAndPersistent(WeakCallCounter* counter)
|
|
: counter(counter) {}
|
|
WeakCallCounter* counter;
|
|
v8::Persistent<T> handle;
|
|
};
|
|
|
|
|
|
template <typename T>
|
|
static void WeakPointerCallback(
|
|
const v8::WeakCallbackInfo<WeakCallCounterAndPersistent<T>>& data) {
|
|
CHECK_EQ(1234, data.GetParameter()->counter->id());
|
|
data.GetParameter()->counter->increment();
|
|
data.GetParameter()->handle.Reset();
|
|
}
|
|
|
|
THREADED_TEST(ScriptException) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
Local<Script> script = v8_compile("throw 'panama!';");
|
|
v8::TryCatch try_catch(env->GetIsolate());
|
|
v8::MaybeLocal<Value> result = script->Run(env.local());
|
|
CHECK(result.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
String::Utf8Value exception_value(env->GetIsolate(), try_catch.Exception());
|
|
CHECK_EQ(0, strcmp(*exception_value, "panama!"));
|
|
}
|
|
|
|
|
|
TEST(TryCatchCustomException) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun(
|
|
"function CustomError() { this.a = 'b'; }"
|
|
"(function f() { throw new CustomError(); })();");
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()
|
|
->ToObject(env.local())
|
|
.ToLocalChecked()
|
|
->Get(env.local(), v8_str("a"))
|
|
.ToLocalChecked()
|
|
->Equals(env.local(), v8_str("b"))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
bool message_received;
|
|
|
|
|
|
static void check_message_0(v8::Local<v8::Message> message,
|
|
v8::Local<Value> data) {
|
|
CHECK_EQ(5.76, data->NumberValue(CcTest::isolate()->GetCurrentContext())
|
|
.FromJust());
|
|
CHECK_EQ(6.75, message->GetScriptOrigin()
|
|
.ResourceName()
|
|
->NumberValue(CcTest::isolate()->GetCurrentContext())
|
|
.FromJust());
|
|
CHECK(!message->IsSharedCrossOrigin());
|
|
message_received = true;
|
|
}
|
|
|
|
|
|
THREADED_TEST(MessageHandler0) {
|
|
message_received = false;
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
CHECK(!message_received);
|
|
LocalContext context;
|
|
CcTest::isolate()->AddMessageListener(check_message_0, v8_num(5.76));
|
|
v8::Local<v8::Script> script =
|
|
CompileWithOrigin("throw 'error'", "6.75", false);
|
|
CHECK(script->Run(context.local()).IsEmpty());
|
|
CHECK(message_received);
|
|
// clear out the message listener
|
|
CcTest::isolate()->RemoveMessageListeners(check_message_0);
|
|
}
|
|
|
|
|
|
static void check_message_1(v8::Local<v8::Message> message,
|
|
v8::Local<Value> data) {
|
|
CHECK(data->IsNumber());
|
|
CHECK_EQ(1337,
|
|
data->Int32Value(CcTest::isolate()->GetCurrentContext()).FromJust());
|
|
CHECK(!message->IsSharedCrossOrigin());
|
|
message_received = true;
|
|
}
|
|
|
|
|
|
TEST(MessageHandler1) {
|
|
message_received = false;
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
CHECK(!message_received);
|
|
CcTest::isolate()->AddMessageListener(check_message_1);
|
|
LocalContext context;
|
|
CompileRun("throw 1337;");
|
|
CHECK(message_received);
|
|
// clear out the message listener
|
|
CcTest::isolate()->RemoveMessageListeners(check_message_1);
|
|
}
|
|
|
|
|
|
static void check_message_2(v8::Local<v8::Message> message,
|
|
v8::Local<Value> data) {
|
|
LocalContext context;
|
|
CHECK(data->IsObject());
|
|
v8::Local<v8::Value> hidden_property =
|
|
v8::Object::Cast(*data)
|
|
->GetPrivate(
|
|
context.local(),
|
|
v8::Private::ForApi(CcTest::isolate(), v8_str("hidden key")))
|
|
.ToLocalChecked();
|
|
CHECK(v8_str("hidden value")
|
|
->Equals(context.local(), hidden_property)
|
|
.FromJust());
|
|
CHECK(!message->IsSharedCrossOrigin());
|
|
message_received = true;
|
|
}
|
|
|
|
|
|
TEST(MessageHandler2) {
|
|
message_received = false;
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
CHECK(!message_received);
|
|
CcTest::isolate()->AddMessageListener(check_message_2);
|
|
LocalContext context;
|
|
v8::Local<v8::Value> error = v8::Exception::Error(v8_str("custom error"));
|
|
v8::Object::Cast(*error)
|
|
->SetPrivate(context.local(),
|
|
v8::Private::ForApi(CcTest::isolate(), v8_str("hidden key")),
|
|
v8_str("hidden value"))
|
|
.FromJust();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("error"), error)
|
|
.FromJust());
|
|
CompileRun("throw error;");
|
|
CHECK(message_received);
|
|
// clear out the message listener
|
|
CcTest::isolate()->RemoveMessageListeners(check_message_2);
|
|
}
|
|
|
|
|
|
static void check_message_3(v8::Local<v8::Message> message,
|
|
v8::Local<Value> data) {
|
|
CHECK(message->IsSharedCrossOrigin());
|
|
CHECK(message->GetScriptOrigin().Options().IsSharedCrossOrigin());
|
|
CHECK(message->GetScriptOrigin().Options().IsOpaque());
|
|
CHECK_EQ(6.75, message->GetScriptOrigin()
|
|
.ResourceName()
|
|
->NumberValue(CcTest::isolate()->GetCurrentContext())
|
|
.FromJust());
|
|
CHECK_EQ(7.40, message->GetScriptOrigin()
|
|
.SourceMapUrl()
|
|
->NumberValue(CcTest::isolate()->GetCurrentContext())
|
|
.FromJust());
|
|
message_received = true;
|
|
}
|
|
|
|
|
|
TEST(MessageHandler3) {
|
|
message_received = false;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
CHECK(!message_received);
|
|
isolate->AddMessageListener(check_message_3);
|
|
LocalContext context;
|
|
v8::ScriptOrigin origin = v8::ScriptOrigin(isolate, v8_str("6.75"), 1, 2,
|
|
true, -1, v8_str("7.40"), true);
|
|
v8::Local<v8::Script> script =
|
|
Script::Compile(context.local(), v8_str("throw 'error'"), &origin)
|
|
.ToLocalChecked();
|
|
CHECK(script->Run(context.local()).IsEmpty());
|
|
CHECK(message_received);
|
|
// clear out the message listener
|
|
isolate->RemoveMessageListeners(check_message_3);
|
|
}
|
|
|
|
|
|
static void check_message_4(v8::Local<v8::Message> message,
|
|
v8::Local<Value> data) {
|
|
CHECK(!message->IsSharedCrossOrigin());
|
|
CHECK_EQ(6.75, message->GetScriptOrigin()
|
|
.ResourceName()
|
|
->NumberValue(CcTest::isolate()->GetCurrentContext())
|
|
.FromJust());
|
|
message_received = true;
|
|
}
|
|
|
|
|
|
TEST(MessageHandler4) {
|
|
message_received = false;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
CHECK(!message_received);
|
|
isolate->AddMessageListener(check_message_4);
|
|
LocalContext context;
|
|
v8::ScriptOrigin origin =
|
|
v8::ScriptOrigin(isolate, v8_str("6.75"), 1, 2, false);
|
|
v8::Local<v8::Script> script =
|
|
Script::Compile(context.local(), v8_str("throw 'error'"), &origin)
|
|
.ToLocalChecked();
|
|
CHECK(script->Run(context.local()).IsEmpty());
|
|
CHECK(message_received);
|
|
// clear out the message listener
|
|
isolate->RemoveMessageListeners(check_message_4);
|
|
}
|
|
|
|
|
|
static void check_message_5a(v8::Local<v8::Message> message,
|
|
v8::Local<Value> data) {
|
|
CHECK(message->IsSharedCrossOrigin());
|
|
CHECK_EQ(6.75, message->GetScriptOrigin()
|
|
.ResourceName()
|
|
->NumberValue(CcTest::isolate()->GetCurrentContext())
|
|
.FromJust());
|
|
message_received = true;
|
|
}
|
|
|
|
|
|
static void check_message_5b(v8::Local<v8::Message> message,
|
|
v8::Local<Value> data) {
|
|
CHECK(!message->IsSharedCrossOrigin());
|
|
CHECK_EQ(6.75, message->GetScriptOrigin()
|
|
.ResourceName()
|
|
->NumberValue(CcTest::isolate()->GetCurrentContext())
|
|
.FromJust());
|
|
message_received = true;
|
|
}
|
|
|
|
|
|
TEST(MessageHandler5) {
|
|
message_received = false;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
CHECK(!message_received);
|
|
isolate->AddMessageListener(check_message_5a);
|
|
LocalContext context;
|
|
v8::ScriptOrigin origin1 =
|
|
v8::ScriptOrigin(isolate, v8_str("6.75"), 1, 2, true);
|
|
v8::Local<v8::Script> script =
|
|
Script::Compile(context.local(), v8_str("throw 'error'"), &origin1)
|
|
.ToLocalChecked();
|
|
CHECK(script->Run(context.local()).IsEmpty());
|
|
CHECK(message_received);
|
|
// clear out the message listener
|
|
isolate->RemoveMessageListeners(check_message_5a);
|
|
|
|
message_received = false;
|
|
isolate->AddMessageListener(check_message_5b);
|
|
v8::ScriptOrigin origin2 =
|
|
v8::ScriptOrigin(isolate, v8_str("6.75"), 1, 2, false);
|
|
script = Script::Compile(context.local(), v8_str("throw 'error'"), &origin2)
|
|
.ToLocalChecked();
|
|
CHECK(script->Run(context.local()).IsEmpty());
|
|
CHECK(message_received);
|
|
// clear out the message listener
|
|
isolate->RemoveMessageListeners(check_message_5b);
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Verifies that after throwing an exception the message object is set up in
|
|
// some particular way by calling the supplied |tester| function. The tests that
|
|
// use this purposely test only a single getter as the getter updates the cached
|
|
// state of the object which could affect the results of other functions.
|
|
const char message_attributes_script[] =
|
|
R"javascript(
|
|
(function() {
|
|
throw new Error();
|
|
})();
|
|
)javascript";
|
|
|
|
void CheckMessageAttributes(std::function<void(v8::Local<v8::Context> context,
|
|
v8::Local<v8::Message> message)>
|
|
tester) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
TryCatch try_catch(context->GetIsolate());
|
|
CompileRun(message_attributes_script);
|
|
CHECK(try_catch.HasCaught());
|
|
|
|
v8::Local<v8::Value> error = try_catch.Exception();
|
|
v8::Local<v8::Message> message =
|
|
v8::Exception::CreateMessage(context->GetIsolate(), error);
|
|
CHECK(!message.IsEmpty());
|
|
|
|
tester(context.local(), message);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(MessageGetLineNumber) {
|
|
CheckMessageAttributes(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Message> message) {
|
|
CHECK_EQ(3, message->GetLineNumber(context).FromJust());
|
|
});
|
|
}
|
|
|
|
TEST(MessageGetStartColumn) {
|
|
CheckMessageAttributes(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Message> message) {
|
|
CHECK_EQ(12, message->GetStartColumn(context).FromJust());
|
|
});
|
|
}
|
|
|
|
TEST(MessageGetEndColumn) {
|
|
CheckMessageAttributes(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Message> message) {
|
|
CHECK_EQ(13, message->GetEndColumn(context).FromJust());
|
|
});
|
|
}
|
|
|
|
TEST(MessageGetStartPosition) {
|
|
CheckMessageAttributes(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Message> message) {
|
|
CHECK_EQ(31, message->GetStartPosition());
|
|
});
|
|
}
|
|
|
|
TEST(MessageGetEndPosition) {
|
|
CheckMessageAttributes(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Message> message) {
|
|
CHECK_EQ(32, message->GetEndPosition());
|
|
});
|
|
}
|
|
|
|
TEST(MessageGetSource) {
|
|
CheckMessageAttributes([](v8::Local<v8::Context> context,
|
|
v8::Local<v8::Message> message) {
|
|
std::string result(*v8::String::Utf8Value(
|
|
context->GetIsolate(), message->GetSource(context).ToLocalChecked()));
|
|
CHECK_EQ(message_attributes_script, result);
|
|
});
|
|
}
|
|
|
|
TEST(MessageGetSourceLine) {
|
|
CheckMessageAttributes(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Message> message) {
|
|
std::string result(*v8::String::Utf8Value(
|
|
context->GetIsolate(),
|
|
message->GetSourceLine(context).ToLocalChecked()));
|
|
CHECK_EQ(" throw new Error();", result);
|
|
});
|
|
}
|
|
|
|
THREADED_TEST(GetSetProperty) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("foo"), v8_num(14))
|
|
.FromJust());
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("12"), v8_num(92))
|
|
.FromJust());
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8::Integer::New(isolate, 16), v8_num(32))
|
|
.FromJust());
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_num(13), v8_num(56))
|
|
.FromJust());
|
|
Local<Value> foo = CompileRun("this.foo");
|
|
CHECK_EQ(14, foo->Int32Value(context.local()).FromJust());
|
|
Local<Value> twelve = CompileRun("this[12]");
|
|
CHECK_EQ(92, twelve->Int32Value(context.local()).FromJust());
|
|
Local<Value> sixteen = CompileRun("this[16]");
|
|
CHECK_EQ(32, sixteen->Int32Value(context.local()).FromJust());
|
|
Local<Value> thirteen = CompileRun("this[13]");
|
|
CHECK_EQ(56, thirteen->Int32Value(context.local()).FromJust());
|
|
CHECK_EQ(92, context->Global()
|
|
->Get(context.local(), v8::Integer::New(isolate, 12))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(92, context->Global()
|
|
->Get(context.local(), v8_str("12"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(92, context->Global()
|
|
->Get(context.local(), v8_num(12))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(32, context->Global()
|
|
->Get(context.local(), v8::Integer::New(isolate, 16))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(32, context->Global()
|
|
->Get(context.local(), v8_str("16"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(32, context->Global()
|
|
->Get(context.local(), v8_num(16))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(56, context->Global()
|
|
->Get(context.local(), v8::Integer::New(isolate, 13))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(56, context->Global()
|
|
->Get(context.local(), v8_str("13"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(56, context->Global()
|
|
->Get(context.local(), v8_num(13))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(PropertyAttributes) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
// none
|
|
Local<String> prop = v8_str("none");
|
|
CHECK(context->Global()->Set(context.local(), prop, v8_num(7)).FromJust());
|
|
CHECK_EQ(v8::None, context->Global()
|
|
->GetPropertyAttributes(context.local(), prop)
|
|
.FromJust());
|
|
// read-only
|
|
prop = v8_str("read_only");
|
|
context->Global()
|
|
->DefineOwnProperty(context.local(), prop, v8_num(7), v8::ReadOnly)
|
|
.FromJust();
|
|
CHECK_EQ(7, context->Global()
|
|
->Get(context.local(), prop)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(v8::ReadOnly, context->Global()
|
|
->GetPropertyAttributes(context.local(), prop)
|
|
.FromJust());
|
|
CompileRun("read_only = 9");
|
|
CHECK_EQ(7, context->Global()
|
|
->Get(context.local(), prop)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK(context->Global()->Set(context.local(), prop, v8_num(10)).FromJust());
|
|
CHECK_EQ(7, context->Global()
|
|
->Get(context.local(), prop)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
// dont-delete
|
|
prop = v8_str("dont_delete");
|
|
context->Global()
|
|
->DefineOwnProperty(context.local(), prop, v8_num(13), v8::DontDelete)
|
|
.FromJust();
|
|
CHECK_EQ(13, context->Global()
|
|
->Get(context.local(), prop)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CompileRun("delete dont_delete");
|
|
CHECK_EQ(13, context->Global()
|
|
->Get(context.local(), prop)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(v8::DontDelete, context->Global()
|
|
->GetPropertyAttributes(context.local(), prop)
|
|
.FromJust());
|
|
// dont-enum
|
|
prop = v8_str("dont_enum");
|
|
context->Global()
|
|
->DefineOwnProperty(context.local(), prop, v8_num(28), v8::DontEnum)
|
|
.FromJust();
|
|
CHECK_EQ(v8::DontEnum, context->Global()
|
|
->GetPropertyAttributes(context.local(), prop)
|
|
.FromJust());
|
|
// absent
|
|
prop = v8_str("absent");
|
|
CHECK_EQ(v8::None, context->Global()
|
|
->GetPropertyAttributes(context.local(), prop)
|
|
.FromJust());
|
|
Local<Value> fake_prop = v8_num(1);
|
|
CHECK_EQ(v8::None, context->Global()
|
|
->GetPropertyAttributes(context.local(), fake_prop)
|
|
.FromJust());
|
|
// exception
|
|
TryCatch try_catch(context->GetIsolate());
|
|
Local<Value> exception =
|
|
CompileRun("({ toString: function() { throw 'exception';} })");
|
|
CHECK(context->Global()
|
|
->GetPropertyAttributes(context.local(), exception)
|
|
.IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
String::Utf8Value exception_value(context->GetIsolate(),
|
|
try_catch.Exception());
|
|
CHECK_EQ(0, strcmp("exception", *exception_value));
|
|
try_catch.Reset();
|
|
}
|
|
|
|
|
|
THREADED_TEST(Array) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
Local<v8::Array> array = v8::Array::New(context->GetIsolate());
|
|
CHECK_EQ(0u, array->Length());
|
|
CHECK(array->Get(context.local(), 0).ToLocalChecked()->IsUndefined());
|
|
CHECK(!array->Has(context.local(), 0).FromJust());
|
|
CHECK(array->Get(context.local(), 100).ToLocalChecked()->IsUndefined());
|
|
CHECK(!array->Has(context.local(), 100).FromJust());
|
|
CHECK(array->Set(context.local(), 2, v8_num(7)).FromJust());
|
|
CHECK_EQ(3u, array->Length());
|
|
CHECK(!array->Has(context.local(), 0).FromJust());
|
|
CHECK(!array->Has(context.local(), 1).FromJust());
|
|
CHECK(array->Has(context.local(), 2).FromJust());
|
|
CHECK_EQ(7, array->Get(context.local(), 2)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
Local<Value> obj = CompileRun("[1, 2, 3]");
|
|
Local<v8::Array> arr = obj.As<v8::Array>();
|
|
CHECK_EQ(3u, arr->Length());
|
|
CHECK_EQ(1, arr->Get(context.local(), 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(2, arr->Get(context.local(), 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(3, arr->Get(context.local(), 2)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
array = v8::Array::New(context->GetIsolate(), 27);
|
|
CHECK_EQ(27u, array->Length());
|
|
array = v8::Array::New(context->GetIsolate(), -27);
|
|
CHECK_EQ(0u, array->Length());
|
|
|
|
std::vector<Local<Value>> vector = {v8_num(1), v8_num(2), v8_num(3)};
|
|
array = v8::Array::New(context->GetIsolate(), vector.data(), vector.size());
|
|
CHECK_EQ(vector.size(), array->Length());
|
|
CHECK_EQ(1, arr->Get(context.local(), 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(2, arr->Get(context.local(), 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(3, arr->Get(context.local(), 2)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
void HandleF(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::EscapableHandleScope scope(args.GetIsolate());
|
|
ApiTestFuzzer::Fuzz();
|
|
Local<v8::Array> result = v8::Array::New(args.GetIsolate(), args.Length());
|
|
for (int i = 0; i < args.Length(); i++) {
|
|
CHECK(result->Set(CcTest::isolate()->GetCurrentContext(), i, args[i])
|
|
.FromJust());
|
|
}
|
|
args.GetReturnValue().Set(scope.Escape(result));
|
|
}
|
|
|
|
|
|
THREADED_TEST(Vector) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> global = ObjectTemplate::New(isolate);
|
|
global->Set(isolate, "f", v8::FunctionTemplate::New(isolate, HandleF));
|
|
LocalContext context(nullptr, global);
|
|
|
|
const char* fun = "f()";
|
|
Local<v8::Array> a0 = CompileRun(fun).As<v8::Array>();
|
|
CHECK_EQ(0u, a0->Length());
|
|
|
|
const char* fun2 = "f(11)";
|
|
Local<v8::Array> a1 = CompileRun(fun2).As<v8::Array>();
|
|
CHECK_EQ(1u, a1->Length());
|
|
CHECK_EQ(11, a1->Get(context.local(), 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
const char* fun3 = "f(12, 13)";
|
|
Local<v8::Array> a2 = CompileRun(fun3).As<v8::Array>();
|
|
CHECK_EQ(2u, a2->Length());
|
|
CHECK_EQ(12, a2->Get(context.local(), 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(13, a2->Get(context.local(), 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
const char* fun4 = "f(14, 15, 16)";
|
|
Local<v8::Array> a3 = CompileRun(fun4).As<v8::Array>();
|
|
CHECK_EQ(3u, a3->Length());
|
|
CHECK_EQ(14, a3->Get(context.local(), 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(15, a3->Get(context.local(), 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(16, a3->Get(context.local(), 2)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
const char* fun5 = "f(17, 18, 19, 20)";
|
|
Local<v8::Array> a4 = CompileRun(fun5).As<v8::Array>();
|
|
CHECK_EQ(4u, a4->Length());
|
|
CHECK_EQ(17, a4->Get(context.local(), 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(18, a4->Get(context.local(), 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(19, a4->Get(context.local(), 2)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(20, a4->Get(context.local(), 3)
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionCall) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
CompileRun(
|
|
"function Foo() {"
|
|
" var result = [];"
|
|
" for (var i = 0; i < arguments.length; i++) {"
|
|
" result.push(arguments[i]);"
|
|
" }"
|
|
" return result;"
|
|
"}"
|
|
"function ReturnThisSloppy() {"
|
|
" return this;"
|
|
"}"
|
|
"function ReturnThisStrict() {"
|
|
" 'use strict';"
|
|
" return this;"
|
|
"}");
|
|
Local<Function> Foo = Local<Function>::Cast(
|
|
context->Global()->Get(context.local(), v8_str("Foo")).ToLocalChecked());
|
|
Local<Function> ReturnThisSloppy = Local<Function>::Cast(
|
|
context->Global()
|
|
->Get(context.local(), v8_str("ReturnThisSloppy"))
|
|
.ToLocalChecked());
|
|
Local<Function> ReturnThisStrict = Local<Function>::Cast(
|
|
context->Global()
|
|
->Get(context.local(), v8_str("ReturnThisStrict"))
|
|
.ToLocalChecked());
|
|
|
|
v8::Local<Value>* args0 = nullptr;
|
|
Local<v8::Array> a0 = Local<v8::Array>::Cast(
|
|
Foo->Call(context.local(), Foo, 0, args0).ToLocalChecked());
|
|
CHECK_EQ(0u, a0->Length());
|
|
|
|
v8::Local<Value> args1[] = {v8_num(1.1)};
|
|
Local<v8::Array> a1 = Local<v8::Array>::Cast(
|
|
Foo->Call(context.local(), Foo, 1, args1).ToLocalChecked());
|
|
CHECK_EQ(1u, a1->Length());
|
|
CHECK_EQ(1.1, a1->Get(context.local(), v8::Integer::New(isolate, 0))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
|
|
v8::Local<Value> args2[] = {v8_num(2.2), v8_num(3.3)};
|
|
Local<v8::Array> a2 = Local<v8::Array>::Cast(
|
|
Foo->Call(context.local(), Foo, 2, args2).ToLocalChecked());
|
|
CHECK_EQ(2u, a2->Length());
|
|
CHECK_EQ(2.2, a2->Get(context.local(), v8::Integer::New(isolate, 0))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(3.3, a2->Get(context.local(), v8::Integer::New(isolate, 1))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
|
|
v8::Local<Value> args3[] = {v8_num(4.4), v8_num(5.5), v8_num(6.6)};
|
|
Local<v8::Array> a3 = Local<v8::Array>::Cast(
|
|
Foo->Call(context.local(), Foo, 3, args3).ToLocalChecked());
|
|
CHECK_EQ(3u, a3->Length());
|
|
CHECK_EQ(4.4, a3->Get(context.local(), v8::Integer::New(isolate, 0))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(5.5, a3->Get(context.local(), v8::Integer::New(isolate, 1))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(6.6, a3->Get(context.local(), v8::Integer::New(isolate, 2))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
|
|
v8::Local<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(context.local(), Foo, 4, args4).ToLocalChecked());
|
|
CHECK_EQ(4u, a4->Length());
|
|
CHECK_EQ(7.7, a4->Get(context.local(), v8::Integer::New(isolate, 0))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(8.8, a4->Get(context.local(), v8::Integer::New(isolate, 1))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(9.9, a4->Get(context.local(), v8::Integer::New(isolate, 2))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(10.11, a4->Get(context.local(), v8::Integer::New(isolate, 3))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
|
|
Local<v8::Value> r1 =
|
|
ReturnThisSloppy
|
|
->Call(context.local(), v8::Undefined(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(r1->StrictEquals(context->Global()));
|
|
Local<v8::Value> r2 =
|
|
ReturnThisSloppy->Call(context.local(), v8::Null(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(r2->StrictEquals(context->Global()));
|
|
Local<v8::Value> r3 =
|
|
ReturnThisSloppy->Call(context.local(), v8_num(42), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(r3->IsNumberObject());
|
|
CHECK_EQ(42.0, r3.As<v8::NumberObject>()->ValueOf());
|
|
Local<v8::Value> r4 =
|
|
ReturnThisSloppy->Call(context.local(), v8_str("hello"), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(r4->IsStringObject());
|
|
CHECK(r4.As<v8::StringObject>()->ValueOf()->StrictEquals(v8_str("hello")));
|
|
Local<v8::Value> r5 =
|
|
ReturnThisSloppy->Call(context.local(), v8::True(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(r5->IsBooleanObject());
|
|
CHECK(r5.As<v8::BooleanObject>()->ValueOf());
|
|
|
|
Local<v8::Value> r6 =
|
|
ReturnThisStrict
|
|
->Call(context.local(), v8::Undefined(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(r6->IsUndefined());
|
|
Local<v8::Value> r7 =
|
|
ReturnThisStrict->Call(context.local(), v8::Null(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(r7->IsNull());
|
|
Local<v8::Value> r8 =
|
|
ReturnThisStrict->Call(context.local(), v8_num(42), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(r8->StrictEquals(v8_num(42)));
|
|
Local<v8::Value> r9 =
|
|
ReturnThisStrict->Call(context.local(), v8_str("hello"), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(r9->StrictEquals(v8_str("hello")));
|
|
Local<v8::Value> r10 =
|
|
ReturnThisStrict->Call(context.local(), v8::True(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(r10->StrictEquals(v8::True(isolate)));
|
|
}
|
|
|
|
|
|
THREADED_TEST(ConstructCall) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
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(context.local(), v8_str("Foo")).ToLocalChecked());
|
|
|
|
v8::Local<Value>* args0 = nullptr;
|
|
Local<v8::Array> a0 = Local<v8::Array>::Cast(
|
|
Foo->NewInstance(context.local(), 0, args0).ToLocalChecked());
|
|
CHECK_EQ(0u, a0->Length());
|
|
|
|
v8::Local<Value> args1[] = {v8_num(1.1)};
|
|
Local<v8::Array> a1 = Local<v8::Array>::Cast(
|
|
Foo->NewInstance(context.local(), 1, args1).ToLocalChecked());
|
|
CHECK_EQ(1u, a1->Length());
|
|
CHECK_EQ(1.1, a1->Get(context.local(), v8::Integer::New(isolate, 0))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
|
|
v8::Local<Value> args2[] = {v8_num(2.2), v8_num(3.3)};
|
|
Local<v8::Array> a2 = Local<v8::Array>::Cast(
|
|
Foo->NewInstance(context.local(), 2, args2).ToLocalChecked());
|
|
CHECK_EQ(2u, a2->Length());
|
|
CHECK_EQ(2.2, a2->Get(context.local(), v8::Integer::New(isolate, 0))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(3.3, a2->Get(context.local(), v8::Integer::New(isolate, 1))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
|
|
v8::Local<Value> args3[] = {v8_num(4.4), v8_num(5.5), v8_num(6.6)};
|
|
Local<v8::Array> a3 = Local<v8::Array>::Cast(
|
|
Foo->NewInstance(context.local(), 3, args3).ToLocalChecked());
|
|
CHECK_EQ(3u, a3->Length());
|
|
CHECK_EQ(4.4, a3->Get(context.local(), v8::Integer::New(isolate, 0))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(5.5, a3->Get(context.local(), v8::Integer::New(isolate, 1))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(6.6, a3->Get(context.local(), v8::Integer::New(isolate, 2))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
|
|
v8::Local<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(context.local(), 4, args4).ToLocalChecked());
|
|
CHECK_EQ(4u, a4->Length());
|
|
CHECK_EQ(7.7, a4->Get(context.local(), v8::Integer::New(isolate, 0))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(8.8, a4->Get(context.local(), v8::Integer::New(isolate, 1))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(9.9, a4->Get(context.local(), v8::Integer::New(isolate, 2))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(10.11, a4->Get(context.local(), v8::Integer::New(isolate, 3))
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(ConversionNumber) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
// Very large number.
|
|
CompileRun("var obj = Math.pow(2,32) * 1237;");
|
|
Local<Value> obj =
|
|
env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK_EQ(5312874545152.0,
|
|
obj->ToNumber(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(0, obj->ToInt32(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(0, obj->ToUint32(env.local()).ToLocalChecked()->Value());
|
|
// Large number.
|
|
CompileRun("var obj = -1234567890123;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK_EQ(-1234567890123.0,
|
|
obj->ToNumber(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(-1912276171, obj->ToInt32(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(2382691125, obj->ToUint32(env.local()).ToLocalChecked()->Value());
|
|
// Small positive integer.
|
|
CompileRun("var obj = 42;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK_EQ(42.0, obj->ToNumber(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(42, obj->ToInt32(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(42, obj->ToUint32(env.local()).ToLocalChecked()->Value());
|
|
// Negative integer.
|
|
CompileRun("var obj = -37;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK_EQ(-37.0, obj->ToNumber(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(-37, obj->ToInt32(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(4294967259, obj->ToUint32(env.local()).ToLocalChecked()->Value());
|
|
// Positive non-int32 integer.
|
|
CompileRun("var obj = 0x81234567;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK_EQ(2166572391.0, obj->ToNumber(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(-2128394905, obj->ToInt32(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(2166572391, obj->ToUint32(env.local()).ToLocalChecked()->Value());
|
|
// Fraction.
|
|
CompileRun("var obj = 42.3;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK_EQ(42.3, obj->ToNumber(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(42, obj->ToInt32(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(42, obj->ToUint32(env.local()).ToLocalChecked()->Value());
|
|
// Large negative fraction.
|
|
CompileRun("var obj = -5726623061.75;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK_EQ(-5726623061.75,
|
|
obj->ToNumber(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(-1431655765, obj->ToInt32(env.local()).ToLocalChecked()->Value());
|
|
CHECK_EQ(2863311531, obj->ToUint32(env.local()).ToLocalChecked()->Value());
|
|
}
|
|
|
|
|
|
THREADED_TEST(isNumberType) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
// Very large number.
|
|
CompileRun("var obj = Math.pow(2,32) * 1237;");
|
|
Local<Value> obj =
|
|
env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK(!obj->IsInt32());
|
|
CHECK(!obj->IsUint32());
|
|
// Large negative number.
|
|
CompileRun("var obj = -1234567890123;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK(!obj->IsInt32());
|
|
CHECK(!obj->IsUint32());
|
|
// Small positive integer.
|
|
CompileRun("var obj = 42;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK(obj->IsInt32());
|
|
CHECK(obj->IsUint32());
|
|
// Negative integer.
|
|
CompileRun("var obj = -37;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK(obj->IsInt32());
|
|
CHECK(!obj->IsUint32());
|
|
// Positive non-int32 integer.
|
|
CompileRun("var obj = 0x81234567;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK(!obj->IsInt32());
|
|
CHECK(obj->IsUint32());
|
|
// Fraction.
|
|
CompileRun("var obj = 42.3;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK(!obj->IsInt32());
|
|
CHECK(!obj->IsUint32());
|
|
// Large negative fraction.
|
|
CompileRun("var obj = -5726623061.75;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK(!obj->IsInt32());
|
|
CHECK(!obj->IsUint32());
|
|
// Positive zero
|
|
CompileRun("var obj = 0.0;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK(obj->IsInt32());
|
|
CHECK(obj->IsUint32());
|
|
// Negative zero
|
|
CompileRun("var obj = -0.0;");
|
|
obj = env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
CHECK(!obj->IsInt32());
|
|
CHECK(!obj->IsUint32());
|
|
}
|
|
|
|
THREADED_TEST(IntegerType) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
Local<Value> result;
|
|
|
|
// Small positive integer
|
|
result = CompileRun("42;");
|
|
CHECK(result->IsNumber());
|
|
CHECK_EQ(42, result.As<v8::Integer>()->Value());
|
|
// Small negative integer
|
|
result = CompileRun("-42;");
|
|
CHECK(result->IsNumber());
|
|
CHECK_EQ(-42, result.As<v8::Integer>()->Value());
|
|
// Positive non-int32 integer
|
|
result = CompileRun("1099511627776;");
|
|
CHECK(result->IsNumber());
|
|
CHECK_EQ(1099511627776, result.As<v8::Integer>()->Value());
|
|
// Negative non-int32 integer
|
|
result = CompileRun("-1099511627776;");
|
|
CHECK(result->IsNumber());
|
|
CHECK_EQ(-1099511627776, result.As<v8::Integer>()->Value());
|
|
// Positive non-integer
|
|
result = CompileRun("3.14;");
|
|
CHECK(result->IsNumber());
|
|
CHECK_EQ(3, result.As<v8::Integer>()->Value());
|
|
// Negative non-integer
|
|
result = CompileRun("-3.14;");
|
|
CHECK(result->IsNumber());
|
|
CHECK_EQ(-3, result.As<v8::Integer>()->Value());
|
|
}
|
|
|
|
static void CheckUncle(v8::Isolate* isolate, v8::TryCatch* try_catch) {
|
|
CHECK(try_catch->HasCaught());
|
|
String::Utf8Value str_value(isolate, try_catch->Exception());
|
|
CHECK_EQ(0, strcmp(*str_value, "uncle?"));
|
|
try_catch->Reset();
|
|
}
|
|
|
|
THREADED_TEST(ConversionException) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
CompileRun(
|
|
"function TestClass() { };"
|
|
"TestClass.prototype.toString = function () { throw 'uncle?'; };"
|
|
"var obj = new TestClass();");
|
|
Local<Value> obj =
|
|
env->Global()->Get(env.local(), v8_str("obj")).ToLocalChecked();
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
CHECK(obj->ToString(env.local()).IsEmpty());
|
|
CheckUncle(isolate, &try_catch);
|
|
|
|
CHECK(obj->ToNumber(env.local()).IsEmpty());
|
|
CheckUncle(isolate, &try_catch);
|
|
|
|
CHECK(obj->ToInteger(env.local()).IsEmpty());
|
|
CheckUncle(isolate, &try_catch);
|
|
|
|
CHECK(obj->ToUint32(env.local()).IsEmpty());
|
|
CheckUncle(isolate, &try_catch);
|
|
|
|
CHECK(obj->ToInt32(env.local()).IsEmpty());
|
|
CheckUncle(isolate, &try_catch);
|
|
|
|
CHECK(v8::Undefined(isolate)->ToObject(env.local()).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
CHECK(obj->Int32Value(env.local()).IsNothing());
|
|
CheckUncle(isolate, &try_catch);
|
|
|
|
CHECK(obj->Uint32Value(env.local()).IsNothing());
|
|
CheckUncle(isolate, &try_catch);
|
|
|
|
CHECK(obj->NumberValue(env.local()).IsNothing());
|
|
CheckUncle(isolate, &try_catch);
|
|
|
|
CHECK(obj->IntegerValue(env.local()).IsNothing());
|
|
CheckUncle(isolate, &try_catch);
|
|
}
|
|
|
|
|
|
void ThrowFromC(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
args.GetIsolate()->ThrowException(v8_str("konto"));
|
|
}
|
|
|
|
|
|
void CCatcher(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
if (args.Length() < 1) {
|
|
args.GetReturnValue().Set(false);
|
|
return;
|
|
}
|
|
v8::HandleScope scope(args.GetIsolate());
|
|
v8::TryCatch try_catch(args.GetIsolate());
|
|
Local<Value> result =
|
|
CompileRun(args[0]
|
|
->ToString(args.GetIsolate()->GetCurrentContext())
|
|
.ToLocalChecked());
|
|
CHECK(!try_catch.HasCaught() || result.IsEmpty());
|
|
args.GetReturnValue().Set(try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
THREADED_TEST(APICatch) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "ThrowFromC",
|
|
v8::FunctionTemplate::New(isolate, ThrowFromC));
|
|
LocalContext context(nullptr, templ);
|
|
CompileRun(
|
|
"var thrown = false;"
|
|
"try {"
|
|
" ThrowFromC();"
|
|
"} catch (e) {"
|
|
" thrown = true;"
|
|
"}");
|
|
Local<Value> thrown = context->Global()
|
|
->Get(context.local(), v8_str("thrown"))
|
|
.ToLocalChecked();
|
|
CHECK(thrown->BooleanValue(isolate));
|
|
}
|
|
|
|
|
|
THREADED_TEST(APIThrowTryCatch) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "ThrowFromC",
|
|
v8::FunctionTemplate::New(isolate, ThrowFromC));
|
|
LocalContext context(nullptr, templ);
|
|
v8::TryCatch try_catch(isolate);
|
|
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::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "CCatcher", v8::FunctionTemplate::New(isolate, CCatcher));
|
|
LocalContext context(nullptr, templ);
|
|
Local<Value> result = CompileRun(
|
|
"try {"
|
|
" try {"
|
|
" CCatcher('throw 7;');"
|
|
" } finally {"
|
|
" }"
|
|
"} catch (e) {"
|
|
"}");
|
|
CHECK(result->IsTrue());
|
|
}
|
|
|
|
|
|
static void check_custom_error_tostring(v8::Local<v8::Message> message,
|
|
v8::Local<v8::Value> data) {
|
|
const char* uncaught_error = "Uncaught MyError toString";
|
|
CHECK(message->Get()
|
|
->Equals(CcTest::isolate()->GetCurrentContext(),
|
|
v8_str(uncaught_error))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
TEST(CustomErrorToString) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
context->GetIsolate()->AddMessageListener(check_custom_error_tostring);
|
|
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'); ");
|
|
context->GetIsolate()->RemoveMessageListeners(check_custom_error_tostring);
|
|
}
|
|
|
|
|
|
static void check_custom_error_message(v8::Local<v8::Message> message,
|
|
v8::Local<v8::Value> data) {
|
|
const char* uncaught_error = "Uncaught MyError: my message";
|
|
printf("%s\n", *v8::String::Utf8Value(CcTest::isolate(), message->Get()));
|
|
CHECK(message->Get()
|
|
->Equals(CcTest::isolate()->GetCurrentContext(),
|
|
v8_str(uncaught_error))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
TEST(CustomErrorMessage) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
context->GetIsolate()->AddMessageListener(check_custom_error_message);
|
|
|
|
// Handlebars.
|
|
CompileRun(
|
|
"function MyError(msg) { "
|
|
" this.name = 'MyError'; "
|
|
" this.message = msg; "
|
|
"} "
|
|
"MyError.prototype = new Error(); "
|
|
"throw new MyError('my message'); ");
|
|
|
|
// Closure.
|
|
CompileRun(
|
|
"function MyError(msg) { "
|
|
" this.name = 'MyError'; "
|
|
" this.message = msg; "
|
|
"} "
|
|
"inherits = function(childCtor, parentCtor) { "
|
|
" function tempCtor() {}; "
|
|
" tempCtor.prototype = parentCtor.prototype; "
|
|
" childCtor.superClass_ = parentCtor.prototype; "
|
|
" childCtor.prototype = new tempCtor(); "
|
|
" childCtor.prototype.constructor = childCtor; "
|
|
"}; "
|
|
"inherits(MyError, Error); "
|
|
"throw new MyError('my message'); ");
|
|
|
|
// Object.create.
|
|
CompileRun(
|
|
"function MyError(msg) { "
|
|
" this.name = 'MyError'; "
|
|
" this.message = msg; "
|
|
"} "
|
|
"MyError.prototype = Object.create(Error.prototype); "
|
|
"throw new MyError('my message'); ");
|
|
|
|
context->GetIsolate()->RemoveMessageListeners(check_custom_error_message);
|
|
}
|
|
|
|
|
|
static void check_custom_rethrowing_message(v8::Local<v8::Message> message,
|
|
v8::Local<v8::Value> data) {
|
|
CHECK(data->IsExternal());
|
|
int* callcount = static_cast<int*>(data.As<v8::External>()->Value());
|
|
++*callcount;
|
|
|
|
const char* uncaught_error = "Uncaught exception";
|
|
CHECK(message->Get()
|
|
->Equals(CcTest::isolate()->GetCurrentContext(),
|
|
v8_str(uncaught_error))
|
|
.FromJust());
|
|
// Test that compiling code inside a message handler works.
|
|
CHECK(CompileRunChecked(CcTest::isolate(), "(function(a) { return a; })(42)")
|
|
->Equals(CcTest::isolate()->GetCurrentContext(),
|
|
v8::Integer::NewFromUnsigned(CcTest::isolate(), 42))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
TEST(CustomErrorRethrowsOnToString) {
|
|
int callcount = 0;
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
context->GetIsolate()->AddMessageListener(
|
|
check_custom_rethrowing_message, v8::External::New(isolate, &callcount));
|
|
|
|
CompileRun(
|
|
"var e = { toString: function() { throw e; } };"
|
|
"try { throw e; } finally {}");
|
|
|
|
CHECK_EQ(callcount, 1);
|
|
context->GetIsolate()->RemoveMessageListeners(
|
|
check_custom_rethrowing_message);
|
|
}
|
|
|
|
TEST(CustomErrorRethrowsOnToStringInsideVerboseTryCatch) {
|
|
int callcount = 0;
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
try_catch.SetVerbose(true);
|
|
context->GetIsolate()->AddMessageListener(
|
|
check_custom_rethrowing_message, v8::External::New(isolate, &callcount));
|
|
|
|
CompileRun(
|
|
"var e = { toString: function() { throw e; } };"
|
|
"try { throw e; } finally {}");
|
|
|
|
CHECK_EQ(callcount, 1);
|
|
context->GetIsolate()->RemoveMessageListeners(
|
|
check_custom_rethrowing_message);
|
|
}
|
|
|
|
|
|
static void receive_message(v8::Local<v8::Message> message,
|
|
v8::Local<v8::Value> data) {
|
|
message->Get();
|
|
message_received = true;
|
|
}
|
|
|
|
|
|
TEST(APIThrowMessage) {
|
|
message_received = false;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
isolate->AddMessageListener(receive_message);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "ThrowFromC",
|
|
v8::FunctionTemplate::New(isolate, ThrowFromC));
|
|
LocalContext context(nullptr, templ);
|
|
CompileRun("ThrowFromC();");
|
|
CHECK(message_received);
|
|
isolate->RemoveMessageListeners(receive_message);
|
|
}
|
|
|
|
|
|
TEST(APIThrowMessageAndVerboseTryCatch) {
|
|
message_received = false;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
isolate->AddMessageListener(receive_message);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "ThrowFromC",
|
|
v8::FunctionTemplate::New(isolate, ThrowFromC));
|
|
LocalContext context(nullptr, templ);
|
|
v8::TryCatch try_catch(isolate);
|
|
try_catch.SetVerbose(true);
|
|
Local<Value> result = CompileRun("ThrowFromC();");
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(result.IsEmpty());
|
|
CHECK(message_received);
|
|
isolate->RemoveMessageListeners(receive_message);
|
|
}
|
|
|
|
|
|
TEST(APIStackOverflowAndVerboseTryCatch) {
|
|
message_received = false;
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
context->GetIsolate()->AddMessageListener(receive_message);
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
try_catch.SetVerbose(true);
|
|
Local<Value> result = CompileRun("function foo() { foo(); } foo();");
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(result.IsEmpty());
|
|
CHECK(message_received);
|
|
context->GetIsolate()->RemoveMessageListeners(receive_message);
|
|
}
|
|
|
|
|
|
THREADED_TEST(ExternalScriptException) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "ThrowFromC",
|
|
v8::FunctionTemplate::New(isolate, ThrowFromC));
|
|
LocalContext context(nullptr, templ);
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
Local<Value> result = CompileRun("ThrowFromC(); throw 'panama';");
|
|
CHECK(result.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
String::Utf8Value exception_value(isolate, try_catch.Exception());
|
|
CHECK_EQ(0, strcmp("konto", *exception_value));
|
|
}
|
|
|
|
|
|
void CThrowCountDown(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CHECK_EQ(4, args.Length());
|
|
v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
|
|
int count = args[0]->Int32Value(context).FromJust();
|
|
int cInterval = args[2]->Int32Value(context).FromJust();
|
|
if (count == 0) {
|
|
args.GetIsolate()->ThrowException(v8_str("FromC"));
|
|
return;
|
|
} else {
|
|
Local<v8::Object> global = context->Global();
|
|
Local<Value> fun =
|
|
global->Get(context, v8_str("JSThrowCountDown")).ToLocalChecked();
|
|
v8::Local<Value> argv[] = {v8_num(count - 1), args[1], args[2], args[3]};
|
|
if (count % cInterval == 0) {
|
|
v8::TryCatch try_catch(args.GetIsolate());
|
|
Local<Value> result = fun.As<Function>()
|
|
->Call(context, global, 4, argv)
|
|
.FromMaybe(Local<Value>());
|
|
int expected = args[3]->Int32Value(context).FromJust();
|
|
if (try_catch.HasCaught()) {
|
|
CHECK_EQ(expected, count);
|
|
CHECK(result.IsEmpty());
|
|
CHECK(!CcTest::i_isolate()->has_scheduled_exception());
|
|
} else {
|
|
CHECK_NE(expected, count);
|
|
}
|
|
args.GetReturnValue().Set(result);
|
|
return;
|
|
} else {
|
|
args.GetReturnValue().Set(fun.As<Function>()
|
|
->Call(context, global, 4, argv)
|
|
.FromMaybe(v8::Local<v8::Value>()));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void JSCheck(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CHECK_EQ(3, args.Length());
|
|
v8::Isolate* isolate = args.GetIsolate();
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
bool equality = args[0]->BooleanValue(isolate);
|
|
int count = args[1]->Int32Value(context).FromJust();
|
|
int expected = args[2]->Int32Value(context).FromJust();
|
|
if (equality) {
|
|
CHECK_EQ(count, expected);
|
|
} else {
|
|
CHECK_NE(count, expected);
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(EvalInTryFinally) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
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::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "check", v8::FunctionTemplate::New(isolate, JSCheck));
|
|
templ->Set(isolate, "CThrowCountDown",
|
|
v8::FunctionTemplate::New(isolate, CThrowCountDown));
|
|
LocalContext context(nullptr, 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(context.local(), v8_str("JSThrowCountDown"))
|
|
.ToLocalChecked());
|
|
|
|
const int argc = 4;
|
|
// count jsInterval cInterval expected
|
|
|
|
// *JS[4] *C[3] @JS[2] C[1] JS[0]
|
|
v8::Local<Value> a0[argc] = {v8_num(4), v8_num(2), v8_num(3), v8_num(2)};
|
|
fun->Call(context.local(), fun, argc, a0).ToLocalChecked();
|
|
|
|
// JS[5] *C[4] JS[3] @C[2] JS[1] C[0]
|
|
v8::Local<Value> a1[argc] = {v8_num(5), v8_num(6), v8_num(1), v8_num(2)};
|
|
fun->Call(context.local(), fun, argc, a1).ToLocalChecked();
|
|
|
|
// JS[6] @C[5] JS[4] C[3] JS[2] C[1] JS[0]
|
|
v8::Local<Value> a2[argc] = {v8_num(6), v8_num(7), v8_num(5), v8_num(5)};
|
|
fun->Call(context.local(), fun, argc, a2).ToLocalChecked();
|
|
|
|
// @JS[6] C[5] JS[4] C[3] JS[2] C[1] JS[0]
|
|
v8::Local<Value> a3[argc] = {v8_num(6), v8_num(6), v8_num(7), v8_num(6)};
|
|
fun->Call(context.local(), fun, argc, a3).ToLocalChecked();
|
|
|
|
// JS[6] *C[5] @JS[4] C[3] JS[2] C[1] JS[0]
|
|
v8::Local<Value> a4[argc] = {v8_num(6), v8_num(4), v8_num(5), v8_num(4)};
|
|
fun->Call(context.local(), fun, argc, a4).ToLocalChecked();
|
|
|
|
// JS[6] C[5] *JS[4] @C[3] JS[2] C[1] JS[0]
|
|
v8::Local<Value> a5[argc] = {v8_num(6), v8_num(4), v8_num(3), v8_num(3)};
|
|
fun->Call(context.local(), fun, argc, a5).ToLocalChecked();
|
|
}
|
|
|
|
|
|
void ThrowValue(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CHECK_EQ(1, args.Length());
|
|
args.GetIsolate()->ThrowException(args[0]);
|
|
}
|
|
|
|
|
|
THREADED_TEST(ThrowValues) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "Throw", v8::FunctionTemplate::New(isolate, ThrowValue));
|
|
LocalContext context(nullptr, templ);
|
|
v8::Local<v8::Array> result = v8::Local<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(5u, result->Length());
|
|
CHECK(result->Get(context.local(), v8::Integer::New(isolate, 0))
|
|
.ToLocalChecked()
|
|
->IsString());
|
|
CHECK(result->Get(context.local(), v8::Integer::New(isolate, 1))
|
|
.ToLocalChecked()
|
|
->IsNumber());
|
|
CHECK_EQ(1, result->Get(context.local(), v8::Integer::New(isolate, 1))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK(result->Get(context.local(), v8::Integer::New(isolate, 2))
|
|
.ToLocalChecked()
|
|
->IsNumber());
|
|
CHECK_EQ(0, result->Get(context.local(), v8::Integer::New(isolate, 2))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK(result->Get(context.local(), v8::Integer::New(isolate, 3))
|
|
.ToLocalChecked()
|
|
->IsNull());
|
|
CHECK(result->Get(context.local(), v8::Integer::New(isolate, 4))
|
|
.ToLocalChecked()
|
|
->IsUndefined());
|
|
}
|
|
|
|
|
|
THREADED_TEST(CatchZero) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
CHECK(!try_catch.HasCaught());
|
|
CompileRun("throw 10");
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK_EQ(10, try_catch.Exception()->Int32Value(context.local()).FromJust());
|
|
try_catch.Reset();
|
|
CHECK(!try_catch.HasCaught());
|
|
CompileRun("throw 0");
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK_EQ(0, try_catch.Exception()->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(CatchExceptionFromWith) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
CHECK(!try_catch.HasCaught());
|
|
CompileRun("var o = {}; with (o) { throw 42; }");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
THREADED_TEST(TryCatchAndFinallyHidingException) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
CHECK(!try_catch.HasCaught());
|
|
CompileRun("function f(k) { try { this[k]; } finally { return 0; } };");
|
|
CompileRun("f({toString: function() { throw 42; }});");
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
void WithTryCatch(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::TryCatch try_catch(args.GetIsolate());
|
|
}
|
|
|
|
|
|
THREADED_TEST(TryCatchAndFinally) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("native_with_try_catch"),
|
|
v8::FunctionTemplate::New(isolate, WithTryCatch)
|
|
->GetFunction(context.local())
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
v8::TryCatch try_catch(isolate);
|
|
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 TryCatchNested1Helper(int depth) {
|
|
if (depth > 0) {
|
|
v8::TryCatch try_catch(CcTest::isolate());
|
|
try_catch.SetVerbose(true);
|
|
TryCatchNested1Helper(depth - 1);
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.ReThrow();
|
|
} else {
|
|
CcTest::isolate()->ThrowException(v8_str("E1"));
|
|
}
|
|
}
|
|
|
|
|
|
static void TryCatchNested2Helper(int depth) {
|
|
if (depth > 0) {
|
|
v8::TryCatch try_catch(CcTest::isolate());
|
|
try_catch.SetVerbose(true);
|
|
TryCatchNested2Helper(depth - 1);
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.ReThrow();
|
|
} else {
|
|
CompileRun("throw 'E2';");
|
|
}
|
|
}
|
|
|
|
TEST(TryCatchNested) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
{
|
|
// Test nested try-catch with a native throw in the end.
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
TryCatchNested1Helper(5);
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK_EQ(0, strcmp(*v8::String::Utf8Value(context->GetIsolate(),
|
|
try_catch.Exception()),
|
|
"E1"));
|
|
}
|
|
|
|
{
|
|
// Test nested try-catch with a JavaScript throw in the end.
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
TryCatchNested2Helper(5);
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK_EQ(0, strcmp(*v8::String::Utf8Value(context->GetIsolate(),
|
|
try_catch.Exception()),
|
|
"E2"));
|
|
}
|
|
}
|
|
|
|
void TryCatchMixedNestingCheck(v8::TryCatch* try_catch) {
|
|
CHECK(try_catch->HasCaught());
|
|
Local<Message> message = try_catch->Message();
|
|
Local<Value> resource = message->GetScriptOrigin().ResourceName();
|
|
CHECK_EQ(
|
|
0, strcmp(*v8::String::Utf8Value(CcTest::isolate(), resource), "inner"));
|
|
CHECK_EQ(0, strcmp(*v8::String::Utf8Value(CcTest::isolate(), message->Get()),
|
|
"Uncaught Error: a"));
|
|
CHECK_EQ(1, message->GetLineNumber(CcTest::isolate()->GetCurrentContext())
|
|
.FromJust());
|
|
CHECK_EQ(0, message->GetStartColumn(CcTest::isolate()->GetCurrentContext())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
void TryCatchMixedNestingHelper(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::TryCatch try_catch(args.GetIsolate());
|
|
CompileRunWithOrigin("throw new Error('a');\n", "inner", 0, 0);
|
|
CHECK(try_catch.HasCaught());
|
|
TryCatchMixedNestingCheck(&try_catch);
|
|
try_catch.ReThrow();
|
|
}
|
|
|
|
|
|
// This test ensures that an outer TryCatch in the following situation:
|
|
// C++/TryCatch -> JS -> C++/TryCatch -> JS w/ SyntaxError
|
|
// does not clobber the Message object generated for the inner TryCatch.
|
|
// This exercises the ability of TryCatch.ReThrow() to restore the
|
|
// inner pending Message before throwing the exception again.
|
|
TEST(TryCatchMixedNesting) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "TryCatchMixedNestingHelper",
|
|
v8::FunctionTemplate::New(isolate, TryCatchMixedNestingHelper));
|
|
LocalContext context(nullptr, templ);
|
|
CompileRunWithOrigin("TryCatchMixedNestingHelper();\n", "outer", 1, 1);
|
|
TryCatchMixedNestingCheck(&try_catch);
|
|
}
|
|
|
|
|
|
void TryCatchNativeHelper(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::TryCatch try_catch(args.GetIsolate());
|
|
args.GetIsolate()->ThrowException(v8_str("boom"));
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
TEST(TryCatchNative) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "TryCatchNativeHelper",
|
|
v8::FunctionTemplate::New(isolate, TryCatchNativeHelper));
|
|
LocalContext context(nullptr, templ);
|
|
CompileRun("TryCatchNativeHelper();");
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
void TryCatchNativeResetHelper(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::TryCatch try_catch(args.GetIsolate());
|
|
args.GetIsolate()->ThrowException(v8_str("boom"));
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
TEST(TryCatchNativeReset) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "TryCatchNativeResetHelper",
|
|
v8::FunctionTemplate::New(isolate, TryCatchNativeResetHelper));
|
|
LocalContext context(nullptr, templ);
|
|
CompileRun("TryCatchNativeResetHelper();");
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
THREADED_TEST(Equality) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
// Check that equality works at all before relying on CHECK_EQ
|
|
CHECK(v8_str("a")->Equals(context.local(), v8_str("a")).FromJust());
|
|
CHECK(!v8_str("a")->Equals(context.local(), v8_str("b")).FromJust());
|
|
|
|
CHECK(v8_str("a")->Equals(context.local(), v8_str("a")).FromJust());
|
|
CHECK(!v8_str("a")->Equals(context.local(), v8_str("b")).FromJust());
|
|
CHECK(v8_num(1)->Equals(context.local(), v8_num(1)).FromJust());
|
|
CHECK(v8_num(1.00)->Equals(context.local(), v8_num(1)).FromJust());
|
|
CHECK(!v8_num(1)->Equals(context.local(), v8_num(2)).FromJust());
|
|
|
|
// Assume String is not internalized.
|
|
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.0)->StrictEquals(v8_num(-0.0)));
|
|
Local<Value> not_a_number = v8_num(std::numeric_limits<double>::quiet_NaN());
|
|
CHECK(!not_a_number->StrictEquals(not_a_number));
|
|
CHECK(v8::False(isolate)->StrictEquals(v8::False(isolate)));
|
|
CHECK(!v8::False(isolate)->StrictEquals(v8::Undefined(isolate)));
|
|
|
|
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
|
v8::Persistent<v8::Object> alias(isolate, obj);
|
|
CHECK(v8::Local<v8::Object>::New(isolate, alias)->StrictEquals(obj));
|
|
alias.Reset();
|
|
|
|
CHECK(v8_str("a")->SameValue(v8_str("a")));
|
|
CHECK(!v8_str("a")->SameValue(v8_str("b")));
|
|
CHECK(!v8_str("5")->SameValue(v8_num(5)));
|
|
CHECK(v8_num(1)->SameValue(v8_num(1)));
|
|
CHECK(!v8_num(1)->SameValue(v8_num(2)));
|
|
CHECK(!v8_num(0.0)->SameValue(v8_num(-0.0)));
|
|
CHECK(not_a_number->SameValue(not_a_number));
|
|
CHECK(v8::False(isolate)->SameValue(v8::False(isolate)));
|
|
CHECK(!v8::False(isolate)->SameValue(v8::Undefined(isolate)));
|
|
}
|
|
|
|
THREADED_TEST(TypeOf) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
Local<v8::FunctionTemplate> t1 = v8::FunctionTemplate::New(isolate);
|
|
Local<v8::Function> fun = t1->GetFunction(context.local()).ToLocalChecked();
|
|
|
|
CHECK(v8::Undefined(isolate)
|
|
->TypeOf(isolate)
|
|
->Equals(context.local(), v8_str("undefined"))
|
|
.FromJust());
|
|
CHECK(v8::Null(isolate)
|
|
->TypeOf(isolate)
|
|
->Equals(context.local(), v8_str("object"))
|
|
.FromJust());
|
|
CHECK(v8_str("str")
|
|
->TypeOf(isolate)
|
|
->Equals(context.local(), v8_str("string"))
|
|
.FromJust());
|
|
CHECK(v8_num(0.0)
|
|
->TypeOf(isolate)
|
|
->Equals(context.local(), v8_str("number"))
|
|
.FromJust());
|
|
CHECK(v8_num(1)
|
|
->TypeOf(isolate)
|
|
->Equals(context.local(), v8_str("number"))
|
|
.FromJust());
|
|
CHECK(v8::Object::New(isolate)
|
|
->TypeOf(isolate)
|
|
->Equals(context.local(), v8_str("object"))
|
|
.FromJust());
|
|
CHECK(v8::Boolean::New(isolate, true)
|
|
->TypeOf(isolate)
|
|
->Equals(context.local(), v8_str("boolean"))
|
|
.FromJust());
|
|
CHECK(fun->TypeOf(isolate)
|
|
->Equals(context.local(), v8_str("function"))
|
|
.FromJust());
|
|
}
|
|
|
|
THREADED_TEST(InstanceOf) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
CompileRun(
|
|
"var A = {};"
|
|
"var B = {};"
|
|
"var C = {};"
|
|
"B.__proto__ = A;"
|
|
"C.__proto__ = B;"
|
|
"function F() {}"
|
|
"F.prototype = A;"
|
|
"var G = { [Symbol.hasInstance] : null};"
|
|
"var H = { [Symbol.hasInstance] : () => { throw new Error(); } };"
|
|
"var J = { [Symbol.hasInstance] : () => true };"
|
|
"class K {}"
|
|
"var D = new K;"
|
|
"class L extends K {}"
|
|
"var E = new L");
|
|
|
|
v8::Local<v8::Object> f = v8::Local<v8::Object>::Cast(CompileRun("F"));
|
|
v8::Local<v8::Object> g = v8::Local<v8::Object>::Cast(CompileRun("G"));
|
|
v8::Local<v8::Object> h = v8::Local<v8::Object>::Cast(CompileRun("H"));
|
|
v8::Local<v8::Object> j = v8::Local<v8::Object>::Cast(CompileRun("J"));
|
|
v8::Local<v8::Object> k = v8::Local<v8::Object>::Cast(CompileRun("K"));
|
|
v8::Local<v8::Object> l = v8::Local<v8::Object>::Cast(CompileRun("L"));
|
|
v8::Local<v8::Value> a = v8::Local<v8::Value>::Cast(CompileRun("A"));
|
|
v8::Local<v8::Value> b = v8::Local<v8::Value>::Cast(CompileRun("B"));
|
|
v8::Local<v8::Value> c = v8::Local<v8::Value>::Cast(CompileRun("C"));
|
|
v8::Local<v8::Value> d = v8::Local<v8::Value>::Cast(CompileRun("D"));
|
|
v8::Local<v8::Value> e = v8::Local<v8::Value>::Cast(CompileRun("E"));
|
|
|
|
v8::TryCatch try_catch(env->GetIsolate());
|
|
CHECK(!a->InstanceOf(env.local(), f).ToChecked());
|
|
CHECK(b->InstanceOf(env.local(), f).ToChecked());
|
|
CHECK(c->InstanceOf(env.local(), f).ToChecked());
|
|
CHECK(!d->InstanceOf(env.local(), f).ToChecked());
|
|
CHECK(!e->InstanceOf(env.local(), f).ToChecked());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
CHECK(a->InstanceOf(env.local(), g).IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
CHECK(b->InstanceOf(env.local(), h).IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
CHECK(v8_num(1)->InstanceOf(env.local(), j).ToChecked());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
CHECK(d->InstanceOf(env.local(), k).ToChecked());
|
|
CHECK(e->InstanceOf(env.local(), k).ToChecked());
|
|
CHECK(!d->InstanceOf(env.local(), l).ToChecked());
|
|
CHECK(e->InstanceOf(env.local(), l).ToChecked());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
THREADED_TEST(MultiRun) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
Local<Script> script = v8_compile("x");
|
|
for (int i = 0; i < 10; i++) {
|
|
script->Run(context.local()).IsEmpty();
|
|
}
|
|
}
|
|
|
|
|
|
static void GetXValue(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CHECK(info.Data()
|
|
->Equals(CcTest::isolate()->GetCurrentContext(), v8_str("donut"))
|
|
.FromJust());
|
|
CHECK(name->Equals(CcTest::isolate()->GetCurrentContext(), v8_str("x"))
|
|
.FromJust());
|
|
info.GetReturnValue().Set(name);
|
|
}
|
|
|
|
|
|
THREADED_TEST(SimplePropertyRead) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("x"), GetXValue, nullptr, v8_str("donut"));
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
Local<Script> script = v8_compile("obj.x");
|
|
for (int i = 0; i < 10; i++) {
|
|
Local<Value> result = script->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->Equals(context.local(), v8_str("x")).FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(DefinePropertyOnAPIAccessor) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("x"), GetXValue, nullptr, v8_str("donut"));
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// Uses getOwnPropertyDescriptor to check the configurable status
|
|
Local<Script> script_desc = v8_compile(
|
|
"var prop = Object.getOwnPropertyDescriptor( "
|
|
"obj, 'x');"
|
|
"prop.configurable;");
|
|
Local<Value> result = script_desc->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->BooleanValue(isolate));
|
|
|
|
// Redefine get - but still configurable
|
|
Local<Script> script_define = v8_compile(
|
|
"var desc = { get: function(){return 42; },"
|
|
" configurable: true };"
|
|
"Object.defineProperty(obj, 'x', desc);"
|
|
"obj.x");
|
|
result = script_define->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->Equals(context.local(), v8_num(42)).FromJust());
|
|
|
|
// Check that the accessor is still configurable
|
|
result = script_desc->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->BooleanValue(isolate));
|
|
|
|
// Redefine to a non-configurable
|
|
script_define = v8_compile(
|
|
"var desc = { get: function(){return 43; },"
|
|
" configurable: false };"
|
|
"Object.defineProperty(obj, 'x', desc);"
|
|
"obj.x");
|
|
result = script_define->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->Equals(context.local(), v8_num(43)).FromJust());
|
|
result = script_desc->Run(context.local()).ToLocalChecked();
|
|
CHECK(!result->BooleanValue(isolate));
|
|
|
|
// Make sure that it is not possible to redefine again
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(script_define->Run(context.local()).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
String::Utf8Value exception_value(isolate, try_catch.Exception());
|
|
CHECK_EQ(0,
|
|
strcmp(*exception_value, "TypeError: Cannot redefine property: x"));
|
|
}
|
|
|
|
|
|
THREADED_TEST(DefinePropertyOnDefineGetterSetter) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("x"), GetXValue, nullptr, v8_str("donut"));
|
|
LocalContext context;
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
Local<Script> script_desc = v8_compile(
|
|
"var prop ="
|
|
"Object.getOwnPropertyDescriptor( "
|
|
"obj, 'x');"
|
|
"prop.configurable;");
|
|
Local<Value> result = script_desc->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->BooleanValue(isolate));
|
|
|
|
Local<Script> script_define = v8_compile(
|
|
"var desc = {get: function(){return 42; },"
|
|
" configurable: true };"
|
|
"Object.defineProperty(obj, 'x', desc);"
|
|
"obj.x");
|
|
result = script_define->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->Equals(context.local(), v8_num(42)).FromJust());
|
|
|
|
result = script_desc->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->BooleanValue(isolate));
|
|
|
|
script_define = v8_compile(
|
|
"var desc = {get: function(){return 43; },"
|
|
" configurable: false };"
|
|
"Object.defineProperty(obj, 'x', desc);"
|
|
"obj.x");
|
|
result = script_define->Run(context.local()).ToLocalChecked();
|
|
CHECK(result->Equals(context.local(), v8_num(43)).FromJust());
|
|
|
|
result = script_desc->Run(context.local()).ToLocalChecked();
|
|
CHECK(!result->BooleanValue(isolate));
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(script_define->Run(context.local()).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
String::Utf8Value exception_value(isolate, try_catch.Exception());
|
|
CHECK_EQ(0,
|
|
strcmp(*exception_value, "TypeError: Cannot redefine property: x"));
|
|
}
|
|
|
|
|
|
static v8::Local<v8::Object> GetGlobalProperty(LocalContext* context,
|
|
char const* name) {
|
|
return v8::Local<v8::Object>::Cast(
|
|
(*context)
|
|
->Global()
|
|
->Get(CcTest::isolate()->GetCurrentContext(), v8_str(name))
|
|
.ToLocalChecked());
|
|
}
|
|
|
|
|
|
THREADED_TEST(DefineAPIAccessorOnObject) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
LocalContext context;
|
|
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj1"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CompileRun("var obj2 = {};");
|
|
|
|
CHECK(CompileRun("obj1.x")->IsUndefined());
|
|
CHECK(CompileRun("obj2.x")->IsUndefined());
|
|
|
|
CHECK(GetGlobalProperty(&context, "obj1")
|
|
->SetAccessor(context.local(), v8_str("x"), GetXValue, nullptr,
|
|
v8_str("donut"))
|
|
.FromJust());
|
|
|
|
ExpectString("obj1.x", "x");
|
|
CHECK(CompileRun("obj2.x")->IsUndefined());
|
|
|
|
CHECK(GetGlobalProperty(&context, "obj2")
|
|
->SetAccessor(context.local(), v8_str("x"), GetXValue, nullptr,
|
|
v8_str("donut"))
|
|
.FromJust());
|
|
|
|
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(context.local(), v8_str("x"), GetXValue, nullptr,
|
|
v8_str("donut"))
|
|
.FromJust());
|
|
CHECK(GetGlobalProperty(&context, "obj2")
|
|
->SetAccessor(context.local(), v8_str("x"), GetXValue, nullptr,
|
|
v8_str("donut"))
|
|
.FromJust());
|
|
|
|
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(context.local(), v8_str("x"), GetXValue, nullptr,
|
|
v8_str("donut"))
|
|
.FromJust());
|
|
CHECK(!GetGlobalProperty(&context, "obj2")
|
|
->SetAccessor(context.local(), v8_str("x"), GetXValue, nullptr,
|
|
v8_str("donut"))
|
|
.FromJust());
|
|
|
|
ExpectString("obj1.x", "z");
|
|
ExpectString("obj2.x", "z");
|
|
}
|
|
|
|
|
|
THREADED_TEST(DontDeleteAPIAccessorsCannotBeOverriden) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
LocalContext context;
|
|
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj1"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CompileRun("var obj2 = {};");
|
|
|
|
CHECK(GetGlobalProperty(&context, "obj1")
|
|
->SetAccessor(context.local(), v8_str("x"), GetXValue, nullptr,
|
|
v8_str("donut"), v8::DEFAULT, v8::DontDelete)
|
|
.FromJust());
|
|
CHECK(GetGlobalProperty(&context, "obj2")
|
|
->SetAccessor(context.local(), v8_str("x"), GetXValue, nullptr,
|
|
v8_str("donut"), v8::DEFAULT, v8::DontDelete)
|
|
.FromJust());
|
|
|
|
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(context.local(), v8_str("x"), GetXValue, nullptr,
|
|
v8_str("donut"))
|
|
.FromJust());
|
|
CHECK(!GetGlobalProperty(&context, "obj2")
|
|
->SetAccessor(context.local(), v8_str("x"), GetXValue, nullptr,
|
|
v8_str("donut"))
|
|
.FromJust());
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun(
|
|
"Object.defineProperty(obj1, 'x',"
|
|
"{get: function() { return 'func'; }})");
|
|
CHECK(try_catch.HasCaught());
|
|
String::Utf8Value exception_value(isolate, try_catch.Exception());
|
|
CHECK_EQ(
|
|
0, strcmp(*exception_value, "TypeError: Cannot redefine property: x"));
|
|
}
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun(
|
|
"Object.defineProperty(obj2, 'x',"
|
|
"{get: function() { return 'func'; }})");
|
|
CHECK(try_catch.HasCaught());
|
|
String::Utf8Value exception_value(isolate, try_catch.Exception());
|
|
CHECK_EQ(
|
|
0, strcmp(*exception_value, "TypeError: Cannot redefine property: x"));
|
|
}
|
|
}
|
|
|
|
|
|
static void Get239Value(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CHECK(info.Data()
|
|
->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("donut"))
|
|
.FromJust());
|
|
CHECK(name->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("239"))
|
|
.FromJust());
|
|
info.GetReturnValue().Set(name);
|
|
}
|
|
|
|
|
|
THREADED_TEST(ElementAPIAccessor) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
LocalContext context;
|
|
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj1"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CompileRun("var obj2 = {};");
|
|
|
|
CHECK(GetGlobalProperty(&context, "obj1")
|
|
->SetAccessor(context.local(), v8_str("239"), Get239Value, nullptr,
|
|
v8_str("donut"))
|
|
.FromJust());
|
|
CHECK(GetGlobalProperty(&context, "obj2")
|
|
->SetAccessor(context.local(), v8_str("239"), Get239Value, nullptr,
|
|
v8_str("donut"))
|
|
.FromJust());
|
|
|
|
ExpectString("obj1[239]", "239");
|
|
ExpectString("obj2[239]", "239");
|
|
ExpectString("obj1['239']", "239");
|
|
ExpectString("obj2['239']", "239");
|
|
}
|
|
|
|
|
|
v8::Persistent<Value> xValue;
|
|
|
|
|
|
static void SetXValue(Local<Name> name, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<void>& info) {
|
|
Local<Context> context = info.GetIsolate()->GetCurrentContext();
|
|
CHECK(value->Equals(context, v8_num(4)).FromJust());
|
|
CHECK(info.Data()->Equals(context, v8_str("donut")).FromJust());
|
|
CHECK(name->Equals(context, v8_str("x")).FromJust());
|
|
CHECK(xValue.IsEmpty());
|
|
xValue.Reset(info.GetIsolate(), value);
|
|
}
|
|
|
|
|
|
THREADED_TEST(SimplePropertyWrite) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("x"), GetXValue, SetXValue, v8_str("donut"));
|
|
LocalContext context;
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
Local<Script> script = v8_compile("obj.x = 4");
|
|
for (int i = 0; i < 10; i++) {
|
|
CHECK(xValue.IsEmpty());
|
|
script->Run(context.local()).ToLocalChecked();
|
|
CHECK(v8_num(4)
|
|
->Equals(context.local(),
|
|
Local<Value>::New(CcTest::isolate(), xValue))
|
|
.FromJust());
|
|
xValue.Reset();
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(SetterOnly) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("x"), nullptr, SetXValue, v8_str("donut"));
|
|
LocalContext context;
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
Local<Script> script = v8_compile("obj.x = 4; obj.x");
|
|
for (int i = 0; i < 10; i++) {
|
|
CHECK(xValue.IsEmpty());
|
|
script->Run(context.local()).ToLocalChecked();
|
|
CHECK(v8_num(4)
|
|
->Equals(context.local(),
|
|
Local<Value>::New(CcTest::isolate(), xValue))
|
|
.FromJust());
|
|
xValue.Reset();
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(NoAccessors) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("x"),
|
|
static_cast<v8::AccessorGetterCallback>(nullptr), nullptr,
|
|
v8_str("donut"));
|
|
LocalContext context;
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
Local<Script> script = v8_compile("obj.x = 4; obj.x");
|
|
for (int i = 0; i < 10; i++) {
|
|
script->Run(context.local()).ToLocalChecked();
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(MultiContexts) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "dummy",
|
|
v8::FunctionTemplate::New(isolate, DummyCallHandler));
|
|
|
|
Local<String> password = v8_str("Password");
|
|
|
|
// Create an environment
|
|
LocalContext context0(nullptr, templ);
|
|
context0->SetSecurityToken(password);
|
|
v8::Local<v8::Object> global0 = context0->Global();
|
|
CHECK(global0->Set(context0.local(), v8_str("custom"), v8_num(1234))
|
|
.FromJust());
|
|
CHECK_EQ(1234, global0->Get(context0.local(), v8_str("custom"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context0.local())
|
|
.FromJust());
|
|
|
|
// Create an independent environment
|
|
LocalContext context1(nullptr, templ);
|
|
context1->SetSecurityToken(password);
|
|
v8::Local<v8::Object> global1 = context1->Global();
|
|
CHECK(global1->Set(context1.local(), v8_str("custom"), v8_num(1234))
|
|
.FromJust());
|
|
CHECK(!global0->Equals(context1.local(), global1).FromJust());
|
|
CHECK_EQ(1234, global0->Get(context1.local(), v8_str("custom"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context0.local())
|
|
.FromJust());
|
|
CHECK_EQ(1234, global1->Get(context1.local(), v8_str("custom"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context1.local())
|
|
.FromJust());
|
|
|
|
// Now create a new context with the old global
|
|
LocalContext context2(nullptr, templ, global1);
|
|
context2->SetSecurityToken(password);
|
|
v8::Local<v8::Object> global2 = context2->Global();
|
|
CHECK(global1->Equals(context2.local(), global2).FromJust());
|
|
CHECK_EQ(0, global1->Get(context2.local(), v8_str("custom"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context1.local())
|
|
.FromJust());
|
|
CHECK_EQ(0, global2->Get(context2.local(), v8_str("custom"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context2.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionPrototypeAcrossContexts) {
|
|
// Make sure that functions created by cloning boilerplates cannot
|
|
// communicate through their __proto__ field.
|
|
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
|
|
LocalContext env0;
|
|
v8::Local<v8::Object> global0 = env0->Global();
|
|
v8::Local<v8::Object> object0 = global0->Get(env0.local(), v8_str("Object"))
|
|
.ToLocalChecked()
|
|
.As<v8::Object>();
|
|
v8::Local<v8::Object> tostring0 =
|
|
object0->Get(env0.local(), v8_str("toString"))
|
|
.ToLocalChecked()
|
|
.As<v8::Object>();
|
|
v8::Local<v8::Object> proto0 =
|
|
tostring0->Get(env0.local(), v8_str("__proto__"))
|
|
.ToLocalChecked()
|
|
.As<v8::Object>();
|
|
CHECK(proto0->Set(env0.local(), v8_str("custom"), v8_num(1234)).FromJust());
|
|
|
|
LocalContext env1;
|
|
v8::Local<v8::Object> global1 = env1->Global();
|
|
v8::Local<v8::Object> object1 = global1->Get(env1.local(), v8_str("Object"))
|
|
.ToLocalChecked()
|
|
.As<v8::Object>();
|
|
v8::Local<v8::Object> tostring1 =
|
|
object1->Get(env1.local(), v8_str("toString"))
|
|
.ToLocalChecked()
|
|
.As<v8::Object>();
|
|
v8::Local<v8::Object> proto1 =
|
|
tostring1->Get(env1.local(), v8_str("__proto__"))
|
|
.ToLocalChecked()
|
|
.As<v8::Object>();
|
|
CHECK(!proto1->Has(env1.local(), v8_str("custom")).FromJust());
|
|
}
|
|
|
|
|
|
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(CcTest::isolate());
|
|
|
|
Local<String> source = v8_str(
|
|
"Object.prototype.obj = 1234;"
|
|
"Array.prototype.arr = 4567;"
|
|
"8901");
|
|
|
|
LocalContext env0;
|
|
Local<Script> script0 = v8_compile(source);
|
|
CHECK_EQ(8901.0, script0->Run(env0.local())
|
|
.ToLocalChecked()
|
|
->NumberValue(env0.local())
|
|
.FromJust());
|
|
|
|
LocalContext env1;
|
|
Local<Script> script1 = v8_compile(source);
|
|
CHECK_EQ(8901.0, script1->Run(env1.local())
|
|
.ToLocalChecked()
|
|
->NumberValue(env1.local())
|
|
.FromJust());
|
|
}
|
|
|
|
static void ReturnThis(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
args.GetReturnValue().Set(args.This());
|
|
}
|
|
|
|
THREADED_TEST(UndetectableObject) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
|
|
Local<v8::FunctionTemplate> desc =
|
|
v8::FunctionTemplate::New(env->GetIsolate());
|
|
desc->InstanceTemplate()->MarkAsUndetectable(); // undetectable
|
|
desc->InstanceTemplate()->SetCallAsFunctionHandler(ReturnThis); // callable
|
|
|
|
Local<v8::Object> obj = desc->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked();
|
|
|
|
CHECK(obj->IsUndetectable());
|
|
|
|
CHECK(
|
|
env->Global()->Set(env.local(), v8_str("undetectable"), obj).FromJust());
|
|
|
|
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) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> desc = v8::FunctionTemplate::New(isolate);
|
|
desc->InstanceTemplate()->MarkAsUndetectable(); // undetectable
|
|
desc->InstanceTemplate()->SetCallAsFunctionHandler(ReturnThis); // callable
|
|
|
|
Local<v8::Object> obj = desc->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked();
|
|
CHECK(
|
|
env->Global()->Set(env.local(), v8_str("undetectable"), obj).FromJust());
|
|
|
|
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) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> desc = v8::FunctionTemplate::New(isolate);
|
|
desc->InstanceTemplate()->MarkAsUndetectable(); // undetectable
|
|
desc->InstanceTemplate()->SetCallAsFunctionHandler(ReturnThis); // callable
|
|
|
|
Local<v8::Object> obj = desc->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked();
|
|
CHECK(
|
|
env->Global()->Set(env.local(), v8_str("undetectable"), obj).FromJust());
|
|
|
|
Local<String> source = v8_str(
|
|
"undetectable.x = 42;"
|
|
"undetectable.x");
|
|
|
|
Local<Script> script = v8_compile(source);
|
|
|
|
CHECK(v8::Integer::New(isolate, 42)
|
|
->Equals(env.local(), script->Run(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
ExpectBoolean("Object.isExtensible(undetectable)", true);
|
|
|
|
source = v8_str("Object.preventExtensions(undetectable);");
|
|
script = v8_compile(source);
|
|
script->Run(env.local()).ToLocalChecked();
|
|
ExpectBoolean("Object.isExtensible(undetectable)", false);
|
|
|
|
source = v8_str("undetectable.y = 2000;");
|
|
script = v8_compile(source);
|
|
script->Run(env.local()).ToLocalChecked();
|
|
ExpectBoolean("undetectable.y == undefined", true);
|
|
}
|
|
|
|
THREADED_TEST(ConstructCallWithUndetectable) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> desc = v8::FunctionTemplate::New(isolate);
|
|
desc->InstanceTemplate()->MarkAsUndetectable(); // undetectable
|
|
desc->InstanceTemplate()->SetCallAsFunctionHandler(ReturnThis); // callable
|
|
|
|
Local<v8::Object> obj = desc->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked();
|
|
CHECK(
|
|
env->Global()->Set(env.local(), v8_str("undetectable"), obj).FromJust());
|
|
|
|
// Undetectable object cannot be called as constructor.
|
|
v8::TryCatch try_catch(env->GetIsolate());
|
|
CHECK(CompileRun("new undetectable()").IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
String::Utf8Value exception_value(env->GetIsolate(), try_catch.Exception());
|
|
CHECK_EQ(0, strcmp("TypeError: undetectable is not a constructor",
|
|
*exception_value));
|
|
}
|
|
|
|
static int increment_callback_counter = 0;
|
|
|
|
static void IncrementCounterConstructCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
increment_callback_counter++;
|
|
CHECK(Local<Object>::Cast(args.NewTarget())
|
|
->Set(args.GetIsolate()->GetCurrentContext(), v8_str("counter"),
|
|
v8_num(increment_callback_counter))
|
|
.FromJust());
|
|
args.GetReturnValue().Set(args.NewTarget());
|
|
}
|
|
|
|
THREADED_TEST(SetCallAsFunctionHandlerConstructor) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> desc = v8::FunctionTemplate::New(isolate);
|
|
desc->InstanceTemplate()->SetCallAsFunctionHandler(
|
|
IncrementCounterConstructCallback); // callable
|
|
|
|
Local<v8::Object> obj = desc->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("Counter"), obj).FromJust());
|
|
|
|
ExpectInt32("(new Counter()).counter", 1);
|
|
CHECK_EQ(1, increment_callback_counter);
|
|
ExpectInt32("(new Counter()).counter", 2);
|
|
CHECK_EQ(2, increment_callback_counter);
|
|
}
|
|
// The point of this test is type checking. We run it only so compilers
|
|
// don't complain about an unused function.
|
|
TEST(PersistentHandles) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<String> str = v8_str("foo");
|
|
v8::Persistent<String> p_str(isolate, str);
|
|
p_str.Reset();
|
|
Local<Script> scr = v8_compile("");
|
|
v8::Persistent<Script> p_scr(isolate, scr);
|
|
p_scr.Reset();
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
v8::Persistent<ObjectTemplate> p_templ(isolate, templ);
|
|
p_templ.Reset();
|
|
}
|
|
|
|
|
|
static void HandleLogDelegator(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
}
|
|
|
|
|
|
THREADED_TEST(GlobalObjectTemplate) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
|
|
global_template->Set(isolate, "JSNI_Log",
|
|
v8::FunctionTemplate::New(isolate, HandleLogDelegator));
|
|
v8::Local<Context> context = Context::New(isolate, nullptr, global_template);
|
|
Context::Scope context_scope(context);
|
|
CompileRun("JSNI_Log('LOG')");
|
|
}
|
|
|
|
|
|
static const char* kSimpleExtensionSource =
|
|
"function Foo() {"
|
|
" return 4;"
|
|
"}";
|
|
|
|
|
|
TEST(SimpleExtensions) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("simpletest", kSimpleExtensionSource));
|
|
const char* extension_names[] = {"simpletest"};
|
|
v8::ExtensionConfiguration extensions(1, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
Context::Scope lock(context);
|
|
v8::Local<Value> result = CompileRun("Foo()");
|
|
CHECK(result->Equals(context, v8::Integer::New(CcTest::isolate(), 4))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
static const char* kStackTraceFromExtensionSource =
|
|
"function foo() {"
|
|
" throw new Error();"
|
|
"}"
|
|
"function bar() {"
|
|
" foo();"
|
|
"}";
|
|
|
|
|
|
TEST(StackTraceInExtension) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
v8::RegisterExtension(std::make_unique<Extension>(
|
|
"stacktracetest", kStackTraceFromExtensionSource));
|
|
const char* extension_names[] = {"stacktracetest"};
|
|
v8::ExtensionConfiguration extensions(1, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
Context::Scope lock(context);
|
|
CompileRun(
|
|
"function user() { bar(); }"
|
|
"var error;"
|
|
"try{ user(); } catch (e) { error = e; }");
|
|
CHECK_EQ(-1, v8_run_int32value(v8_compile("error.stack.indexOf('foo')")));
|
|
CHECK_EQ(-1, v8_run_int32value(v8_compile("error.stack.indexOf('bar')")));
|
|
CHECK_NE(-1, v8_run_int32value(v8_compile("error.stack.indexOf('user')")));
|
|
}
|
|
|
|
|
|
TEST(NullExtensions) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
v8::RegisterExtension(std::make_unique<Extension>("nulltest", nullptr));
|
|
const char* extension_names[] = {"nulltest"};
|
|
v8::ExtensionConfiguration extensions(1, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
Context::Scope lock(context);
|
|
v8::Local<Value> result = CompileRun("1+3");
|
|
CHECK(result->Equals(context, v8::Integer::New(CcTest::isolate(), 4))
|
|
.FromJust());
|
|
}
|
|
|
|
static const char* kEmbeddedExtensionSource =
|
|
"function Ret54321(){return 54321;}~~@@$"
|
|
"$%% THIS IS A SERIES OF NON-nullptr-TERMINATED STRINGS.";
|
|
static const int kEmbeddedExtensionSourceValidLen = 34;
|
|
|
|
|
|
TEST(ExtensionMissingSourceLength) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("srclentest_fail", kEmbeddedExtensionSource));
|
|
const char* extension_names[] = {"srclentest_fail"};
|
|
v8::ExtensionConfiguration extensions(1, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
CHECK_NULL(*context);
|
|
}
|
|
|
|
|
|
TEST(ExtensionWithSourceLength) {
|
|
for (int source_len = kEmbeddedExtensionSourceValidLen - 1;
|
|
source_len <= kEmbeddedExtensionSourceValidLen + 1; ++source_len) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
v8::base::ScopedVector<char> extension_name(32);
|
|
v8::base::SNPrintF(extension_name, "ext #%d", source_len);
|
|
v8::RegisterExtension(std::make_unique<Extension>(extension_name.begin(),
|
|
kEmbeddedExtensionSource,
|
|
0, nullptr, source_len));
|
|
const char* extension_names[1] = {extension_name.begin()};
|
|
v8::ExtensionConfiguration extensions(1, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
if (source_len == kEmbeddedExtensionSourceValidLen) {
|
|
Context::Scope lock(context);
|
|
v8::Local<Value> result = CompileRun("Ret54321()");
|
|
CHECK(v8::Integer::New(CcTest::isolate(), 54321)
|
|
->Equals(context, result)
|
|
.FromJust());
|
|
} else {
|
|
// Anything but exactly the right length should fail to compile.
|
|
CHECK_NULL(*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;"
|
|
"})()";
|
|
|
|
|
|
TEST(UseEvalFromExtension) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("evaltest1", kEvalExtensionSource1));
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("evaltest2", kEvalExtensionSource2));
|
|
const char* extension_names[] = {"evaltest1", "evaltest2"};
|
|
v8::ExtensionConfiguration extensions(2, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
Context::Scope lock(context);
|
|
v8::Local<Value> result = CompileRun("UseEval1()");
|
|
CHECK(result->Equals(context, v8::Integer::New(CcTest::isolate(), 42))
|
|
.FromJust());
|
|
result = CompileRun("UseEval2()");
|
|
CHECK(result->Equals(context, v8::Integer::New(CcTest::isolate(), 42))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
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;"
|
|
"})()";
|
|
|
|
|
|
TEST(UseWithFromExtension) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("withtest1", kWithExtensionSource1));
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("withtest2", kWithExtensionSource2));
|
|
const char* extension_names[] = {"withtest1", "withtest2"};
|
|
v8::ExtensionConfiguration extensions(2, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
Context::Scope lock(context);
|
|
v8::Local<Value> result = CompileRun("UseWith1()");
|
|
CHECK(result->Equals(context, v8::Integer::New(CcTest::isolate(), 87))
|
|
.FromJust());
|
|
result = CompileRun("UseWith2()");
|
|
CHECK(result->Equals(context, v8::Integer::New(CcTest::isolate(), 87))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
TEST(AutoExtensions) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
auto extension =
|
|
std::make_unique<Extension>("autotest", kSimpleExtensionSource);
|
|
extension->set_auto_enable(true);
|
|
v8::RegisterExtension(std::move(extension));
|
|
v8::Local<Context> context = Context::New(CcTest::isolate());
|
|
Context::Scope lock(context);
|
|
v8::Local<Value> result = CompileRun("Foo()");
|
|
CHECK(result->Equals(context, v8::Integer::New(CcTest::isolate(), 4))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
static const char* kSyntaxErrorInExtensionSource = "[";
|
|
|
|
|
|
// Test that a syntax error in an extension does not cause a fatal
|
|
// error but results in an empty context.
|
|
TEST(SyntaxErrorExtensions) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
v8::RegisterExtension(std::make_unique<Extension>(
|
|
"syntaxerror", kSyntaxErrorInExtensionSource));
|
|
const char* extension_names[] = {"syntaxerror"};
|
|
v8::ExtensionConfiguration extensions(1, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &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.
|
|
TEST(ExceptionExtensions) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("exception", kExceptionInExtensionSource));
|
|
const char* extension_names[] = {"exception"};
|
|
v8::ExtensionConfiguration extensions(1, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
CHECK(context.IsEmpty());
|
|
}
|
|
|
|
static const char* kNativeCallInExtensionSource =
|
|
"function call_runtime_last_index_of(x) {"
|
|
" return %StringLastIndexOf(x, 'bob');"
|
|
"}";
|
|
|
|
static const char* kNativeCallTest =
|
|
"call_runtime_last_index_of('bobbobboellebobboellebobbob');";
|
|
|
|
// Test that a native runtime calls are supported in extensions.
|
|
TEST(NativeCallInExtensions) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("nativecall", kNativeCallInExtensionSource));
|
|
const char* extension_names[] = {"nativecall"};
|
|
v8::ExtensionConfiguration extensions(1, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
Context::Scope lock(context);
|
|
v8::Local<Value> result = CompileRun(kNativeCallTest);
|
|
CHECK(result->Equals(context, v8::Integer::New(CcTest::isolate(), 24))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
class NativeFunctionExtension : public Extension {
|
|
public:
|
|
NativeFunctionExtension(const char* name, const char* source,
|
|
v8::FunctionCallback fun = &Echo)
|
|
: Extension(name, source), function_(fun) {}
|
|
|
|
v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
|
|
v8::Isolate* isolate, v8::Local<v8::String> name) override {
|
|
return v8::FunctionTemplate::New(isolate, function_);
|
|
}
|
|
|
|
static void Echo(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
if (args.Length() >= 1) args.GetReturnValue().Set(args[0]);
|
|
}
|
|
|
|
private:
|
|
v8::FunctionCallback function_;
|
|
};
|
|
|
|
|
|
TEST(NativeFunctionDeclaration) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
const char* name = "nativedecl";
|
|
v8::RegisterExtension(std::make_unique<NativeFunctionExtension>(
|
|
name, "native function foo();"));
|
|
const char* extension_names[] = {name};
|
|
v8::ExtensionConfiguration extensions(1, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
Context::Scope lock(context);
|
|
v8::Local<Value> result = CompileRun("foo(42);");
|
|
CHECK(result->Equals(context, v8::Integer::New(CcTest::isolate(), 42))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
TEST(NativeFunctionDeclarationError) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
const char* name = "nativedeclerr";
|
|
// Syntax error in extension code.
|
|
v8::RegisterExtension(std::make_unique<NativeFunctionExtension>(
|
|
name, "native\nfunction foo();"));
|
|
const char* extension_names[] = {name};
|
|
v8::ExtensionConfiguration extensions(1, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
CHECK(context.IsEmpty());
|
|
}
|
|
|
|
|
|
TEST(NativeFunctionDeclarationErrorEscape) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
const char* name = "nativedeclerresc";
|
|
// Syntax error in extension code - escape code in "native" means that
|
|
// it's not treated as a keyword.
|
|
v8::RegisterExtension(std::make_unique<NativeFunctionExtension>(
|
|
name, "nativ\\u0065 function foo();"));
|
|
const char* extension_names[] = {name};
|
|
v8::ExtensionConfiguration extensions(1, extension_names);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &extensions);
|
|
CHECK(context.IsEmpty());
|
|
}
|
|
|
|
|
|
static void CheckDependencies(const char* name, const char* expected) {
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
v8::ExtensionConfiguration config(1, &name);
|
|
LocalContext context(&config);
|
|
CHECK(
|
|
v8_str(expected)
|
|
->Equals(context.local(), context->Global()
|
|
->Get(context.local(), v8_str("loaded"))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
/*
|
|
* Configuration:
|
|
*
|
|
* /-- B <--\
|
|
* A <- -- D <-- E
|
|
* \-- C <--/
|
|
*/
|
|
THREADED_TEST(ExtensionDependency) {
|
|
static const char* kEDeps[] = {"D"};
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("E", "this.loaded += 'E';", 1, kEDeps));
|
|
static const char* kDDeps[] = {"B", "C"};
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("D", "this.loaded += 'D';", 2, kDDeps));
|
|
static const char* kBCDeps[] = {"A"};
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("B", "this.loaded += 'B';", 1, kBCDeps));
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("C", "this.loaded += 'C';", 1, kBCDeps));
|
|
v8::RegisterExtension(
|
|
std::make_unique<Extension>("A", "this.loaded += 'A';"));
|
|
CheckDependencies("A", "undefinedA");
|
|
CheckDependencies("B", "undefinedAB");
|
|
CheckDependencies("C", "undefinedAC");
|
|
CheckDependencies("D", "undefinedABCD");
|
|
CheckDependencies("E", "undefinedABCDE");
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
static const char* exts[2] = {"C", "E"};
|
|
v8::ExtensionConfiguration config(2, exts);
|
|
LocalContext context(&config);
|
|
CHECK(
|
|
v8_str("undefinedACBDE")
|
|
->Equals(context.local(), context->Global()
|
|
->Get(context.local(), v8_str("loaded"))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
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 void CallFun(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (args.IsConstructCall()) {
|
|
CHECK(args.This()
|
|
->Set(args.GetIsolate()->GetCurrentContext(), v8_str("data"),
|
|
args.Data())
|
|
.FromJust());
|
|
args.GetReturnValue().SetNull();
|
|
return;
|
|
}
|
|
args.GetReturnValue().Set(args.Data());
|
|
}
|
|
|
|
|
|
class FunctionExtension : public Extension {
|
|
public:
|
|
FunctionExtension() : Extension("functiontest", kExtensionTestScript) {}
|
|
v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
|
|
v8::Isolate* isolate, v8::Local<String> name) override;
|
|
};
|
|
|
|
|
|
static int lookup_count = 0;
|
|
v8::Local<v8::FunctionTemplate> FunctionExtension::GetNativeFunctionTemplate(
|
|
v8::Isolate* isolate, v8::Local<String> name) {
|
|
lookup_count++;
|
|
if (name->StrictEquals(v8_str("A"))) {
|
|
return v8::FunctionTemplate::New(isolate, CallFun,
|
|
v8::Integer::New(isolate, 8));
|
|
} else if (name->StrictEquals(v8_str("B"))) {
|
|
return v8::FunctionTemplate::New(isolate, CallFun,
|
|
v8::Integer::New(isolate, 7));
|
|
} else if (name->StrictEquals(v8_str("C"))) {
|
|
return v8::FunctionTemplate::New(isolate, CallFun,
|
|
v8::Integer::New(isolate, 6));
|
|
} else {
|
|
return v8::Local<v8::FunctionTemplate>();
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionLookup) {
|
|
v8::RegisterExtension(std::make_unique<FunctionExtension>());
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
static const char* exts[1] = {"functiontest"};
|
|
v8::ExtensionConfiguration config(1, exts);
|
|
LocalContext context(&config);
|
|
CHECK_EQ(3, lookup_count);
|
|
CHECK(v8::Integer::New(CcTest::isolate(), 8)
|
|
->Equals(context.local(), CompileRun("Foo(0)"))
|
|
.FromJust());
|
|
CHECK(v8::Integer::New(CcTest::isolate(), 7)
|
|
->Equals(context.local(), CompileRun("Foo(1)"))
|
|
.FromJust());
|
|
CHECK(v8::Integer::New(CcTest::isolate(), 6)
|
|
->Equals(context.local(), CompileRun("Foo(2)"))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(NativeFunctionConstructCall) {
|
|
v8::RegisterExtension(std::make_unique<FunctionExtension>());
|
|
v8::HandleScope handle_scope(CcTest::isolate());
|
|
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(v8::Integer::New(CcTest::isolate(), 8)
|
|
->Equals(context.local(), CompileRun("(new A()).data"))
|
|
.FromJust());
|
|
CHECK(v8::Integer::New(CcTest::isolate(), 7)
|
|
->Equals(context.local(), CompileRun("(new B()).data"))
|
|
.FromJust());
|
|
CHECK(v8::Integer::New(CcTest::isolate(), 6)
|
|
->Equals(context.local(), CompileRun("(new C()).data"))
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
static const char* last_location;
|
|
static const char* last_message;
|
|
void StoringErrorCallback(const char* location, const char* message) {
|
|
if (last_location == nullptr) {
|
|
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) {
|
|
CcTest::isolate()->SetFatalErrorHandler(StoringErrorCallback);
|
|
static const char* aDeps[] = {"B"};
|
|
v8::RegisterExtension(std::make_unique<Extension>("A", "", 1, aDeps));
|
|
static const char* bDeps[] = {"A"};
|
|
v8::RegisterExtension(std::make_unique<Extension>("B", "", 1, bDeps));
|
|
last_location = nullptr;
|
|
v8::ExtensionConfiguration config(1, bDeps);
|
|
v8::Local<Context> context = Context::New(CcTest::isolate(), &config);
|
|
CHECK(context.IsEmpty());
|
|
CHECK(last_location);
|
|
}
|
|
|
|
static size_t dcheck_count;
|
|
void DcheckErrorCallback(const char* file, int line, const char* message) {
|
|
last_message = message;
|
|
++dcheck_count;
|
|
}
|
|
|
|
TEST(DcheckErrorHandler) {
|
|
V8::SetDcheckErrorHandler(DcheckErrorCallback);
|
|
|
|
last_message = nullptr;
|
|
dcheck_count = 0;
|
|
|
|
DCHECK(false && "w00t");
|
|
#ifdef DEBUG
|
|
CHECK_EQ(dcheck_count, 1);
|
|
CHECK(last_message);
|
|
CHECK(std::string(last_message).find("w00t") != std::string::npos);
|
|
#else
|
|
// The DCHECK should be a noop in non-DEBUG builds.
|
|
CHECK_EQ(dcheck_count, 0);
|
|
#endif
|
|
}
|
|
|
|
static void MissingScriptInfoMessageListener(v8::Local<v8::Message> message,
|
|
v8::Local<Value> data) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
Local<Context> context = isolate->GetCurrentContext();
|
|
CHECK(message->GetScriptOrigin().ResourceName()->IsUndefined());
|
|
CHECK(v8::Undefined(isolate)
|
|
->Equals(context, message->GetScriptOrigin().ResourceName())
|
|
.FromJust());
|
|
message->GetLineNumber(context).FromJust();
|
|
message->GetSourceLine(context).ToLocalChecked();
|
|
}
|
|
|
|
|
|
THREADED_TEST(ErrorWithMissingScriptInfo) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
context->GetIsolate()->AddMessageListener(MissingScriptInfoMessageListener);
|
|
CompileRun("throw Error()");
|
|
context->GetIsolate()->RemoveMessageListeners(
|
|
MissingScriptInfoMessageListener);
|
|
}
|
|
|
|
|
|
struct FlagAndPersistent {
|
|
bool flag;
|
|
v8::Global<v8::Object> handle;
|
|
};
|
|
|
|
static void SetFlag(const v8::WeakCallbackInfo<FlagAndPersistent>& data) {
|
|
data.GetParameter()->flag = true;
|
|
data.GetParameter()->handle.Reset();
|
|
}
|
|
|
|
static void IndependentWeakHandle(bool global_gc, bool interlinked) {
|
|
ManualGCScope manual_gc_scope;
|
|
// Parallel scavenge introduces too much fragmentation.
|
|
i::FLAG_parallel_scavenge = false;
|
|
v8::Isolate* iso = CcTest::isolate();
|
|
v8::HandleScope scope(iso);
|
|
v8::Local<Context> context = Context::New(iso);
|
|
Context::Scope context_scope(context);
|
|
|
|
FlagAndPersistent object_a, object_b;
|
|
|
|
size_t big_heap_size = 0;
|
|
size_t big_array_size = 0;
|
|
|
|
{
|
|
v8::HandleScope handle_scope(iso);
|
|
Local<Object> a(v8::Object::New(iso));
|
|
Local<Object> b(v8::Object::New(iso));
|
|
object_a.handle.Reset(iso, a);
|
|
object_b.handle.Reset(iso, b);
|
|
if (interlinked) {
|
|
a->Set(context, v8_str("x"), b).FromJust();
|
|
b->Set(context, v8_str("x"), a).FromJust();
|
|
}
|
|
if (v8::internal::FLAG_single_generation || global_gc) {
|
|
CcTest::CollectAllGarbage();
|
|
} else {
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
}
|
|
v8::Local<Value> big_array = v8::Array::New(CcTest::isolate(), 5000);
|
|
// Verify that we created an array where the space was reserved up front.
|
|
big_array_size =
|
|
v8::internal::JSArray::cast(*v8::Utils::OpenHandle(*big_array))
|
|
.elements()
|
|
.Size();
|
|
CHECK_LE(20000, big_array_size);
|
|
a->Set(context, v8_str("y"), big_array).FromJust();
|
|
big_heap_size = CcTest::heap()->SizeOfObjects();
|
|
}
|
|
|
|
object_a.flag = false;
|
|
object_b.flag = false;
|
|
object_a.handle.SetWeak(&object_a, &SetFlag,
|
|
v8::WeakCallbackType::kParameter);
|
|
object_b.handle.SetWeak(&object_b, &SetFlag,
|
|
v8::WeakCallbackType::kParameter);
|
|
if (v8::internal::FLAG_single_generation || global_gc) {
|
|
CcTest::CollectAllGarbage();
|
|
} else {
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
}
|
|
// A single GC should be enough to reclaim the memory, since we are using
|
|
// phantom handles.
|
|
CHECK_GT(big_heap_size - big_array_size, CcTest::heap()->SizeOfObjects());
|
|
CHECK(object_a.flag);
|
|
CHECK(object_b.flag);
|
|
}
|
|
|
|
TEST(IndependentWeakHandle) {
|
|
IndependentWeakHandle(false, false);
|
|
IndependentWeakHandle(false, true);
|
|
IndependentWeakHandle(true, false);
|
|
IndependentWeakHandle(true, true);
|
|
}
|
|
|
|
class Trivial {
|
|
public:
|
|
explicit Trivial(int x) : x_(x) {}
|
|
|
|
int x() { return x_; }
|
|
void set_x(int x) { x_ = x; }
|
|
|
|
private:
|
|
int x_;
|
|
};
|
|
|
|
|
|
class Trivial2 {
|
|
public:
|
|
Trivial2(int x, int y) : y_(y), x_(x) {}
|
|
|
|
int x() { return x_; }
|
|
void set_x(int x) { x_ = x; }
|
|
|
|
int y() { return y_; }
|
|
void set_y(int y) { y_ = y; }
|
|
|
|
private:
|
|
int y_;
|
|
int x_;
|
|
};
|
|
|
|
void CheckInternalFields(
|
|
const v8::WeakCallbackInfo<v8::Persistent<v8::Object>>& data) {
|
|
v8::Persistent<v8::Object>* handle = data.GetParameter();
|
|
handle->Reset();
|
|
Trivial* t1 = reinterpret_cast<Trivial*>(data.GetInternalField(0));
|
|
Trivial2* t2 = reinterpret_cast<Trivial2*>(data.GetInternalField(1));
|
|
CHECK_EQ(42, t1->x());
|
|
CHECK_EQ(103, t2->x());
|
|
t1->set_x(1729);
|
|
t2->set_x(33550336);
|
|
}
|
|
|
|
void InternalFieldCallback(bool global_gc) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
Local<v8::ObjectTemplate> instance_templ = templ->InstanceTemplate();
|
|
Trivial* t1;
|
|
Trivial2* t2;
|
|
instance_templ->SetInternalFieldCount(2);
|
|
v8::Persistent<v8::Object> handle;
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
Local<v8::Object> obj = templ->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked();
|
|
handle.Reset(isolate, obj);
|
|
CHECK_EQ(2, obj->InternalFieldCount());
|
|
CHECK(obj->GetInternalField(0)->IsUndefined());
|
|
t1 = new Trivial(42);
|
|
t2 = new Trivial2(103, 9);
|
|
|
|
obj->SetAlignedPointerInInternalField(0, t1);
|
|
t1 = reinterpret_cast<Trivial*>(obj->GetAlignedPointerFromInternalField(0));
|
|
CHECK_EQ(42, t1->x());
|
|
|
|
obj->SetAlignedPointerInInternalField(1, t2);
|
|
t2 =
|
|
reinterpret_cast<Trivial2*>(obj->GetAlignedPointerFromInternalField(1));
|
|
CHECK_EQ(103, t2->x());
|
|
|
|
handle.SetWeak<v8::Persistent<v8::Object>>(
|
|
&handle, CheckInternalFields, v8::WeakCallbackType::kInternalFields);
|
|
}
|
|
if (v8::internal::FLAG_single_generation || global_gc) {
|
|
CcTest::CollectAllGarbage();
|
|
} else {
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
}
|
|
|
|
CHECK_EQ(1729, t1->x());
|
|
CHECK_EQ(33550336, t2->x());
|
|
|
|
delete t1;
|
|
delete t2;
|
|
}
|
|
|
|
THREADED_TEST(InternalFieldCallback) {
|
|
InternalFieldCallback(false);
|
|
InternalFieldCallback(true);
|
|
}
|
|
|
|
static void ResetUseValueAndSetFlag(
|
|
const v8::WeakCallbackInfo<FlagAndPersistent>& data) {
|
|
// Blink will reset the handle, and then use the other handle, so they
|
|
// can't use the same backing slot.
|
|
data.GetParameter()->handle.Reset();
|
|
data.GetParameter()->flag = true;
|
|
}
|
|
|
|
void v8::internal::heap::HeapTester::ResetWeakHandle(bool global_gc) {
|
|
using v8::Context;
|
|
using v8::Local;
|
|
using v8::Object;
|
|
|
|
v8::Isolate* iso = CcTest::isolate();
|
|
v8::HandleScope scope(iso);
|
|
v8::Local<Context> context = Context::New(iso);
|
|
Context::Scope context_scope(context);
|
|
|
|
FlagAndPersistent object_a, object_b;
|
|
|
|
{
|
|
v8::HandleScope handle_scope(iso);
|
|
Local<Object> a(v8::Object::New(iso));
|
|
Local<Object> b(v8::Object::New(iso));
|
|
object_a.handle.Reset(iso, a);
|
|
object_b.handle.Reset(iso, b);
|
|
if (global_gc || FLAG_single_generation) {
|
|
CcTest::PreciseCollectAllGarbage();
|
|
} else {
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
}
|
|
}
|
|
|
|
object_a.flag = false;
|
|
object_b.flag = false;
|
|
object_a.handle.SetWeak(&object_a, &ResetUseValueAndSetFlag,
|
|
v8::WeakCallbackType::kParameter);
|
|
object_b.handle.SetWeak(&object_b, &ResetUseValueAndSetFlag,
|
|
v8::WeakCallbackType::kParameter);
|
|
if (global_gc || FLAG_single_generation) {
|
|
CcTest::PreciseCollectAllGarbage();
|
|
} else {
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
}
|
|
CHECK(object_a.flag);
|
|
CHECK(object_b.flag);
|
|
}
|
|
|
|
THREADED_HEAP_TEST(ResetWeakHandle) {
|
|
v8::internal::heap::HeapTester::ResetWeakHandle(false);
|
|
v8::internal::heap::HeapTester::ResetWeakHandle(true);
|
|
}
|
|
|
|
static void InvokeScavenge() { CcTest::CollectGarbage(i::NEW_SPACE); }
|
|
|
|
static void InvokeMarkSweep() { CcTest::CollectAllGarbage(); }
|
|
|
|
static void ForceScavenge2(
|
|
const v8::WeakCallbackInfo<FlagAndPersistent>& data) {
|
|
data.GetParameter()->flag = true;
|
|
InvokeScavenge();
|
|
}
|
|
|
|
static void ForceScavenge1(
|
|
const v8::WeakCallbackInfo<FlagAndPersistent>& data) {
|
|
data.GetParameter()->handle.Reset();
|
|
data.SetSecondPassCallback(ForceScavenge2);
|
|
}
|
|
|
|
static void ForceMarkSweep2(
|
|
const v8::WeakCallbackInfo<FlagAndPersistent>& data) {
|
|
data.GetParameter()->flag = true;
|
|
InvokeMarkSweep();
|
|
}
|
|
|
|
static void ForceMarkSweep1(
|
|
const v8::WeakCallbackInfo<FlagAndPersistent>& data) {
|
|
data.GetParameter()->handle.Reset();
|
|
data.SetSecondPassCallback(ForceMarkSweep2);
|
|
}
|
|
|
|
THREADED_TEST(GCFromWeakCallbacks) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Locker locker(CcTest::isolate());
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<Context> context = Context::New(isolate);
|
|
Context::Scope context_scope(context);
|
|
|
|
if (v8::internal::FLAG_single_generation) {
|
|
FlagAndPersistent object;
|
|
{
|
|
v8::HandleScope handle_scope(isolate);
|
|
object.handle.Reset(isolate, v8::Object::New(isolate));
|
|
}
|
|
object.flag = false;
|
|
object.handle.SetWeak(&object, &ForceMarkSweep1,
|
|
v8::WeakCallbackType::kParameter);
|
|
InvokeMarkSweep();
|
|
EmptyMessageQueues(isolate);
|
|
CHECK(object.flag);
|
|
return;
|
|
}
|
|
|
|
static const int kNumberOfGCTypes = 2;
|
|
using Callback = v8::WeakCallbackInfo<FlagAndPersistent>::Callback;
|
|
Callback gc_forcing_callback[kNumberOfGCTypes] = {&ForceScavenge1,
|
|
&ForceMarkSweep1};
|
|
|
|
using GCInvoker = void (*)();
|
|
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++) {
|
|
FlagAndPersistent object;
|
|
{
|
|
v8::HandleScope handle_scope(isolate);
|
|
object.handle.Reset(isolate, v8::Object::New(isolate));
|
|
}
|
|
object.flag = false;
|
|
object.handle.SetWeak(&object, gc_forcing_callback[inner_gc],
|
|
v8::WeakCallbackType::kParameter);
|
|
invoke_gc[outer_gc]();
|
|
EmptyMessageQueues(isolate);
|
|
CHECK(object.flag);
|
|
}
|
|
}
|
|
}
|
|
|
|
v8::Local<Function> args_fun;
|
|
|
|
|
|
static void ArgumentsTestCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Isolate* isolate = args.GetIsolate();
|
|
Local<Context> context = isolate->GetCurrentContext();
|
|
CHECK_EQ(3, args.Length());
|
|
CHECK(v8::Integer::New(isolate, 1)->Equals(context, args[0]).FromJust());
|
|
CHECK(v8::Integer::New(isolate, 2)->Equals(context, args[1]).FromJust());
|
|
CHECK(v8::Integer::New(isolate, 3)->Equals(context, args[2]).FromJust());
|
|
CHECK(v8::Undefined(isolate)->Equals(context, args[3]).FromJust());
|
|
v8::HandleScope scope(args.GetIsolate());
|
|
CcTest::CollectAllGarbage();
|
|
}
|
|
|
|
|
|
THREADED_TEST(Arguments) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> global = ObjectTemplate::New(isolate);
|
|
global->Set(isolate, "f",
|
|
v8::FunctionTemplate::New(isolate, ArgumentsTestCallback));
|
|
LocalContext context(nullptr, global);
|
|
args_fun = context->Global()
|
|
->Get(context.local(), v8_str("f"))
|
|
.ToLocalChecked()
|
|
.As<Function>();
|
|
v8_compile("f(1, 2, 3)")->Run(context.local()).ToLocalChecked();
|
|
}
|
|
|
|
|
|
static int p_getter_count;
|
|
static int p_getter_count2;
|
|
|
|
|
|
static void PGetter(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
p_getter_count++;
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
v8::Local<v8::Object> global = context->Global();
|
|
CHECK(
|
|
info.Holder()
|
|
->Equals(context, global->Get(context, v8_str("o1")).ToLocalChecked())
|
|
.FromJust());
|
|
if (name->Equals(context, v8_str("p1")).FromJust()) {
|
|
CHECK(info.This()
|
|
->Equals(context,
|
|
global->Get(context, v8_str("o1")).ToLocalChecked())
|
|
.FromJust());
|
|
} else if (name->Equals(context, v8_str("p2")).FromJust()) {
|
|
CHECK(info.This()
|
|
->Equals(context,
|
|
global->Get(context, v8_str("o2")).ToLocalChecked())
|
|
.FromJust());
|
|
} else if (name->Equals(context, v8_str("p3")).FromJust()) {
|
|
CHECK(info.This()
|
|
->Equals(context,
|
|
global->Get(context, v8_str("o3")).ToLocalChecked())
|
|
.FromJust());
|
|
} else if (name->Equals(context, v8_str("p4")).FromJust()) {
|
|
CHECK(info.This()
|
|
->Equals(context,
|
|
global->Get(context, v8_str("o4")).ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
static void RunHolderTest(v8::Local<v8::ObjectTemplate> obj) {
|
|
ApiTestFuzzer::Fuzz();
|
|
LocalContext context;
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("o1"),
|
|
obj->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
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 void PGetter2(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
p_getter_count2++;
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
v8::Local<v8::Object> global = context->Global();
|
|
CHECK(
|
|
info.Holder()
|
|
->Equals(context, global->Get(context, v8_str("o1")).ToLocalChecked())
|
|
.FromJust());
|
|
if (name->Equals(context, v8_str("p1")).FromJust()) {
|
|
CHECK(info.This()
|
|
->Equals(context,
|
|
global->Get(context, v8_str("o1")).ToLocalChecked())
|
|
.FromJust());
|
|
} else if (name->Equals(context, v8_str("p2")).FromJust()) {
|
|
CHECK(info.This()
|
|
->Equals(context,
|
|
global->Get(context, v8_str("o2")).ToLocalChecked())
|
|
.FromJust());
|
|
} else if (name->Equals(context, v8_str("p3")).FromJust()) {
|
|
CHECK(info.This()
|
|
->Equals(context,
|
|
global->Get(context, v8_str("o3")).ToLocalChecked())
|
|
.FromJust());
|
|
} else if (name->Equals(context, v8_str("p4")).FromJust()) {
|
|
CHECK(info.This()
|
|
->Equals(context,
|
|
global->Get(context, v8_str("o4")).ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(GetterHolders) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
|
|
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::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
|
|
obj->SetHandler(v8::NamedPropertyHandlerConfiguration(PGetter2));
|
|
p_getter_count2 = 0;
|
|
RunHolderTest(obj);
|
|
CHECK_EQ(40, p_getter_count2);
|
|
}
|
|
|
|
|
|
THREADED_TEST(ObjectInstantiation) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("t"), PGetter2);
|
|
LocalContext context;
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
for (int i = 0; i < 100; i++) {
|
|
v8::HandleScope inner_scope(CcTest::isolate());
|
|
v8::Local<v8::Object> obj =
|
|
templ->NewInstance(context.local()).ToLocalChecked();
|
|
CHECK(!obj->Equals(context.local(), context->Global()
|
|
->Get(context.local(), v8_str("o"))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(
|
|
context->Global()->Set(context.local(), v8_str("o2"), obj).FromJust());
|
|
v8::Local<Value> value = CompileRun("o.__proto__ === o2.__proto__");
|
|
CHECK(v8::True(isolate)->Equals(context.local(), value).FromJust());
|
|
CHECK(context->Global()->Set(context.local(), v8_str("o"), obj).FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
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(v8::Isolate* isolate, Local<String> str) {
|
|
int len = str->Utf8Length(isolate);
|
|
if (len < 0) {
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
i::Handle<i::String> istr(v8::Utils::OpenHandle(*str));
|
|
i::String::Flatten(i_isolate, istr);
|
|
len = str->Utf8Length(isolate);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
|
|
THREADED_TEST(StringWrite) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<String> str = v8_str("abcde");
|
|
// abc<Icelandic eth><Unicode snowman>.
|
|
v8::Local<String> str2 = v8_str("abc\xC3\xB0\xE2\x98\x83");
|
|
v8::Local<String> str3 =
|
|
v8::String::NewFromUtf8Literal(context->GetIsolate(), "abc\0def");
|
|
// "ab" + lead surrogate + "wx" + trail surrogate + "yz"
|
|
uint16_t orphans[8] = {0x61, 0x62, 0xD800, 0x77, 0x78, 0xDC00, 0x79, 0x7A};
|
|
v8::Local<String> orphans_str =
|
|
v8::String::NewFromTwoByte(context->GetIsolate(), orphans,
|
|
v8::NewStringType::kNormal, 8)
|
|
.ToLocalChecked();
|
|
// single lead surrogate
|
|
uint16_t lead[1] = {0xD800};
|
|
v8::Local<String> lead_str =
|
|
v8::String::NewFromTwoByte(context->GetIsolate(), lead,
|
|
v8::NewStringType::kNormal, 1)
|
|
.ToLocalChecked();
|
|
// single trail surrogate
|
|
uint16_t trail[1] = {0xDC00};
|
|
v8::Local<String> trail_str =
|
|
v8::String::NewFromTwoByte(context->GetIsolate(), trail,
|
|
v8::NewStringType::kNormal, 1)
|
|
.ToLocalChecked();
|
|
// surrogate pair
|
|
uint16_t pair[2] = {0xD800, 0xDC00};
|
|
v8::Local<String> pair_str =
|
|
v8::String::NewFromTwoByte(context->GetIsolate(), pair,
|
|
v8::NewStringType::kNormal, 2)
|
|
.ToLocalChecked();
|
|
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::Local<v8::Object> global = context->Global();
|
|
Local<String> left_tree = global->Get(context.local(), v8_str("left"))
|
|
.ToLocalChecked()
|
|
.As<String>();
|
|
Local<String> right_tree = global->Get(context.local(), v8_str("right"))
|
|
.ToLocalChecked()
|
|
.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 = v8::String::Empty(isolate)->WriteUtf8(isolate, utf8buf, sizeof(utf8buf),
|
|
&charlen);
|
|
CHECK_EQ(1, len);
|
|
CHECK_EQ(0, charlen);
|
|
CHECK_EQ(0, strcmp(utf8buf, ""));
|
|
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = str2->WriteUtf8(isolate, utf8buf, sizeof(utf8buf), &charlen);
|
|
CHECK_EQ(9, len);
|
|
CHECK_EQ(5, charlen);
|
|
CHECK_EQ(0, strcmp(utf8buf, "abc\xC3\xB0\xE2\x98\x83"));
|
|
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = str2->WriteUtf8(isolate, utf8buf, 8, &charlen);
|
|
CHECK_EQ(8, len);
|
|
CHECK_EQ(5, charlen);
|
|
CHECK_EQ(0, strncmp(utf8buf, "abc\xC3\xB0\xE2\x98\x83\x01", 9));
|
|
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = str2->WriteUtf8(isolate, utf8buf, 7, &charlen);
|
|
CHECK_EQ(5, len);
|
|
CHECK_EQ(4, charlen);
|
|
CHECK_EQ(0, strncmp(utf8buf, "abc\xC3\xB0\x01", 5));
|
|
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = str2->WriteUtf8(isolate, utf8buf, 6, &charlen);
|
|
CHECK_EQ(5, len);
|
|
CHECK_EQ(4, charlen);
|
|
CHECK_EQ(0, strncmp(utf8buf, "abc\xC3\xB0\x01", 5));
|
|
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = str2->WriteUtf8(isolate, utf8buf, 5, &charlen);
|
|
CHECK_EQ(5, len);
|
|
CHECK_EQ(4, charlen);
|
|
CHECK_EQ(0, strncmp(utf8buf, "abc\xC3\xB0\x01", 5));
|
|
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = str2->WriteUtf8(isolate, utf8buf, 4, &charlen);
|
|
CHECK_EQ(3, len);
|
|
CHECK_EQ(3, charlen);
|
|
CHECK_EQ(0, strncmp(utf8buf, "abc\x01", 4));
|
|
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = str2->WriteUtf8(isolate, utf8buf, 3, &charlen);
|
|
CHECK_EQ(3, len);
|
|
CHECK_EQ(3, charlen);
|
|
CHECK_EQ(0, strncmp(utf8buf, "abc\x01", 4));
|
|
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = str2->WriteUtf8(isolate, utf8buf, 2, &charlen);
|
|
CHECK_EQ(2, len);
|
|
CHECK_EQ(2, charlen);
|
|
CHECK_EQ(0, strncmp(utf8buf, "ab\x01", 3));
|
|
|
|
// allow orphan surrogates by default
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = orphans_str->WriteUtf8(isolate, utf8buf, sizeof(utf8buf), &charlen);
|
|
CHECK_EQ(13, len);
|
|
CHECK_EQ(8, charlen);
|
|
CHECK_EQ(0, strcmp(utf8buf, "ab\xED\xA0\x80wx\xED\xB0\x80yz"));
|
|
|
|
// replace orphan surrogates with Unicode replacement character
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = orphans_str->WriteUtf8(isolate, utf8buf, sizeof(utf8buf), &charlen,
|
|
String::REPLACE_INVALID_UTF8);
|
|
CHECK_EQ(13, len);
|
|
CHECK_EQ(8, charlen);
|
|
CHECK_EQ(0, strcmp(utf8buf, "ab\xEF\xBF\xBDwx\xEF\xBF\xBDyz"));
|
|
|
|
// replace single lead surrogate with Unicode replacement character
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = lead_str->WriteUtf8(isolate, utf8buf, sizeof(utf8buf), &charlen,
|
|
String::REPLACE_INVALID_UTF8);
|
|
CHECK_EQ(4, len);
|
|
CHECK_EQ(1, charlen);
|
|
CHECK_EQ(0, strcmp(utf8buf, "\xEF\xBF\xBD"));
|
|
|
|
// replace single trail surrogate with Unicode replacement character
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = trail_str->WriteUtf8(isolate, utf8buf, sizeof(utf8buf), &charlen,
|
|
String::REPLACE_INVALID_UTF8);
|
|
CHECK_EQ(4, len);
|
|
CHECK_EQ(1, charlen);
|
|
CHECK_EQ(0, strcmp(utf8buf, "\xEF\xBF\xBD"));
|
|
|
|
// do not replace / write anything if surrogate pair does not fit the buffer
|
|
// space
|
|
memset(utf8buf, 0x1, 1000);
|
|
len = pair_str->WriteUtf8(isolate, utf8buf, 3, &charlen,
|
|
String::REPLACE_INVALID_UTF8);
|
|
CHECK_EQ(0, len);
|
|
CHECK_EQ(0, charlen);
|
|
|
|
memset(utf8buf, 0x1, sizeof(utf8buf));
|
|
len = GetUtf8Length(isolate, left_tree);
|
|
int utf8_expected =
|
|
(0x80 + (0x800 - 0x80) * 2 + (0xD800 - 0x800) * 3) / kStride;
|
|
CHECK_EQ(utf8_expected, len);
|
|
len = left_tree->WriteUtf8(isolate, 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(isolate, right_tree);
|
|
CHECK_EQ(utf8_expected, len);
|
|
len = right_tree->WriteUtf8(isolate, 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->WriteOneByte(isolate, reinterpret_cast<uint8_t*>(buf));
|
|
CHECK_EQ(5, len);
|
|
len = str->Write(isolate, 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->WriteOneByte(isolate, reinterpret_cast<uint8_t*>(buf), 0, 4);
|
|
CHECK_EQ(4, len);
|
|
len = str->Write(isolate, wbuf, 0, 4);
|
|
CHECK_EQ(4, len);
|
|
CHECK_EQ(0, strncmp("abcd\x01", 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->WriteOneByte(isolate, reinterpret_cast<uint8_t*>(buf), 0, 5);
|
|
CHECK_EQ(5, len);
|
|
len = str->Write(isolate, wbuf, 0, 5);
|
|
CHECK_EQ(5, len);
|
|
CHECK_EQ(0, strncmp("abcde\x01", 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->WriteOneByte(isolate, reinterpret_cast<uint8_t*>(buf), 0, 6);
|
|
CHECK_EQ(5, len);
|
|
len = str->Write(isolate, 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->WriteOneByte(isolate, reinterpret_cast<uint8_t*>(buf), 4, -1);
|
|
CHECK_EQ(1, len);
|
|
len = str->Write(isolate, 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->WriteOneByte(isolate, reinterpret_cast<uint8_t*>(buf), 4, 6);
|
|
CHECK_EQ(1, len);
|
|
len = str->Write(isolate, 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->WriteOneByte(isolate, reinterpret_cast<uint8_t*>(buf), 4, 1);
|
|
CHECK_EQ(1, len);
|
|
len = str->Write(isolate, wbuf, 4, 1);
|
|
CHECK_EQ(1, len);
|
|
CHECK_EQ(0, strncmp("e\x01", 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->WriteOneByte(isolate, reinterpret_cast<uint8_t*>(buf), 3, 1);
|
|
CHECK_EQ(1, len);
|
|
len = str->Write(isolate, wbuf, 3, 1);
|
|
CHECK_EQ(1, len);
|
|
CHECK_EQ(0, strncmp("d\x01", 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(isolate, 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->WriteOneByte(isolate, reinterpret_cast<uint8_t*>(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(isolate, 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\xC3\xB0\xE2\x98\x83", 8));
|
|
CHECK_NE(0, strcmp(utf8buf, "abc\xC3\xB0\xE2\x98\x83"));
|
|
utf8buf[8] = '\0';
|
|
CHECK_EQ(0, strcmp(utf8buf, "abc\xC3\xB0\xE2\x98\x83"));
|
|
|
|
memset(utf8buf, 0x1, sizeof(utf8buf));
|
|
utf8buf[5] = 'X';
|
|
len = str->WriteUtf8(isolate, 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->WriteOneByte(isolate, reinterpret_cast<uint8_t*>(buf));
|
|
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->WriteOneByte(isolate, nullptr, 0, 0,
|
|
String::NO_NULL_TERMINATION));
|
|
CHECK_EQ(0, str->WriteUtf8(isolate, nullptr, 0, nullptr,
|
|
String::NO_NULL_TERMINATION));
|
|
CHECK_EQ(0, str->Write(isolate, nullptr, 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(context.local(), v8_str(name)).ToLocalChecked());
|
|
Local<v8::Array> alens =
|
|
Local<v8::Array>::Cast(context->Global()
|
|
->Get(context.local(), v8_str(lengths_name))
|
|
.ToLocalChecked());
|
|
for (int i = 0; i < len; i++) {
|
|
Local<v8::String> string =
|
|
Local<v8::String>::Cast(a->Get(context.local(), i).ToLocalChecked());
|
|
Local<v8::Number> expected_len = Local<v8::Number>::Cast(
|
|
alens->Get(context.local(), i).ToLocalChecked());
|
|
int length = GetUtf8Length(context->GetIsolate(), string);
|
|
CHECK_EQ(static_cast<int>(expected_len->Value()), length);
|
|
}
|
|
}
|
|
|
|
void TestUtf8DecodingAgainstReference(
|
|
v8::Isolate* isolate, const char* cases[],
|
|
const std::vector<std::vector<uint16_t>>& unicode_expected) {
|
|
for (size_t test_ix = 0; test_ix < unicode_expected.size(); ++test_ix) {
|
|
v8::Local<String> str = v8_str(cases[test_ix]);
|
|
CHECK_EQ(unicode_expected[test_ix].size(), str->Length());
|
|
|
|
std::unique_ptr<uint16_t[]> buffer(new uint16_t[str->Length()]);
|
|
str->Write(isolate, buffer.get(), 0, -1, String::NO_NULL_TERMINATION);
|
|
|
|
for (size_t i = 0; i < unicode_expected[test_ix].size(); ++i) {
|
|
CHECK_EQ(unicode_expected[test_ix][i], buffer[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
THREADED_TEST(OverlongSequencesAndSurrogates) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
const char* cases[] = {
|
|
// Overlong 2-byte sequence.
|
|
"X\xc0\xbfY\0",
|
|
// Another overlong 2-byte sequence.
|
|
"X\xc1\xbfY\0",
|
|
// Overlong 3-byte sequence.
|
|
"X\xe0\x9f\xbfY\0",
|
|
// Overlong 4-byte sequence.
|
|
"X\xf0\x89\xbf\xbfY\0",
|
|
// Invalid 3-byte sequence (reserved for surrogates).
|
|
"X\xed\xa0\x80Y\0",
|
|
// Invalid 4-bytes sequence (value out of range).
|
|
"X\xf4\x90\x80\x80Y\0",
|
|
|
|
// Start of an overlong 3-byte sequence but not enough continuation bytes.
|
|
"X\xe0\x9fY\0",
|
|
// Start of an overlong 4-byte sequence but not enough continuation bytes.
|
|
"X\xf0\x89\xbfY\0",
|
|
// Start of an invalid 3-byte sequence (reserved for surrogates) but not
|
|
// enough continuation bytes.
|
|
"X\xed\xa0Y\0",
|
|
// Start of an invalid 4-bytes sequence (value out of range) but not
|
|
// enough continuation bytes.
|
|
"X\xf4\x90\x80Y\0",
|
|
};
|
|
const std::vector<std::vector<uint16_t>> unicode_expected = {
|
|
{0x58, 0xFFFD, 0xFFFD, 0x59},
|
|
{0x58, 0xFFFD, 0xFFFD, 0x59},
|
|
{0x58, 0xFFFD, 0xFFFD, 0xFFFD, 0x59},
|
|
{0x58, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0x59},
|
|
{0x58, 0xFFFD, 0xFFFD, 0xFFFD, 0x59},
|
|
{0x58, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0x59},
|
|
{0x58, 0xFFFD, 0xFFFD, 0x59},
|
|
{0x58, 0xFFFD, 0xFFFD, 0xFFFD, 0x59},
|
|
{0x58, 0xFFFD, 0xFFFD, 0x59},
|
|
{0x58, 0xFFFD, 0xFFFD, 0xFFFD, 0x59},
|
|
};
|
|
CHECK_EQ(unicode_expected.size(), arraysize(cases));
|
|
TestUtf8DecodingAgainstReference(context->GetIsolate(), cases,
|
|
unicode_expected);
|
|
}
|
|
|
|
THREADED_TEST(Utf16) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
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);
|
|
}
|
|
|
|
|
|
static bool SameSymbol(Local<String> s1, Local<String> s2) {
|
|
i::Handle<i::String> is1(v8::Utils::OpenHandle(*s1));
|
|
i::Handle<i::String> is2(v8::Utils::OpenHandle(*s2));
|
|
return *is1 == *is2;
|
|
}
|
|
|
|
|
|
THREADED_TEST(Utf16Symbol) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
Local<String> symbol1 = v8::String::NewFromUtf8Literal(
|
|
context->GetIsolate(), "abc", v8::NewStringType::kInternalized);
|
|
Local<String> symbol2 = v8::String::NewFromUtf8Literal(
|
|
context->GetIsolate(), "abc", v8::NewStringType::kInternalized);
|
|
CHECK(SameSymbol(symbol1, symbol2));
|
|
|
|
CompileRun(
|
|
"var sym0 = 'benedictus';"
|
|
"var sym0b = 'S\xC3\xB8ren';"
|
|
"var sym1 = '\xED\xA0\x81\xED\xB0\x87';"
|
|
"var sym2 = '\xF0\x90\x90\x88';"
|
|
"var sym3 = 'x\xED\xA0\x81\xED\xB0\x87';"
|
|
"var sym4 = 'x\xF0\x90\x90\x88';"
|
|
"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);");
|
|
Local<String> sym0 = v8::String::NewFromUtf8Literal(
|
|
context->GetIsolate(), "benedictus", v8::NewStringType::kInternalized);
|
|
Local<String> sym0b = v8::String::NewFromUtf8Literal(
|
|
context->GetIsolate(), "S\xC3\xB8ren", v8::NewStringType::kInternalized);
|
|
Local<String> sym1 = v8::String::NewFromUtf8Literal(
|
|
context->GetIsolate(), "\xED\xA0\x81\xED\xB0\x87",
|
|
v8::NewStringType::kInternalized);
|
|
Local<String> sym2 =
|
|
v8::String::NewFromUtf8Literal(context->GetIsolate(), "\xF0\x90\x90\x88",
|
|
v8::NewStringType::kInternalized);
|
|
Local<String> sym3 = v8::String::NewFromUtf8Literal(
|
|
context->GetIsolate(), "x\xED\xA0\x81\xED\xB0\x87",
|
|
v8::NewStringType::kInternalized);
|
|
Local<String> sym4 =
|
|
v8::String::NewFromUtf8Literal(context->GetIsolate(), "x\xF0\x90\x90\x88",
|
|
v8::NewStringType::kInternalized);
|
|
v8::Local<v8::Object> global = context->Global();
|
|
Local<Value> s0 =
|
|
global->Get(context.local(), v8_str("sym0")).ToLocalChecked();
|
|
Local<Value> s0b =
|
|
global->Get(context.local(), v8_str("sym0b")).ToLocalChecked();
|
|
Local<Value> s1 =
|
|
global->Get(context.local(), v8_str("sym1")).ToLocalChecked();
|
|
Local<Value> s2 =
|
|
global->Get(context.local(), v8_str("sym2")).ToLocalChecked();
|
|
Local<Value> s3 =
|
|
global->Get(context.local(), v8_str("sym3")).ToLocalChecked();
|
|
Local<Value> s4 =
|
|
global->Get(context.local(), v8_str("sym4")).ToLocalChecked();
|
|
CHECK(SameSymbol(sym0, Local<String>::Cast(s0)));
|
|
CHECK(SameSymbol(sym0b, Local<String>::Cast(s0b)));
|
|
CHECK(SameSymbol(sym1, Local<String>::Cast(s1)));
|
|
CHECK(SameSymbol(sym2, Local<String>::Cast(s2)));
|
|
CHECK(SameSymbol(sym3, Local<String>::Cast(s3)));
|
|
CHECK(SameSymbol(sym4, Local<String>::Cast(s4)));
|
|
}
|
|
|
|
|
|
THREADED_TEST(Utf16MissingTrailing) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
// Make sure it will go past the buffer, so it will call `WriteUtf16Slow`
|
|
int size = 1024 * 64;
|
|
uint8_t* buffer = new uint8_t[size];
|
|
for (int i = 0; i < size; i += 4) {
|
|
buffer[i] = 0xF0;
|
|
buffer[i + 1] = 0x9D;
|
|
buffer[i + 2] = 0x80;
|
|
buffer[i + 3] = 0x9E;
|
|
}
|
|
|
|
// Now invoke the decoder without last 3 bytes
|
|
v8::Local<v8::String> str =
|
|
v8::String::NewFromUtf8(
|
|
context->GetIsolate(), reinterpret_cast<char*>(buffer),
|
|
v8::NewStringType::kNormal, size - 3).ToLocalChecked();
|
|
USE(str);
|
|
delete[] buffer;
|
|
}
|
|
|
|
|
|
THREADED_TEST(Utf16Trailing3Byte) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Make sure it will go past the buffer, so it will call `WriteUtf16Slow`
|
|
int size = 1024 * 63;
|
|
uint8_t* buffer = new uint8_t[size];
|
|
for (int i = 0; i < size; i += 3) {
|
|
buffer[i] = 0xE2;
|
|
buffer[i + 1] = 0x80;
|
|
buffer[i + 2] = 0xA6;
|
|
}
|
|
|
|
// Now invoke the decoder without last 3 bytes
|
|
v8::Local<v8::String> str =
|
|
v8::String::NewFromUtf8(isolate, reinterpret_cast<char*>(buffer),
|
|
v8::NewStringType::kNormal, size)
|
|
.ToLocalChecked();
|
|
|
|
v8::String::Value value(isolate, str);
|
|
CHECK_EQ(value.length(), size / 3);
|
|
CHECK_EQ((*value)[value.length() - 1], 0x2026);
|
|
|
|
delete[] buffer;
|
|
}
|
|
|
|
|
|
THREADED_TEST(ToArrayIndex) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<String> str = v8_str("42");
|
|
v8::MaybeLocal<v8::Uint32> index = str->ToArrayIndex(context.local());
|
|
CHECK(!index.IsEmpty());
|
|
CHECK_EQ(42.0,
|
|
index.ToLocalChecked()->Uint32Value(context.local()).FromJust());
|
|
str = v8_str("42asdf");
|
|
index = str->ToArrayIndex(context.local());
|
|
CHECK(index.IsEmpty());
|
|
str = v8_str("-42");
|
|
index = str->ToArrayIndex(context.local());
|
|
CHECK(index.IsEmpty());
|
|
str = v8_str("4294967294");
|
|
index = str->ToArrayIndex(context.local());
|
|
CHECK(!index.IsEmpty());
|
|
CHECK_EQ(4294967294.0,
|
|
index.ToLocalChecked()->Uint32Value(context.local()).FromJust());
|
|
v8::Local<v8::Number> num = v8::Number::New(isolate, 1);
|
|
index = num->ToArrayIndex(context.local());
|
|
CHECK(!index.IsEmpty());
|
|
CHECK_EQ(1.0,
|
|
index.ToLocalChecked()->Uint32Value(context.local()).FromJust());
|
|
num = v8::Number::New(isolate, -1);
|
|
index = num->ToArrayIndex(context.local());
|
|
CHECK(index.IsEmpty());
|
|
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
|
index = obj->ToArrayIndex(context.local());
|
|
CHECK(index.IsEmpty());
|
|
}
|
|
|
|
THREADED_TEST(ErrorConstruction) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
v8::Local<String> foo = v8_str("foo");
|
|
v8::Local<String> message = v8_str("message");
|
|
v8::Local<Value> range_error = v8::Exception::RangeError(foo);
|
|
CHECK(range_error->IsObject());
|
|
CHECK(range_error.As<v8::Object>()
|
|
->Get(context.local(), message)
|
|
.ToLocalChecked()
|
|
->Equals(context.local(), foo)
|
|
.FromJust());
|
|
v8::Local<Value> reference_error = v8::Exception::ReferenceError(foo);
|
|
CHECK(reference_error->IsObject());
|
|
CHECK(reference_error.As<v8::Object>()
|
|
->Get(context.local(), message)
|
|
.ToLocalChecked()
|
|
->Equals(context.local(), foo)
|
|
.FromJust());
|
|
v8::Local<Value> syntax_error = v8::Exception::SyntaxError(foo);
|
|
CHECK(syntax_error->IsObject());
|
|
CHECK(syntax_error.As<v8::Object>()
|
|
->Get(context.local(), message)
|
|
.ToLocalChecked()
|
|
->Equals(context.local(), foo)
|
|
.FromJust());
|
|
v8::Local<Value> type_error = v8::Exception::TypeError(foo);
|
|
CHECK(type_error->IsObject());
|
|
CHECK(type_error.As<v8::Object>()
|
|
->Get(context.local(), message)
|
|
.ToLocalChecked()
|
|
->Equals(context.local(), foo)
|
|
.FromJust());
|
|
v8::Local<Value> error = v8::Exception::Error(foo);
|
|
CHECK(error->IsObject());
|
|
CHECK(error.As<v8::Object>()
|
|
->Get(context.local(), message)
|
|
.ToLocalChecked()
|
|
->Equals(context.local(), foo)
|
|
.FromJust());
|
|
}
|
|
|
|
THREADED_TEST(ExceptionCreateMessageLength) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
// Test that the message is not truncated.
|
|
TryCatch try_catch(context->GetIsolate());
|
|
CompileRun(
|
|
"var message = 'm';"
|
|
"while (message.length < 1000) message += message;"
|
|
"throw message;");
|
|
CHECK(try_catch.HasCaught());
|
|
|
|
CHECK_LT(1000, try_catch.Message()->Get()->Length());
|
|
}
|
|
|
|
|
|
static void YGetter(Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
info.GetReturnValue().Set(v8_num(10));
|
|
}
|
|
|
|
|
|
static void YSetter(Local<String> name,
|
|
Local<Value> value,
|
|
const v8::PropertyCallbackInfo<void>& info) {
|
|
Local<Object> this_obj = info.This().As<Object>();
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
if (this_obj->Has(context, name).FromJust())
|
|
this_obj->Delete(context, name).FromJust();
|
|
CHECK(this_obj->Set(context, name, value).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(DeleteAccessor) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
|
|
obj->SetAccessor(v8_str("y"), YGetter, YSetter);
|
|
LocalContext context;
|
|
v8::Local<v8::Object> holder =
|
|
obj->NewInstance(context.local()).ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("holder"), holder)
|
|
.FromJust());
|
|
v8::Local<Value> result =
|
|
CompileRun("holder.y = 11; holder.y = 12; holder.y");
|
|
CHECK_EQ(12u, result->Uint32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
static int trouble_nesting = 0;
|
|
static void TroubleCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
trouble_nesting++;
|
|
|
|
// Call a JS function that throws an uncaught exception.
|
|
Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
|
|
Local<v8::Object> arg_this = context->Global();
|
|
Local<Value> trouble_callee =
|
|
(trouble_nesting == 3)
|
|
? arg_this->Get(context, v8_str("trouble_callee")).ToLocalChecked()
|
|
: arg_this->Get(context, v8_str("trouble_caller")).ToLocalChecked();
|
|
CHECK(trouble_callee->IsFunction());
|
|
args.GetReturnValue().Set(Function::Cast(*trouble_callee)
|
|
->Call(context, arg_this, 0, nullptr)
|
|
.FromMaybe(v8::Local<v8::Value>()));
|
|
}
|
|
|
|
|
|
static int report_count = 0;
|
|
static void ApiUncaughtExceptionTestListener(v8::Local<v8::Message>,
|
|
v8::Local<Value>) {
|
|
report_count++;
|
|
}
|
|
|
|
|
|
// Counts uncaught exceptions, but other tests running in parallel
|
|
// also have uncaught exceptions.
|
|
TEST(ApiUncaughtException) {
|
|
report_count = 0;
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
isolate->AddMessageListener(ApiUncaughtExceptionTestListener);
|
|
|
|
Local<v8::FunctionTemplate> fun =
|
|
v8::FunctionTemplate::New(isolate, TroubleCallback);
|
|
v8::Local<v8::Object> global = env->Global();
|
|
CHECK(global->Set(env.local(), v8_str("trouble"),
|
|
fun->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
CompileRun(
|
|
"function trouble_callee() {"
|
|
" var x = null;"
|
|
" return x.foo;"
|
|
"};"
|
|
"function trouble_caller() {"
|
|
" trouble();"
|
|
"};");
|
|
Local<Value> trouble =
|
|
global->Get(env.local(), v8_str("trouble")).ToLocalChecked();
|
|
CHECK(trouble->IsFunction());
|
|
Local<Value> trouble_callee =
|
|
global->Get(env.local(), v8_str("trouble_callee")).ToLocalChecked();
|
|
CHECK(trouble_callee->IsFunction());
|
|
Local<Value> trouble_caller =
|
|
global->Get(env.local(), v8_str("trouble_caller")).ToLocalChecked();
|
|
CHECK(trouble_caller->IsFunction());
|
|
Function::Cast(*trouble_caller)
|
|
->Call(env.local(), global, 0, nullptr)
|
|
.FromMaybe(v8::Local<v8::Value>());
|
|
CHECK_EQ(1, report_count);
|
|
isolate->RemoveMessageListeners(ApiUncaughtExceptionTestListener);
|
|
}
|
|
|
|
|
|
static const char* script_resource_name = "ExceptionInNativeScript.js";
|
|
static void ExceptionInNativeScriptTestListener(v8::Local<v8::Message> message,
|
|
v8::Local<Value>) {
|
|
v8::Isolate* isolate = message->GetIsolate();
|
|
v8::Local<v8::Value> name_val = message->GetScriptOrigin().ResourceName();
|
|
CHECK(!name_val.IsEmpty() && name_val->IsString());
|
|
v8::String::Utf8Value name(isolate,
|
|
message->GetScriptOrigin().ResourceName());
|
|
CHECK_EQ(0, strcmp(script_resource_name, *name));
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
CHECK_EQ(3, message->GetLineNumber(context).FromJust());
|
|
v8::String::Utf8Value source_line(
|
|
isolate, message->GetSourceLine(context).ToLocalChecked());
|
|
CHECK_EQ(0, strcmp(" new o.foo();", *source_line));
|
|
}
|
|
|
|
|
|
TEST(ExceptionInNativeScript) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
isolate->AddMessageListener(ExceptionInNativeScriptTestListener);
|
|
|
|
Local<v8::FunctionTemplate> fun =
|
|
v8::FunctionTemplate::New(isolate, TroubleCallback);
|
|
v8::Local<v8::Object> global = env->Global();
|
|
CHECK(global->Set(env.local(), v8_str("trouble"),
|
|
fun->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
CompileRunWithOrigin(
|
|
"function trouble() {\n"
|
|
" var o = {};\n"
|
|
" new o.foo();\n"
|
|
"};",
|
|
script_resource_name);
|
|
Local<Value> trouble =
|
|
global->Get(env.local(), v8_str("trouble")).ToLocalChecked();
|
|
CHECK(trouble->IsFunction());
|
|
CHECK(Function::Cast(*trouble)
|
|
->Call(env.local(), global, 0, nullptr)
|
|
.IsEmpty());
|
|
isolate->RemoveMessageListeners(ExceptionInNativeScriptTestListener);
|
|
}
|
|
|
|
|
|
TEST(CompilationErrorUsingTryCatchHandler) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::TryCatch try_catch(env->GetIsolate());
|
|
CHECK(v8_try_compile("This doesn't &*&@#$&*^ compile.").IsEmpty());
|
|
CHECK(*try_catch.Exception());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
TEST(TryCatchFinallyUsingTryCatchHandler) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::TryCatch try_catch(env->GetIsolate());
|
|
CompileRun("try { throw ''; } catch (e) {}");
|
|
CHECK(!try_catch.HasCaught());
|
|
CompileRun("try { throw ''; } finally {}");
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
CompileRun(
|
|
"(function() {"
|
|
"try { throw ''; } finally { return; }"
|
|
"})()");
|
|
CHECK(!try_catch.HasCaught());
|
|
CompileRun(
|
|
"(function()"
|
|
" { try { throw ''; } finally { throw 0; }"
|
|
"})()");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
void CEvaluate(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::HandleScope scope(args.GetIsolate());
|
|
CompileRun(args[0]
|
|
->ToString(args.GetIsolate()->GetCurrentContext())
|
|
.ToLocalChecked());
|
|
}
|
|
|
|
|
|
TEST(TryCatchFinallyStoresMessageUsingTryCatchHandler) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "CEvaluate",
|
|
v8::FunctionTemplate::New(isolate, CEvaluate));
|
|
LocalContext context(nullptr, templ);
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun("try {"
|
|
" CEvaluate('throw 1;');"
|
|
"} finally {"
|
|
"}");
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(!try_catch.Message().IsEmpty());
|
|
String::Utf8Value exception_value(isolate, try_catch.Exception());
|
|
CHECK_EQ(0, strcmp(*exception_value, "1"));
|
|
try_catch.Reset();
|
|
CompileRun("try {"
|
|
" CEvaluate('throw 1;');"
|
|
"} finally {"
|
|
" throw 2;"
|
|
"}");
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(!try_catch.Message().IsEmpty());
|
|
String::Utf8Value finally_exception_value(isolate, try_catch.Exception());
|
|
CHECK_EQ(0, strcmp(*finally_exception_value, "2"));
|
|
}
|
|
|
|
|
|
// For use within the TestSecurityHandler() test.
|
|
static bool g_security_callback_result = false;
|
|
static bool SecurityTestCallback(Local<v8::Context> accessing_context,
|
|
Local<v8::Object> accessed_object,
|
|
Local<v8::Value> data) {
|
|
printf("a\n");
|
|
CHECK(!data.IsEmpty() && data->IsInt32());
|
|
CHECK_EQ(42, data->Int32Value(accessing_context).FromJust());
|
|
return g_security_callback_result;
|
|
}
|
|
|
|
|
|
// SecurityHandler can't be run twice
|
|
TEST(SecurityHandler) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope0(isolate);
|
|
v8::Local<v8::ObjectTemplate> global_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
global_template->SetAccessCheckCallback(SecurityTestCallback, v8_num(42));
|
|
// Create an environment
|
|
v8::Local<Context> context0 = Context::New(isolate, nullptr, global_template);
|
|
context0->Enter();
|
|
|
|
v8::Local<v8::Object> global0 = context0->Global();
|
|
v8::Local<Script> script0 = v8_compile("foo = 111");
|
|
script0->Run(context0).ToLocalChecked();
|
|
CHECK(global0->Set(context0, v8_str("0"), v8_num(999)).FromJust());
|
|
v8::Local<Value> foo0 =
|
|
global0->Get(context0, v8_str("foo")).ToLocalChecked();
|
|
CHECK_EQ(111, foo0->Int32Value(context0).FromJust());
|
|
v8::Local<Value> z0 = global0->Get(context0, v8_str("0")).ToLocalChecked();
|
|
CHECK_EQ(999, z0->Int32Value(context0).FromJust());
|
|
|
|
// Create another environment, should fail security checks.
|
|
v8::HandleScope scope1(isolate);
|
|
|
|
v8::Local<Context> context1 = Context::New(isolate, nullptr, global_template);
|
|
context1->Enter();
|
|
|
|
v8::Local<v8::Object> global1 = context1->Global();
|
|
global1->Set(context1, v8_str("othercontext"), global0).FromJust();
|
|
// This set will fail the security check.
|
|
v8::Local<Script> script1 =
|
|
v8_compile("othercontext.foo = 222; othercontext[0] = 888;");
|
|
CHECK(script1->Run(context1).IsEmpty());
|
|
g_security_callback_result = true;
|
|
// This read will pass the security check.
|
|
v8::Local<Value> foo1 =
|
|
global0->Get(context1, v8_str("foo")).ToLocalChecked();
|
|
CHECK_EQ(111, foo1->Int32Value(context0).FromJust());
|
|
// This read will pass the security check.
|
|
v8::Local<Value> z1 = global0->Get(context1, v8_str("0")).ToLocalChecked();
|
|
CHECK_EQ(999, z1->Int32Value(context1).FromJust());
|
|
|
|
// Create another environment, should pass security checks.
|
|
{
|
|
v8::HandleScope scope2(isolate);
|
|
LocalContext context2;
|
|
v8::Local<v8::Object> global2 = context2->Global();
|
|
CHECK(global2->Set(context2.local(), v8_str("othercontext"), global0)
|
|
.FromJust());
|
|
v8::Local<Script> script2 =
|
|
v8_compile("othercontext.foo = 333; othercontext[0] = 888;");
|
|
script2->Run(context2.local()).ToLocalChecked();
|
|
v8::Local<Value> foo2 =
|
|
global0->Get(context2.local(), v8_str("foo")).ToLocalChecked();
|
|
CHECK_EQ(333, foo2->Int32Value(context2.local()).FromJust());
|
|
v8::Local<Value> z2 =
|
|
global0->Get(context2.local(), v8_str("0")).ToLocalChecked();
|
|
CHECK_EQ(888, z2->Int32Value(context2.local()).FromJust());
|
|
}
|
|
|
|
context1->Exit();
|
|
context0->Exit();
|
|
}
|
|
|
|
|
|
THREADED_TEST(SecurityChecks) {
|
|
LocalContext env1;
|
|
v8::HandleScope handle_scope(env1->GetIsolate());
|
|
v8::Local<Context> env2 = Context::New(env1->GetIsolate());
|
|
|
|
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.
|
|
CompileRun("spy=function(){return spy;}");
|
|
Local<Value> spy =
|
|
env1->Global()->Get(env1.local(), v8_str("spy")).ToLocalChecked();
|
|
CHECK(spy->IsFunction());
|
|
|
|
// Create another function accessing global objects.
|
|
CompileRun("spy2=function(){return new this.Array();}");
|
|
Local<Value> spy2 =
|
|
env1->Global()->Get(env1.local(), v8_str("spy2")).ToLocalChecked();
|
|
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, env2->Global(), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(result->IsFunction());
|
|
}
|
|
|
|
{
|
|
env2->SetSecurityToken(bar);
|
|
Context::Scope scope_env2(env2);
|
|
|
|
// Call cross_domain_call, it should throw an exception
|
|
v8::TryCatch try_catch(env1->GetIsolate());
|
|
CHECK(Function::Cast(*spy2)
|
|
->Call(env2, env2->Global(), 0, nullptr)
|
|
.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
}
|
|
|
|
|
|
// Regression test case for issue 1183439.
|
|
THREADED_TEST(SecurityChecksForPrototypeChain) {
|
|
LocalContext current;
|
|
v8::HandleScope scope(current->GetIsolate());
|
|
v8::Local<Context> other = Context::New(current->GetIsolate());
|
|
|
|
// 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 context_scope(other);
|
|
other_object =
|
|
other->Global()->Get(other, v8_str("Object")).ToLocalChecked();
|
|
CHECK(other->Global()->Set(other, v8_num(42), v8_num(87)).FromJust());
|
|
}
|
|
|
|
CHECK(current->Global()
|
|
->Set(current.local(), v8_str("other"), other->Global())
|
|
.FromJust());
|
|
CHECK(v8_compile("other")
|
|
->Run(current.local())
|
|
.ToLocalChecked()
|
|
->Equals(current.local(), other->Global())
|
|
.FromJust());
|
|
|
|
// 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(current.local()).IsEmpty());
|
|
CHECK(access_other1->Run(current.local()).IsEmpty());
|
|
}
|
|
|
|
// 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(current.local())
|
|
.ToLocalChecked();
|
|
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(current.local()).IsEmpty());
|
|
CHECK(access_f1->Run(current.local()).IsEmpty());
|
|
}
|
|
|
|
// 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 context_scope(other);
|
|
CHECK(other->Global()
|
|
->Set(other, v8_str("__proto__"), current->Global())
|
|
.FromJust());
|
|
}
|
|
// 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.
|
|
CHECK(current->Global()
|
|
->Set(current.local(), v8_str("foo"), v8_num(100))
|
|
.FromJust());
|
|
CHECK(current->Global()
|
|
->Set(current.local(), v8_num(99), v8_num(101))
|
|
.FromJust());
|
|
// 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(current.local()).IsEmpty());
|
|
CHECK(access_f3->Run(current.local()).IsEmpty());
|
|
}
|
|
}
|
|
|
|
|
|
static bool security_check_with_gc_called;
|
|
|
|
static bool SecurityTestCallbackWithGC(Local<v8::Context> accessing_context,
|
|
Local<v8::Object> accessed_object,
|
|
Local<v8::Value> data) {
|
|
CcTest::CollectAllGarbage();
|
|
security_check_with_gc_called = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
TEST(SecurityTestGCAllowed) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> object_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
object_template->SetAccessCheckCallback(SecurityTestCallbackWithGC);
|
|
|
|
v8::Local<Context> context = Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
CHECK(context->Global()
|
|
->Set(context, v8_str("obj"),
|
|
object_template->NewInstance(context).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
security_check_with_gc_called = false;
|
|
CompileRun("obj[0] = new String(1002);");
|
|
CHECK(security_check_with_gc_called);
|
|
|
|
security_check_with_gc_called = false;
|
|
CHECK(CompileRun("obj[0]")
|
|
->ToString(context)
|
|
.ToLocalChecked()
|
|
->Equals(context, v8_str("1002"))
|
|
.FromJust());
|
|
CHECK(security_check_with_gc_called);
|
|
}
|
|
|
|
|
|
THREADED_TEST(CrossDomainDelete) {
|
|
LocalContext env1;
|
|
v8::HandleScope handle_scope(env1->GetIsolate());
|
|
v8::Local<Context> env2 = Context::New(env1->GetIsolate());
|
|
|
|
Local<Value> foo = v8_str("foo");
|
|
Local<Value> bar = v8_str("bar");
|
|
|
|
// Set to the same domain.
|
|
env1->SetSecurityToken(foo);
|
|
env2->SetSecurityToken(foo);
|
|
|
|
CHECK(
|
|
env1->Global()->Set(env1.local(), v8_str("prop"), v8_num(3)).FromJust());
|
|
CHECK(env2->Global()->Set(env2, v8_str("env1"), env1->Global()).FromJust());
|
|
|
|
// Change env2 to a different domain and delete env1.prop.
|
|
env2->SetSecurityToken(bar);
|
|
{
|
|
Context::Scope scope_env2(env2);
|
|
Local<Value> result =
|
|
CompileRun("delete env1.prop");
|
|
CHECK(result.IsEmpty());
|
|
}
|
|
|
|
// Check that env1.prop still exists.
|
|
Local<Value> v =
|
|
env1->Global()->Get(env1.local(), v8_str("prop")).ToLocalChecked();
|
|
CHECK(v->IsNumber());
|
|
CHECK_EQ(3, v->Int32Value(env1.local()).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(CrossDomainPropertyIsEnumerable) {
|
|
LocalContext env1;
|
|
v8::HandleScope handle_scope(env1->GetIsolate());
|
|
v8::Local<Context> env2 = Context::New(env1->GetIsolate());
|
|
|
|
Local<Value> foo = v8_str("foo");
|
|
Local<Value> bar = v8_str("bar");
|
|
|
|
// Set to the same domain.
|
|
env1->SetSecurityToken(foo);
|
|
env2->SetSecurityToken(foo);
|
|
|
|
CHECK(
|
|
env1->Global()->Set(env1.local(), v8_str("prop"), v8_num(3)).FromJust());
|
|
CHECK(env2->Global()->Set(env2, v8_str("env1"), env1->Global()).FromJust());
|
|
|
|
// env1.prop is enumerable in env2.
|
|
Local<String> test = v8_str("propertyIsEnumerable.call(env1, 'prop')");
|
|
{
|
|
Context::Scope scope_env2(env2);
|
|
Local<Value> result = CompileRun(test);
|
|
CHECK(result->IsTrue());
|
|
}
|
|
|
|
// Change env2 to a different domain and test again.
|
|
env2->SetSecurityToken(bar);
|
|
{
|
|
Context::Scope scope_env2(env2);
|
|
Local<Value> result = CompileRun(test);
|
|
CHECK(result.IsEmpty());
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(CrossDomainFor) {
|
|
LocalContext env1;
|
|
v8::HandleScope handle_scope(env1->GetIsolate());
|
|
v8::Local<Context> env2 = Context::New(env1->GetIsolate());
|
|
|
|
Local<Value> foo = v8_str("foo");
|
|
Local<Value> bar = v8_str("bar");
|
|
|
|
// Set to the same domain.
|
|
env1->SetSecurityToken(foo);
|
|
env2->SetSecurityToken(foo);
|
|
|
|
CHECK(
|
|
env1->Global()->Set(env1.local(), v8_str("prop"), v8_num(3)).FromJust());
|
|
CHECK(env2->Global()->Set(env2, v8_str("env1"), env1->Global()).FromJust());
|
|
|
|
// 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. It shouldn't throw either, just silently ignore them.
|
|
env2->SetSecurityToken(bar);
|
|
{
|
|
Context::Scope scope_env2(env2);
|
|
Local<Value> result = CompileRun(
|
|
"(function() {"
|
|
" try {"
|
|
" for (var p in env1) {"
|
|
" if (p == 'prop') return false;"
|
|
" }"
|
|
" return true;"
|
|
" } catch (e) {"
|
|
" return false;"
|
|
" }"
|
|
"})()");
|
|
CHECK(result->IsTrue());
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(CrossDomainForInOnPrototype) {
|
|
LocalContext env1;
|
|
v8::HandleScope handle_scope(env1->GetIsolate());
|
|
v8::Local<Context> env2 = Context::New(env1->GetIsolate());
|
|
|
|
Local<Value> foo = v8_str("foo");
|
|
Local<Value> bar = v8_str("bar");
|
|
|
|
// Set to the same domain.
|
|
env1->SetSecurityToken(foo);
|
|
env2->SetSecurityToken(foo);
|
|
|
|
CHECK(
|
|
env1->Global()->Set(env1.local(), v8_str("prop"), v8_num(3)).FromJust());
|
|
CHECK(env2->Global()->Set(env2, v8_str("env1"), env1->Global()).FromJust());
|
|
|
|
// 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 };"
|
|
" try {"
|
|
" for (var p in obj) {"
|
|
" if (p == 'prop') return false;"
|
|
" }"
|
|
" return true;"
|
|
" } catch (e) {"
|
|
" return false;"
|
|
" }"
|
|
"})()");
|
|
CHECK(result->IsTrue());
|
|
}
|
|
}
|
|
|
|
|
|
TEST(ContextDetachGlobal) {
|
|
LocalContext env1;
|
|
v8::HandleScope handle_scope(env1->GetIsolate());
|
|
v8::Local<Context> env2 = Context::New(env1->GetIsolate());
|
|
|
|
|
|
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.
|
|
Local<v8::Object> global2 = env2->Global();
|
|
CHECK(global2->Set(env2, v8_str("prop"),
|
|
v8::Integer::New(env2->GetIsolate(), 1))
|
|
.FromJust());
|
|
CompileRun("function getProp() {return prop;}");
|
|
|
|
CHECK(env1->Global()
|
|
->Set(env1.local(), v8_str("getProp"),
|
|
global2->Get(env2, v8_str("getProp")).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// Detach env2's global, and reuse the global object of env2
|
|
env2->Exit();
|
|
env2->DetachGlobal();
|
|
|
|
v8::Local<Context> env3 = Context::New(
|
|
env1->GetIsolate(), nullptr, v8::Local<v8::ObjectTemplate>(), global2);
|
|
env3->SetSecurityToken(v8_str("bar"));
|
|
|
|
env3->Enter();
|
|
Local<v8::Object> global3 = env3->Global();
|
|
CHECK(global2->Equals(env3, global3).FromJust());
|
|
CHECK(global3->Get(env3, v8_str("prop")).ToLocalChecked()->IsUndefined());
|
|
CHECK(global3->Get(env3, v8_str("getProp")).ToLocalChecked()->IsUndefined());
|
|
CHECK(global3->Set(env3, v8_str("prop"),
|
|
v8::Integer::New(env3->GetIsolate(), -1))
|
|
.FromJust());
|
|
CHECK(global3->Set(env3, v8_str("prop2"),
|
|
v8::Integer::New(env3->GetIsolate(), 2))
|
|
.FromJust());
|
|
env3->Exit();
|
|
|
|
// Call getProp in env1, and it should return the value 1
|
|
{
|
|
Local<v8::Object> global1 = env1->Global();
|
|
Local<Value> get_prop =
|
|
global1->Get(env1.local(), v8_str("getProp")).ToLocalChecked();
|
|
CHECK(get_prop->IsFunction());
|
|
v8::TryCatch try_catch(env1->GetIsolate());
|
|
Local<Value> r = Function::Cast(*get_prop)
|
|
->Call(env1.local(), global1, 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(1, r->Int32Value(env1.local()).FromJust());
|
|
}
|
|
|
|
// Check that env3 is not accessible from env1
|
|
{
|
|
v8::MaybeLocal<Value> r = global3->Get(env1.local(), v8_str("prop2"));
|
|
CHECK(r.IsEmpty());
|
|
}
|
|
}
|
|
|
|
|
|
TEST(DetachGlobal) {
|
|
LocalContext env1;
|
|
v8::HandleScope scope(env1->GetIsolate());
|
|
|
|
// Create second environment.
|
|
v8::Local<Context> env2 = Context::New(env1->GetIsolate());
|
|
|
|
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 context_scope(env2);
|
|
CHECK(env2->Global()
|
|
->Set(env2, v8_str("p"), v8::Integer::New(env2->GetIsolate(), 42))
|
|
.FromJust());
|
|
}
|
|
|
|
// Create a reference to env2 global from env1 global.
|
|
CHECK(env1->Global()
|
|
->Set(env1.local(), v8_str("other"), env2->Global())
|
|
.FromJust());
|
|
|
|
// 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(env1.local()).FromJust());
|
|
|
|
// 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.IsEmpty());
|
|
|
|
// Reuse global2 for env3.
|
|
v8::Local<Context> env3 = Context::New(
|
|
env1->GetIsolate(), nullptr, v8::Local<v8::ObjectTemplate>(), global2);
|
|
CHECK(global2->Equals(env1.local(), env3->Global()).FromJust());
|
|
|
|
// 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 context_scope(env3);
|
|
CHECK(env3->Global()
|
|
->Set(env3, v8_str("p"), v8::Integer::New(env3->GetIsolate(), 24))
|
|
.FromJust());
|
|
}
|
|
|
|
// 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(env3).FromJust());
|
|
}
|
|
|
|
|
|
void GetThisX(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
info.GetReturnValue().Set(
|
|
context->Global()->Get(context, v8_str("x")).ToLocalChecked());
|
|
}
|
|
|
|
|
|
TEST(DetachedAccesses) {
|
|
LocalContext env1;
|
|
v8::HandleScope scope(env1->GetIsolate());
|
|
|
|
// Create second environment.
|
|
Local<ObjectTemplate> inner_global_template =
|
|
FunctionTemplate::New(env1->GetIsolate())->InstanceTemplate();
|
|
inner_global_template ->SetAccessorProperty(
|
|
v8_str("this_x"), FunctionTemplate::New(env1->GetIsolate(), GetThisX));
|
|
v8::Local<Context> env2 =
|
|
Context::New(env1->GetIsolate(), nullptr, inner_global_template);
|
|
|
|
Local<Value> foo = v8_str("foo");
|
|
|
|
// Set same security token for env1 and env2.
|
|
env1->SetSecurityToken(foo);
|
|
env2->SetSecurityToken(foo);
|
|
|
|
CHECK(env1->Global()
|
|
->Set(env1.local(), v8_str("x"), v8_str("env1_x"))
|
|
.FromJust());
|
|
|
|
{
|
|
v8::Context::Scope context_scope(env2);
|
|
CHECK(env2->Global()->Set(env2, v8_str("x"), v8_str("env2_x")).FromJust());
|
|
CompileRun(
|
|
"function bound_x() { return x; }"
|
|
"function get_x() { return this.x; }"
|
|
"function get_x_w() { return (function() {return this.x;})(); }");
|
|
CHECK(env1->Global()
|
|
->Set(env1.local(), v8_str("bound_x"), CompileRun("bound_x"))
|
|
.FromJust());
|
|
CHECK(env1->Global()
|
|
->Set(env1.local(), v8_str("get_x"), CompileRun("get_x"))
|
|
.FromJust());
|
|
CHECK(env1->Global()
|
|
->Set(env1.local(), v8_str("get_x_w"), CompileRun("get_x_w"))
|
|
.FromJust());
|
|
env1->Global()
|
|
->Set(env1.local(), v8_str("this_x"),
|
|
CompileRun("Object.getOwnPropertyDescriptor(this, 'this_x').get"))
|
|
.FromJust();
|
|
}
|
|
|
|
Local<Object> env2_global = env2->Global();
|
|
env2->DetachGlobal();
|
|
|
|
Local<Value> result;
|
|
result = CompileRun("bound_x()");
|
|
CHECK(v8_str("env2_x")->Equals(env1.local(), result).FromJust());
|
|
result = CompileRun("get_x()");
|
|
CHECK(result.IsEmpty());
|
|
result = CompileRun("get_x_w()");
|
|
CHECK(result.IsEmpty());
|
|
result = CompileRun("this_x()");
|
|
CHECK(v8_str("env2_x")->Equals(env1.local(), result).FromJust());
|
|
|
|
// Reattach env2's proxy
|
|
env2 = Context::New(env1->GetIsolate(), nullptr,
|
|
v8::Local<v8::ObjectTemplate>(), env2_global);
|
|
env2->SetSecurityToken(foo);
|
|
{
|
|
v8::Context::Scope context_scope(env2);
|
|
CHECK(env2->Global()->Set(env2, v8_str("x"), v8_str("env3_x")).FromJust());
|
|
CHECK(env2->Global()->Set(env2, v8_str("env1"), env1->Global()).FromJust());
|
|
result = CompileRun(
|
|
"results = [];"
|
|
"for (var i = 0; i < 4; i++ ) {"
|
|
" results.push(env1.bound_x());"
|
|
" results.push(env1.get_x());"
|
|
" results.push(env1.get_x_w());"
|
|
" results.push(env1.this_x());"
|
|
"}"
|
|
"results");
|
|
Local<v8::Array> results = Local<v8::Array>::Cast(result);
|
|
CHECK_EQ(16u, results->Length());
|
|
for (int i = 0; i < 16; i += 4) {
|
|
CHECK(v8_str("env2_x")
|
|
->Equals(env2, results->Get(env2, i + 0).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("env1_x")
|
|
->Equals(env2, results->Get(env2, i + 1).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("env3_x")
|
|
->Equals(env2, results->Get(env2, i + 2).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("env2_x")
|
|
->Equals(env2, results->Get(env2, i + 3).ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
result = CompileRun(
|
|
"results = [];"
|
|
"for (var i = 0; i < 4; i++ ) {"
|
|
" results.push(bound_x());"
|
|
" results.push(get_x());"
|
|
" results.push(get_x_w());"
|
|
" results.push(this_x());"
|
|
"}"
|
|
"results");
|
|
Local<v8::Array> results = Local<v8::Array>::Cast(result);
|
|
CHECK_EQ(16u, results->Length());
|
|
for (int i = 0; i < 16; i += 4) {
|
|
CHECK(v8_str("env2_x")
|
|
->Equals(env1.local(),
|
|
results->Get(env1.local(), i + 0).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("env3_x")
|
|
->Equals(env1.local(),
|
|
results->Get(env1.local(), i + 1).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("env3_x")
|
|
->Equals(env1.local(),
|
|
results->Get(env1.local(), i + 2).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("env2_x")
|
|
->Equals(env1.local(),
|
|
results->Get(env1.local(), i + 3).ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
|
|
result = CompileRun(
|
|
"results = [];"
|
|
"for (var i = 0; i < 4; i++ ) {"
|
|
" results.push(this.bound_x());"
|
|
" results.push(this.get_x());"
|
|
" results.push(this.get_x_w());"
|
|
" results.push(this.this_x());"
|
|
"}"
|
|
"results");
|
|
results = Local<v8::Array>::Cast(result);
|
|
CHECK_EQ(16u, results->Length());
|
|
for (int i = 0; i < 16; i += 4) {
|
|
CHECK(v8_str("env2_x")
|
|
->Equals(env1.local(),
|
|
results->Get(env1.local(), i + 0).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("env1_x")
|
|
->Equals(env1.local(),
|
|
results->Get(env1.local(), i + 1).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("env3_x")
|
|
->Equals(env1.local(),
|
|
results->Get(env1.local(), i + 2).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("env2_x")
|
|
->Equals(env1.local(),
|
|
results->Get(env1.local(), i + 3).ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
static bool allowed_access = false;
|
|
static bool AccessBlocker(Local<v8::Context> accessing_context,
|
|
Local<v8::Object> accessed_object,
|
|
Local<v8::Value> data) {
|
|
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
|
|
return context->Global()->Equals(context, accessed_object).FromJust() ||
|
|
allowed_access;
|
|
}
|
|
|
|
|
|
static int g_echo_value = -1;
|
|
|
|
|
|
static void EchoGetter(
|
|
Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(v8_num(g_echo_value));
|
|
}
|
|
|
|
|
|
static void EchoSetter(Local<String> name, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<void>& args) {
|
|
if (value->IsNumber())
|
|
g_echo_value =
|
|
value->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust();
|
|
}
|
|
|
|
|
|
static void UnreachableGetter(
|
|
Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
UNREACHABLE(); // This function should not be called..
|
|
}
|
|
|
|
|
|
static void UnreachableSetter(Local<String>,
|
|
Local<Value>,
|
|
const v8::PropertyCallbackInfo<void>&) {
|
|
UNREACHABLE(); // This function should not be called.
|
|
}
|
|
|
|
|
|
static void UnreachableFunction(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
UNREACHABLE(); // This function should not be called..
|
|
}
|
|
|
|
|
|
TEST(AccessControl) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> global_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
|
|
global_template->SetAccessCheckCallback(AccessBlocker);
|
|
|
|
// Add an accessor accessible by cross-domain JS code.
|
|
global_template->SetAccessor(
|
|
v8_str("accessible_prop"), EchoGetter, EchoSetter, v8::Local<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::Local<Value>(),
|
|
v8::DEFAULT);
|
|
|
|
global_template->SetAccessorProperty(
|
|
v8_str("blocked_js_prop"),
|
|
v8::FunctionTemplate::New(isolate, UnreachableFunction),
|
|
v8::FunctionTemplate::New(isolate, UnreachableFunction),
|
|
v8::None,
|
|
v8::DEFAULT);
|
|
|
|
// Create an environment
|
|
v8::Local<Context> context0 = Context::New(isolate, nullptr, global_template);
|
|
context0->Enter();
|
|
|
|
v8::Local<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(context0, v8_str("getter")).ToLocalChecked();
|
|
Local<Value> setter =
|
|
global0->Get(context0, v8_str("setter")).ToLocalChecked();
|
|
|
|
// And define normal element.
|
|
CHECK(global0->Set(context0, 239, v8_str("239")).FromJust());
|
|
|
|
// 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(context0, v8_str("el_getter")).ToLocalChecked();
|
|
Local<Value> el_setter =
|
|
global0->Get(context0, v8_str("el_setter")).ToLocalChecked();
|
|
|
|
v8::HandleScope scope1(isolate);
|
|
|
|
v8::Local<Context> context1 = Context::New(isolate);
|
|
context1->Enter();
|
|
|
|
v8::Local<v8::Object> global1 = context1->Global();
|
|
CHECK(global1->Set(context1, v8_str("other"), global0).FromJust());
|
|
|
|
// Access blocked property.
|
|
CompileRun("other.blocked_prop = 1");
|
|
|
|
CHECK(CompileRun("other.blocked_prop").IsEmpty());
|
|
CHECK(CompileRun("Object.getOwnPropertyDescriptor(other, 'blocked_prop')")
|
|
.IsEmpty());
|
|
CHECK(
|
|
CompileRun("propertyIsEnumerable.call(other, 'blocked_prop')").IsEmpty());
|
|
|
|
// Access blocked element.
|
|
CHECK(CompileRun("other[239] = 1").IsEmpty());
|
|
|
|
CHECK(CompileRun("other[239]").IsEmpty());
|
|
CHECK(CompileRun("Object.getOwnPropertyDescriptor(other, '239')").IsEmpty());
|
|
CHECK(CompileRun("propertyIsEnumerable.call(other, '239')").IsEmpty());
|
|
|
|
allowed_access = true;
|
|
// Now we can enumerate the property.
|
|
ExpectTrue("propertyIsEnumerable.call(other, '239')");
|
|
allowed_access = false;
|
|
|
|
// Access a property with JS accessor.
|
|
CHECK(CompileRun("other.js_accessor_p = 2").IsEmpty());
|
|
|
|
CHECK(CompileRun("other.js_accessor_p").IsEmpty());
|
|
CHECK(CompileRun("Object.getOwnPropertyDescriptor(other, 'js_accessor_p')")
|
|
.IsEmpty());
|
|
|
|
allowed_access = 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 = false;
|
|
|
|
// Access an element with JS accessor.
|
|
CHECK(CompileRun("other[42] = 2").IsEmpty());
|
|
|
|
CHECK(CompileRun("other[42]").IsEmpty());
|
|
CHECK(CompileRun("Object.getOwnPropertyDescriptor(other, '42')").IsEmpty());
|
|
|
|
allowed_access = 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 = false;
|
|
|
|
v8::Local<Value> value;
|
|
|
|
// Access accessible property
|
|
value = CompileRun("other.accessible_prop = 3");
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(3, value->Int32Value(context1).FromJust());
|
|
CHECK_EQ(3, g_echo_value);
|
|
|
|
value = CompileRun("other.accessible_prop");
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(3, value->Int32Value(context1).FromJust());
|
|
|
|
value = CompileRun(
|
|
"Object.getOwnPropertyDescriptor(other, 'accessible_prop').value");
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(3, value->Int32Value(context1).FromJust());
|
|
|
|
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.
|
|
// Enumeration doesn't throw, it silently ignores what it can't access.
|
|
value = CompileRun(
|
|
"(function() {"
|
|
" var obj = { '__proto__': other };"
|
|
" try {"
|
|
" for (var p in obj) {"
|
|
" if (p == 'accessible_prop' ||"
|
|
" p == 'blocked_js_prop' ||"
|
|
" p == 'blocked_js_prop') {"
|
|
" return false;"
|
|
" }"
|
|
" }"
|
|
" return true;"
|
|
" } catch (e) {"
|
|
" return false;"
|
|
" }"
|
|
"})()");
|
|
CHECK(value->IsTrue());
|
|
|
|
// Test that preventExtensions fails on a non-accessible object even if that
|
|
// object is already non-extensible.
|
|
CHECK(global1->Set(context1, v8_str("checked_object"),
|
|
global_template->NewInstance(context1).ToLocalChecked())
|
|
.FromJust());
|
|
allowed_access = true;
|
|
CompileRun("Object.preventExtensions(checked_object)");
|
|
ExpectFalse("Object.isExtensible(checked_object)");
|
|
allowed_access = false;
|
|
CHECK(CompileRun("Object.preventExtensions(checked_object)").IsEmpty());
|
|
|
|
context1->Exit();
|
|
context0->Exit();
|
|
}
|
|
|
|
|
|
TEST(AccessControlES5) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> global_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
|
|
global_template->SetAccessCheckCallback(AccessBlocker);
|
|
|
|
// Add accessible accessor.
|
|
global_template->SetAccessor(
|
|
v8_str("accessible_prop"), EchoGetter, EchoSetter, v8::Local<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::Local<Value>(),
|
|
v8::DEFAULT);
|
|
|
|
// Create an environment
|
|
v8::Local<Context> context0 = Context::New(isolate, nullptr, global_template);
|
|
context0->Enter();
|
|
|
|
v8::Local<v8::Object> global0 = context0->Global();
|
|
|
|
v8::Local<Context> context1 = Context::New(isolate);
|
|
context1->Enter();
|
|
v8::Local<v8::Object> global1 = context1->Global();
|
|
CHECK(global1->Set(context1, v8_str("other"), global0).FromJust());
|
|
|
|
// Regression test for issue 1154.
|
|
CHECK(CompileRun("Object.keys(other).length == 1")->BooleanValue(isolate));
|
|
CHECK(CompileRun("Object.keys(other)[0] == 'accessible_prop'")
|
|
->BooleanValue(isolate));
|
|
CHECK(CompileRun("other.blocked_prop").IsEmpty());
|
|
|
|
// Regression test for issue 1027.
|
|
CompileRun("Object.defineProperty(\n"
|
|
" other, 'blocked_prop', {configurable: false})");
|
|
CHECK(CompileRun("other.blocked_prop").IsEmpty());
|
|
CHECK(CompileRun("Object.getOwnPropertyDescriptor(other, 'blocked_prop')")
|
|
.IsEmpty());
|
|
|
|
// 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);
|
|
|
|
// [[DefineOwnProperty]] always throws for access-checked objects.
|
|
CHECK(
|
|
CompileRun("Object.defineProperty(other, 'accessible_prop', {value: 43})")
|
|
.IsEmpty());
|
|
CHECK(CompileRun("other.accessible_prop == 42")->IsTrue());
|
|
CHECK_EQ(42, g_echo_value); // Make sure we didn't call the setter.
|
|
}
|
|
|
|
static bool AccessAlwaysBlocked(Local<v8::Context> accessing_context,
|
|
Local<v8::Object> global,
|
|
Local<v8::Value> data) {
|
|
i::PrintF("Access blocked.\n");
|
|
return false;
|
|
}
|
|
|
|
static bool AccessAlwaysAllowed(Local<v8::Context> accessing_context,
|
|
Local<v8::Object> global,
|
|
Local<v8::Value> data) {
|
|
i::PrintF("Access allowed.\n");
|
|
return true;
|
|
}
|
|
|
|
THREADED_TEST(AccessControlGetOwnPropertyNames) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New(isolate);
|
|
|
|
obj_template->Set(isolate, "x", v8::Integer::New(isolate, 42));
|
|
obj_template->SetAccessCheckCallback(AccessAlwaysBlocked);
|
|
|
|
// Add an accessor accessible by cross-domain JS code.
|
|
obj_template->SetAccessor(
|
|
v8_str("accessible_prop"), EchoGetter, EchoSetter, v8::Local<Value>(),
|
|
v8::AccessControl(v8::ALL_CAN_READ | v8::ALL_CAN_WRITE));
|
|
|
|
// Create an environment
|
|
v8::Local<Context> context0 = Context::New(isolate, nullptr, obj_template);
|
|
context0->Enter();
|
|
|
|
v8::Local<v8::Object> global0 = context0->Global();
|
|
|
|
v8::HandleScope scope1(CcTest::isolate());
|
|
|
|
v8::Local<Context> context1 = Context::New(isolate);
|
|
context1->Enter();
|
|
|
|
v8::Local<v8::Object> global1 = context1->Global();
|
|
CHECK(global1->Set(context1, v8_str("other"), global0).FromJust());
|
|
CHECK(global1->Set(context1, v8_str("object"),
|
|
obj_template->NewInstance(context1).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
v8::Local<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(
|
|
"var names = Object.getOwnPropertyNames(other);"
|
|
"names.length == 1 && names[0] == 'accessible_prop';");
|
|
CHECK(value->BooleanValue(isolate));
|
|
|
|
value = CompileRun(
|
|
"var names = Object.getOwnPropertyNames(object);"
|
|
"names.length == 1 && names[0] == 'accessible_prop';");
|
|
CHECK(value->BooleanValue(isolate));
|
|
|
|
context1->Exit();
|
|
context0->Exit();
|
|
}
|
|
|
|
|
|
TEST(Regress470113) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New(isolate);
|
|
obj_template->SetAccessCheckCallback(AccessAlwaysBlocked);
|
|
LocalContext env;
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("prohibited"),
|
|
obj_template->NewInstance(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun(
|
|
"'use strict';\n"
|
|
"class C extends Object {\n"
|
|
" m() { super.powned = 'Powned!'; }\n"
|
|
"}\n"
|
|
"let c = new C();\n"
|
|
"c.m.call(prohibited)");
|
|
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
}
|
|
|
|
|
|
static void ConstTenGetter(Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(v8_num(10));
|
|
}
|
|
|
|
|
|
THREADED_TEST(CrossDomainAccessors) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
v8::Local<v8::FunctionTemplate> func_template =
|
|
v8::FunctionTemplate::New(isolate);
|
|
|
|
v8::Local<v8::ObjectTemplate> global_template =
|
|
func_template->InstanceTemplate();
|
|
|
|
v8::Local<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, nullptr,
|
|
v8::Local<Value>(), v8::ALL_CAN_READ);
|
|
|
|
// Add an accessor that is not accessible by cross-domain JS code.
|
|
global_template->SetAccessor(v8_str("unreachable"), UnreachableGetter,
|
|
nullptr, v8::Local<Value>(), v8::DEFAULT);
|
|
|
|
v8::Local<Context> context0 = Context::New(isolate, nullptr, global_template);
|
|
context0->Enter();
|
|
|
|
Local<v8::Object> global = context0->Global();
|
|
// Add a normal property that shadows 'accessible'
|
|
CHECK(global->Set(context0, v8_str("accessible"), v8_num(11)).FromJust());
|
|
|
|
// Enter a new context.
|
|
v8::HandleScope scope1(CcTest::isolate());
|
|
v8::Local<Context> context1 = Context::New(isolate);
|
|
context1->Enter();
|
|
|
|
v8::Local<v8::Object> global1 = context1->Global();
|
|
CHECK(global1->Set(context1, v8_str("other"), global).FromJust());
|
|
|
|
// Should return 10, instead of 11
|
|
v8::Local<Value> value =
|
|
v8_compile("other.accessible")->Run(context1).ToLocalChecked();
|
|
CHECK(value->IsNumber());
|
|
CHECK_EQ(10, value->Int32Value(context1).FromJust());
|
|
|
|
v8::MaybeLocal<v8::Value> maybe_value =
|
|
v8_compile("other.unreachable")->Run(context1);
|
|
CHECK(maybe_value.IsEmpty());
|
|
|
|
context1->Exit();
|
|
context0->Exit();
|
|
}
|
|
|
|
|
|
static int access_count = 0;
|
|
|
|
static bool AccessCounter(Local<v8::Context> accessing_context,
|
|
Local<v8::Object> accessed_object,
|
|
Local<v8::Value> data) {
|
|
access_count++;
|
|
return true;
|
|
}
|
|
|
|
|
|
// This one is too easily disturbed by other tests.
|
|
TEST(AccessControlIC) {
|
|
access_count = 0;
|
|
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
// Create an environment.
|
|
v8::Local<Context> context0 = Context::New(isolate);
|
|
context0->Enter();
|
|
|
|
// Create an object that requires access-check functions to be
|
|
// called for cross-domain access.
|
|
v8::Local<v8::ObjectTemplate> object_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
object_template->SetAccessCheckCallback(AccessCounter);
|
|
Local<v8::Object> object =
|
|
object_template->NewInstance(context0).ToLocalChecked();
|
|
|
|
v8::HandleScope scope1(isolate);
|
|
|
|
// Create another environment.
|
|
v8::Local<Context> context1 = Context::New(isolate);
|
|
context1->Enter();
|
|
|
|
// Make easy access to the object from the other environment.
|
|
v8::Local<v8::Object> global1 = context1->Global();
|
|
CHECK(global1->Set(context1, v8_str("obj"), object).FromJust());
|
|
|
|
v8::Local<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(context1).FromJust());
|
|
CHECK_EQ(21, 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(context1).FromJust());
|
|
CHECK_EQ(42, 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(context1).FromJust());
|
|
CHECK_EQ(63, access_count);
|
|
|
|
// Check that the indexed access-control function is called every time.
|
|
access_count = 0;
|
|
|
|
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(context1).FromJust());
|
|
CHECK_EQ(21, 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(context1).FromJust());
|
|
CHECK_EQ(42, access_count);
|
|
|
|
access_count = 0;
|
|
// 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)");
|
|
printf("%i\n", access_count);
|
|
CHECK_EQ(11, access_count);
|
|
|
|
// Force obj into slow case.
|
|
value = CompileRun("delete obj.prop");
|
|
CHECK(value->BooleanValue(isolate));
|
|
// 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(context1).FromJust());
|
|
CHECK_EQ(33, 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(43, access_count);
|
|
|
|
context1->Exit();
|
|
context0->Exit();
|
|
}
|
|
|
|
|
|
THREADED_TEST(Version) { v8::V8::GetVersion(); }
|
|
|
|
|
|
static void InstanceFunctionCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
args.GetReturnValue().Set(v8_num(12));
|
|
}
|
|
|
|
|
|
THREADED_TEST(InstanceProperties) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
|
|
Local<ObjectTemplate> instance = t->InstanceTemplate();
|
|
|
|
instance->Set(isolate, "x", v8_num(42));
|
|
instance->Set(isolate, "f",
|
|
v8::FunctionTemplate::New(isolate, InstanceFunctionCallback));
|
|
|
|
Local<Value> o = t->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
|
|
CHECK(context->Global()->Set(context.local(), v8_str("i"), o).FromJust());
|
|
Local<Value> value = CompileRun("i.x");
|
|
CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
|
|
|
|
value = CompileRun("i.f()");
|
|
CHECK_EQ(12, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
static void GlobalObjectInstancePropertiesGet(
|
|
Local<Name> key, const v8::PropertyCallbackInfo<v8::Value>&) {
|
|
ApiTestFuzzer::Fuzz();
|
|
}
|
|
|
|
static int script_execution_count = 0;
|
|
static void ScriptExecutionCallback(v8::Isolate* isolate,
|
|
Local<Context> context) {
|
|
script_execution_count++;
|
|
}
|
|
|
|
THREADED_TEST(ContextScriptExecutionCallback) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext context;
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
script_execution_count = 0;
|
|
ExpectTrue("1 + 1 == 2");
|
|
CHECK_EQ(0, script_execution_count);
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
context->SetAbortScriptExecution(ScriptExecutionCallback);
|
|
|
|
{ // Function binding does not trigger callback.
|
|
v8::Local<v8::FunctionTemplate> function_template =
|
|
v8::FunctionTemplate::New(isolate, DummyCallHandler);
|
|
v8::Local<v8::Function> function =
|
|
function_template->GetFunction(context.local()).ToLocalChecked();
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
script_execution_count = 0;
|
|
|
|
CHECK_EQ(13.4,
|
|
function->Call(context.local(), v8::Undefined(isolate), 0, nullptr)
|
|
.ToLocalChecked()
|
|
->NumberValue(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(0, script_execution_count);
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{ // Script execution triggers callback.
|
|
v8::TryCatch try_catch(isolate);
|
|
script_execution_count = 0;
|
|
CHECK(CompileRun(context.local(), "2 + 2 == 4").IsEmpty());
|
|
CHECK_EQ(1, script_execution_count);
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
context->SetAbortScriptExecution(nullptr);
|
|
|
|
{ // Script execution no longer triggers callback.
|
|
v8::TryCatch try_catch(isolate);
|
|
script_execution_count = 0;
|
|
ExpectTrue("2 + 2 == 4");
|
|
CHECK_EQ(0, script_execution_count);
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
}
|
|
|
|
THREADED_TEST(GlobalObjectInstanceProperties) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<Value> global_object;
|
|
|
|
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
|
|
t->InstanceTemplate()->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(GlobalObjectInstancePropertiesGet));
|
|
Local<ObjectTemplate> instance_template = t->InstanceTemplate();
|
|
instance_template->Set(isolate, "x", v8_num(42));
|
|
instance_template->Set(
|
|
isolate, "f",
|
|
v8::FunctionTemplate::New(isolate, InstanceFunctionCallback));
|
|
|
|
// The script to check how TurboFan 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(nullptr, 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 = CompileRun("x");
|
|
CHECK_EQ(42, value->Int32Value(env.local()).FromJust());
|
|
value = CompileRun("f()");
|
|
CHECK_EQ(12, value->Int32Value(env.local()).FromJust());
|
|
value = CompileRun(script);
|
|
CHECK_EQ(1, value->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// Create new environment reusing the global object.
|
|
LocalContext env(nullptr, instance_template, global_object);
|
|
Local<Value> value = CompileRun("x");
|
|
CHECK_EQ(42, value->Int32Value(env.local()).FromJust());
|
|
value = CompileRun("f()");
|
|
CHECK_EQ(12, value->Int32Value(env.local()).FromJust());
|
|
value = CompileRun(script);
|
|
CHECK_EQ(1, value->Int32Value(env.local()).FromJust());
|
|
}
|
|
}
|
|
|
|
THREADED_TEST(ObjectGetOwnPropertyNames) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
v8::Local<v8::Object> value = v8::Local<v8::Object>::Cast(
|
|
v8::StringObject::New(CcTest::isolate(), v8_str("test")));
|
|
v8::Local<v8::Array> properties;
|
|
|
|
CHECK(value
|
|
->GetOwnPropertyNames(context.local(),
|
|
static_cast<v8::PropertyFilter>(
|
|
v8::PropertyFilter::ALL_PROPERTIES |
|
|
v8::PropertyFilter::SKIP_SYMBOLS),
|
|
v8::KeyConversionMode::kKeepNumbers)
|
|
.ToLocal(&properties));
|
|
CHECK_EQ(5u, properties->Length());
|
|
v8::Local<v8::Value> property;
|
|
CHECK(properties->Get(context.local(), 4).ToLocal(&property) &&
|
|
property->IsString());
|
|
CHECK(property.As<v8::String>()
|
|
->Equals(context.local(), v8_str("length"))
|
|
.FromMaybe(false));
|
|
for (int i = 0; i < 4; ++i) {
|
|
CHECK(properties->Get(context.local(), i).ToLocal(&property) &&
|
|
property->IsInt32());
|
|
CHECK_EQ(property.As<v8::Int32>()->Value(), i);
|
|
}
|
|
|
|
CHECK(value
|
|
->GetOwnPropertyNames(context.local(),
|
|
v8::PropertyFilter::ONLY_ENUMERABLE,
|
|
v8::KeyConversionMode::kKeepNumbers)
|
|
.ToLocal(&properties));
|
|
v8::Local<v8::Array> number_properties;
|
|
CHECK(value
|
|
->GetOwnPropertyNames(context.local(),
|
|
v8::PropertyFilter::ONLY_ENUMERABLE,
|
|
v8::KeyConversionMode::kConvertToString)
|
|
.ToLocal(&number_properties));
|
|
CHECK_EQ(4u, properties->Length());
|
|
for (int i = 0; i < 4; ++i) {
|
|
v8::Local<v8::Value> property_index;
|
|
v8::Local<v8::Value> property_name;
|
|
|
|
CHECK(number_properties->Get(context.local(), i).ToLocal(&property_name));
|
|
CHECK(property_name->IsString());
|
|
|
|
CHECK(properties->Get(context.local(), i).ToLocal(&property_index));
|
|
CHECK(property_index->IsInt32());
|
|
|
|
CHECK_EQ(property_index.As<v8::Int32>()->Value(), i);
|
|
CHECK_EQ(property_name->ToNumber(context.local())
|
|
.ToLocalChecked()
|
|
.As<v8::Int32>()
|
|
->Value(),
|
|
i);
|
|
}
|
|
|
|
value = value->GetPrototype().As<v8::Object>();
|
|
CHECK(value
|
|
->GetOwnPropertyNames(context.local(),
|
|
static_cast<v8::PropertyFilter>(
|
|
v8::PropertyFilter::ALL_PROPERTIES |
|
|
v8::PropertyFilter::SKIP_SYMBOLS))
|
|
.ToLocal(&properties));
|
|
bool concat_found = false;
|
|
bool starts_with_found = false;
|
|
for (uint32_t i = 0; i < properties->Length(); ++i) {
|
|
CHECK(properties->Get(context.local(), i).ToLocal(&property));
|
|
if (!property->IsString()) continue;
|
|
if (!concat_found)
|
|
concat_found = property.As<v8::String>()
|
|
->Equals(context.local(), v8_str("concat"))
|
|
.FromMaybe(false);
|
|
if (!starts_with_found)
|
|
starts_with_found = property.As<v8::String>()
|
|
->Equals(context.local(), v8_str("startsWith"))
|
|
.FromMaybe(false);
|
|
}
|
|
CHECK(concat_found && starts_with_found);
|
|
}
|
|
|
|
THREADED_TEST(CallKnownGlobalReceiver) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<Value> global_object;
|
|
|
|
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
|
|
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(nullptr, instance_template);
|
|
// Hold on to the global object so it can be used again in another
|
|
// environment initialization.
|
|
global_object = env->Global();
|
|
foo = CompileRun(script);
|
|
}
|
|
|
|
{
|
|
// Create new environment reusing the global object.
|
|
LocalContext env(nullptr, instance_template, global_object);
|
|
CHECK(env->Global()->Set(env.local(), v8_str("foo"), foo).FromJust());
|
|
CompileRun("foo()");
|
|
}
|
|
}
|
|
|
|
|
|
static void ShadowFunctionCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
args.GetReturnValue().Set(v8_num(42));
|
|
}
|
|
|
|
|
|
static int shadow_y;
|
|
static int shadow_y_setter_call_count;
|
|
static int shadow_y_getter_call_count;
|
|
|
|
|
|
static void ShadowYSetter(Local<String>,
|
|
Local<Value>,
|
|
const v8::PropertyCallbackInfo<void>&) {
|
|
shadow_y_setter_call_count++;
|
|
shadow_y = 42;
|
|
}
|
|
|
|
|
|
static void ShadowYGetter(Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
shadow_y_getter_call_count++;
|
|
info.GetReturnValue().Set(v8_num(shadow_y));
|
|
}
|
|
|
|
|
|
static void ShadowIndexedGet(uint32_t index,
|
|
const v8::PropertyCallbackInfo<v8::Value>&) {
|
|
}
|
|
|
|
|
|
static void ShadowNamedGet(Local<Name> key,
|
|
const v8::PropertyCallbackInfo<v8::Value>&) {}
|
|
|
|
THREADED_TEST(ShadowObject) {
|
|
shadow_y = shadow_y_setter_call_count = shadow_y_getter_call_count = 0;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<ObjectTemplate> global_template = v8::ObjectTemplate::New(isolate);
|
|
LocalContext context(nullptr, global_template);
|
|
|
|
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
|
|
t->InstanceTemplate()->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(ShadowNamedGet));
|
|
t->InstanceTemplate()->SetHandler(
|
|
v8::IndexedPropertyHandlerConfiguration(ShadowIndexedGet));
|
|
Local<ObjectTemplate> proto = t->PrototypeTemplate();
|
|
Local<ObjectTemplate> instance = t->InstanceTemplate();
|
|
|
|
proto->Set(isolate, "f",
|
|
v8::FunctionTemplate::New(isolate, ShadowFunctionCallback,
|
|
Local<Value>()));
|
|
proto->Set(isolate, "x", v8_num(12));
|
|
|
|
instance->SetAccessor(v8_str("y"), ShadowYGetter, ShadowYSetter);
|
|
|
|
Local<Value> o = t->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("__proto__"), o)
|
|
.FromJust());
|
|
|
|
Local<Value> value =
|
|
CompileRun("this.propertyIsEnumerable(0)");
|
|
CHECK(value->IsBoolean());
|
|
CHECK(!value->BooleanValue(isolate));
|
|
|
|
value = CompileRun("x");
|
|
CHECK_EQ(12, value->Int32Value(context.local()).FromJust());
|
|
|
|
value = CompileRun("f()");
|
|
CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
|
|
|
|
CompileRun("y = 43");
|
|
CHECK_EQ(1, shadow_y_setter_call_count);
|
|
value = CompileRun("y");
|
|
CHECK_EQ(1, shadow_y_getter_call_count);
|
|
CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
THREADED_TEST(ShadowObjectAndDataProperty) {
|
|
// Lite mode doesn't make use of feedback vectors, which is what we
|
|
// want to ensure has the correct form.
|
|
if (i::FLAG_lite_mode) return;
|
|
// This test mimics the kind of shadow property the Chromium embedder
|
|
// uses for undeclared globals. The IC subsystem has special handling
|
|
// for this case, using a PREMONOMORPHIC state to delay entering
|
|
// MONOMORPHIC state until enough information is available to support
|
|
// efficient access and good feedback for optimization.
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
i::FLAG_allow_natives_syntax = true;
|
|
|
|
Local<ObjectTemplate> global_template = v8::ObjectTemplate::New(isolate);
|
|
LocalContext context(nullptr, global_template);
|
|
|
|
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
|
|
t->InstanceTemplate()->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(ShadowNamedGet));
|
|
|
|
Local<Value> o = t->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("__proto__"), o)
|
|
.FromJust());
|
|
|
|
CompileRun(
|
|
"function foo(x) { i = x; }"
|
|
"%EnsureFeedbackVectorForFunction(foo);"
|
|
"foo(0)");
|
|
|
|
i::Handle<i::JSFunction> foo(i::Handle<i::JSFunction>::cast(
|
|
v8::Utils::OpenHandle(*context->Global()
|
|
->Get(context.local(), v8_str("foo"))
|
|
.ToLocalChecked())));
|
|
CHECK(foo->has_feedback_vector());
|
|
i::FeedbackSlot slot = i::FeedbackVector::ToSlot(0);
|
|
i::FeedbackNexus nexus(foo->feedback_vector(), slot);
|
|
CHECK_EQ(i::FeedbackSlotKind::kStoreGlobalSloppy, nexus.kind());
|
|
CompileRun("foo(1)");
|
|
CHECK_EQ(i::InlineCacheState::MONOMORPHIC, nexus.ic_state());
|
|
// We go a bit further, checking that the form of monomorphism is
|
|
// a PropertyCell in the vector. This is because we want to make sure
|
|
// we didn't settle for a "poor man's monomorphism," such as a
|
|
// slow_stub bailout which would mean a trip to the runtime on all
|
|
// subsequent stores, and a lack of feedback for the optimizing
|
|
// compiler downstream.
|
|
i::HeapObject heap_object;
|
|
CHECK(nexus.GetFeedback().GetHeapObject(&heap_object));
|
|
CHECK(heap_object.IsPropertyCell());
|
|
}
|
|
|
|
THREADED_TEST(ShadowObjectAndDataPropertyTurbo) {
|
|
// This test is the same as the previous one except that it triggers
|
|
// optimization of {foo} after its first invocation.
|
|
i::FLAG_allow_natives_syntax = true;
|
|
|
|
if (i::FLAG_lite_mode) return;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<ObjectTemplate> global_template = v8::ObjectTemplate::New(isolate);
|
|
LocalContext context(nullptr, global_template);
|
|
|
|
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
|
|
t->InstanceTemplate()->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(ShadowNamedGet));
|
|
|
|
Local<Value> o = t->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("__proto__"), o)
|
|
.FromJust());
|
|
|
|
CompileRun(
|
|
"function foo(x) { i = x; };"
|
|
"%PrepareFunctionForOptimization(foo);"
|
|
"foo(0)");
|
|
|
|
i::Handle<i::JSFunction> foo(i::Handle<i::JSFunction>::cast(
|
|
v8::Utils::OpenHandle(*context->Global()
|
|
->Get(context.local(), v8_str("foo"))
|
|
.ToLocalChecked())));
|
|
CHECK(foo->has_feedback_vector());
|
|
i::FeedbackSlot slot = i::FeedbackVector::ToSlot(0);
|
|
i::FeedbackNexus nexus(foo->feedback_vector(), slot);
|
|
CHECK_EQ(i::FeedbackSlotKind::kStoreGlobalSloppy, nexus.kind());
|
|
CompileRun("%OptimizeFunctionOnNextCall(foo); foo(1)");
|
|
CHECK_EQ(i::InlineCacheState::MONOMORPHIC, nexus.ic_state());
|
|
i::HeapObject heap_object;
|
|
CHECK(nexus.GetFeedback().GetHeapObject(&heap_object));
|
|
CHECK(heap_object.IsPropertyCell());
|
|
}
|
|
|
|
THREADED_TEST(SetPrototype) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> t0 = v8::FunctionTemplate::New(isolate);
|
|
t0->InstanceTemplate()->Set(isolate, "x", v8_num(0));
|
|
Local<v8::FunctionTemplate> t1 = v8::FunctionTemplate::New(isolate);
|
|
t1->InstanceTemplate()->Set(isolate, "y", v8_num(1));
|
|
Local<v8::FunctionTemplate> t2 = v8::FunctionTemplate::New(isolate);
|
|
t2->InstanceTemplate()->Set(isolate, "z", v8_num(2));
|
|
Local<v8::FunctionTemplate> t3 = v8::FunctionTemplate::New(isolate);
|
|
t3->InstanceTemplate()->Set(isolate, "u", v8_num(3));
|
|
|
|
Local<v8::Object> o0 = t0->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
Local<v8::Object> o1 = t1->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
Local<v8::Object> o2 = t2->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
Local<v8::Object> o3 = t3->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
|
|
CHECK_EQ(0, o0->Get(context.local(), v8_str("x"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK(o0->SetPrototype(context.local(), o1).FromJust());
|
|
CHECK_EQ(0, o0->Get(context.local(), v8_str("x"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(1, o0->Get(context.local(), v8_str("y"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK(o1->SetPrototype(context.local(), o2).FromJust());
|
|
CHECK_EQ(0, o0->Get(context.local(), v8_str("x"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(1, o0->Get(context.local(), v8_str("y"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(2, o0->Get(context.local(), v8_str("z"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK(o2->SetPrototype(context.local(), o3).FromJust());
|
|
CHECK_EQ(0, o0->Get(context.local(), v8_str("x"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(1, o0->Get(context.local(), v8_str("y"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(2, o0->Get(context.local(), v8_str("z"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(3, o0->Get(context.local(), v8_str("u"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
Local<Value> proto =
|
|
o0->Get(context.local(), v8_str("__proto__")).ToLocalChecked();
|
|
CHECK(proto->IsObject());
|
|
CHECK(proto.As<v8::Object>()->Equals(context.local(), o1).FromJust());
|
|
|
|
Local<Value> proto0 = o0->GetPrototype();
|
|
CHECK(proto0->IsObject());
|
|
CHECK(proto0.As<v8::Object>()->Equals(context.local(), o1).FromJust());
|
|
|
|
Local<Value> proto1 = o1->GetPrototype();
|
|
CHECK(proto1->IsObject());
|
|
CHECK(proto1.As<v8::Object>()->Equals(context.local(), o2).FromJust());
|
|
|
|
Local<Value> proto2 = o2->GetPrototype();
|
|
CHECK(proto2->IsObject());
|
|
CHECK(proto2.As<v8::Object>()->Equals(context.local(), o3).FromJust());
|
|
}
|
|
|
|
|
|
// Getting property names of an object with a prototype chain that
|
|
// triggers dictionary elements in GetOwnPropertyNames() shouldn't
|
|
// crash the runtime.
|
|
THREADED_TEST(Regress91517) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> t1 = v8::FunctionTemplate::New(isolate);
|
|
t1->InstanceTemplate()->Set(isolate, "foo", v8_num(1));
|
|
Local<v8::FunctionTemplate> t2 = v8::FunctionTemplate::New(isolate);
|
|
t2->InstanceTemplate()->Set(isolate, "fuz1", v8_num(2));
|
|
t2->InstanceTemplate()->Set(isolate, "objects",
|
|
v8::ObjectTemplate::New(isolate));
|
|
t2->InstanceTemplate()->Set(isolate, "fuz2", v8_num(2));
|
|
Local<v8::FunctionTemplate> t3 = v8::FunctionTemplate::New(isolate);
|
|
t3->InstanceTemplate()->Set(isolate, "boo", v8_num(3));
|
|
Local<v8::FunctionTemplate> t4 = v8::FunctionTemplate::New(isolate);
|
|
t4->InstanceTemplate()->Set(isolate, "baz", v8_num(4));
|
|
|
|
// Force dictionary-based properties.
|
|
v8::base::ScopedVector<char> name_buf(1024);
|
|
for (int i = 1; i <= 1000; i++) {
|
|
v8::base::SNPrintF(name_buf, "sdf%d", i);
|
|
t2->InstanceTemplate()->Set(v8_str(name_buf.begin()), v8_num(2));
|
|
}
|
|
|
|
Local<v8::Object> o1 = t1->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
Local<v8::Object> o2 = t2->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
Local<v8::Object> o3 = t3->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
Local<v8::Object> o4 = t4->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
|
|
CHECK(o4->SetPrototype(context.local(), o3).FromJust());
|
|
CHECK(o3->SetPrototype(context.local(), o2).FromJust());
|
|
CHECK(o2->SetPrototype(context.local(), o1).FromJust());
|
|
|
|
// Call the runtime version of GetOwnPropertyNames() on the natively
|
|
// created object through JavaScript.
|
|
CHECK(context->Global()->Set(context.local(), v8_str("obj"), o4).FromJust());
|
|
// PROPERTY_FILTER_NONE = 0
|
|
CompileRun("var names = %GetOwnPropertyKeys(obj, 0);");
|
|
|
|
ExpectInt32("names.length", 1);
|
|
ExpectTrue("names.indexOf(\"baz\") >= 0");
|
|
ExpectFalse("names.indexOf(\"boo\") >= 0");
|
|
ExpectFalse("names.indexOf(\"foo\") >= 0");
|
|
ExpectFalse("names.indexOf(\"fuz1\") >= 0");
|
|
ExpectFalse("names.indexOf(\"objects\") >= 0");
|
|
ExpectFalse("names.indexOf(\"fuz2\") >= 0");
|
|
ExpectTrue("names[1005] == undefined");
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionReadOnlyPrototype) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> t1 = v8::FunctionTemplate::New(isolate);
|
|
t1->PrototypeTemplate()->Set(isolate, "x", v8::Integer::New(isolate, 42));
|
|
t1->ReadOnlyPrototype();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("func1"),
|
|
t1->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
// Configured value of ReadOnly flag.
|
|
CHECK(
|
|
CompileRun(
|
|
"(function() {"
|
|
" descriptor = Object.getOwnPropertyDescriptor(func1, 'prototype');"
|
|
" return (descriptor['writable'] == false);"
|
|
"})()")
|
|
->BooleanValue(isolate));
|
|
CHECK_EQ(
|
|
42,
|
|
CompileRun("func1.prototype.x")->Int32Value(context.local()).FromJust());
|
|
CHECK_EQ(42, CompileRun("func1.prototype = {}; func1.prototype.x")
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
Local<v8::FunctionTemplate> t2 = v8::FunctionTemplate::New(isolate);
|
|
t2->PrototypeTemplate()->Set(isolate, "x", v8::Integer::New(isolate, 42));
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("func2"),
|
|
t2->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
// Default value of ReadOnly flag.
|
|
CHECK(
|
|
CompileRun(
|
|
"(function() {"
|
|
" descriptor = Object.getOwnPropertyDescriptor(func2, 'prototype');"
|
|
" return (descriptor['writable'] == true);"
|
|
"})()")
|
|
->BooleanValue(isolate));
|
|
CHECK_EQ(
|
|
42,
|
|
CompileRun("func2.prototype.x")->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(SetPrototypeThrows) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
|
|
|
|
Local<v8::Object> o0 = t->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
Local<v8::Object> o1 = t->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
|
|
CHECK(o0->SetPrototype(context.local(), o1).FromJust());
|
|
// If setting the prototype leads to the cycle, SetPrototype should
|
|
// return false, because cyclic prototype chains would be invalid.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(o1->SetPrototype(context.local(), o0).IsNothing());
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK(!CcTest::i_isolate()->has_pending_exception());
|
|
|
|
CHECK_EQ(42, CompileRun("function f() { return 42; }; f()")
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionRemovePrototype) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> t1 = v8::FunctionTemplate::New(isolate);
|
|
t1->RemovePrototype();
|
|
Local<v8::Function> fun = t1->GetFunction(context.local()).ToLocalChecked();
|
|
CHECK(!fun->IsConstructor());
|
|
CHECK(context->Global()->Set(context.local(), v8_str("fun"), fun).FromJust());
|
|
CHECK(!CompileRun("'prototype' in fun")->BooleanValue(isolate));
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun("new fun()");
|
|
CHECK(try_catch.HasCaught());
|
|
|
|
try_catch.Reset();
|
|
CHECK(fun->NewInstance(context.local()).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
THREADED_TEST(GetterSetterExceptions) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
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(context.local(), v8_str("x")).ToLocalChecked());
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(x->Set(context.local(), v8_str("set"), v8::Integer::New(isolate, 8))
|
|
.IsNothing());
|
|
CHECK(x->Get(context.local(), v8_str("get")).IsEmpty());
|
|
CHECK(x->Set(context.local(), v8_str("set"), v8::Integer::New(isolate, 8))
|
|
.IsNothing());
|
|
CHECK(x->Get(context.local(), v8_str("get")).IsEmpty());
|
|
CHECK(x->Set(context.local(), v8_str("set"), v8::Integer::New(isolate, 8))
|
|
.IsNothing());
|
|
CHECK(x->Get(context.local(), v8_str("get")).IsEmpty());
|
|
CHECK(x->Set(context.local(), v8_str("set"), v8::Integer::New(isolate, 8))
|
|
.IsNothing());
|
|
CHECK(x->Get(context.local(), v8_str("get")).IsEmpty());
|
|
}
|
|
|
|
|
|
THREADED_TEST(Constructor) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
templ->SetClassName(v8_str("Fun"));
|
|
Local<Function> cons = templ->GetFunction(context.local()).ToLocalChecked();
|
|
CHECK(
|
|
context->Global()->Set(context.local(), v8_str("Fun"), cons).FromJust());
|
|
Local<v8::Object> inst = cons->NewInstance(context.local()).ToLocalChecked();
|
|
i::Handle<i::JSReceiver> obj(v8::Utils::OpenHandle(*inst));
|
|
CHECK(obj->IsJSObject());
|
|
Local<Value> value = CompileRun("(new Fun()).constructor === Fun");
|
|
CHECK(value->BooleanValue(isolate));
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionDescriptorException) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
templ->SetClassName(v8_str("Fun"));
|
|
Local<Function> cons = templ->GetFunction(context.local()).ToLocalChecked();
|
|
CHECK(
|
|
context->Global()->Set(context.local(), v8_str("Fun"), cons).FromJust());
|
|
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(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(EvalAliasedDynamic) {
|
|
LocalContext current;
|
|
v8::HandleScope scope(current->GetIsolate());
|
|
|
|
// Tests where aliased eval can only be resolved dynamically.
|
|
Local<Script> script = v8_compile(
|
|
"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(current.local()).ToLocalChecked();
|
|
CHECK_EQ(2, current->Global()
|
|
->Get(current.local(), v8_str("result1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(current.local())
|
|
.FromJust());
|
|
CHECK_EQ(0, current->Global()
|
|
->Get(current.local(), v8_str("result2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(current.local())
|
|
.FromJust());
|
|
CHECK_EQ(1, current->Global()
|
|
->Get(current.local(), v8_str("result3"))
|
|
.ToLocalChecked()
|
|
->Int32Value(current.local())
|
|
.FromJust());
|
|
|
|
v8::TryCatch try_catch(current->GetIsolate());
|
|
script = v8_compile(
|
|
"function f(x) { "
|
|
" var bar = 2;"
|
|
" with (x) { return eval('bar'); }"
|
|
"}"
|
|
"result4 = f(this)");
|
|
script->Run(current.local()).ToLocalChecked();
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(2, current->Global()
|
|
->Get(current.local(), v8_str("result4"))
|
|
.ToLocalChecked()
|
|
->Int32Value(current.local())
|
|
.FromJust());
|
|
|
|
try_catch.Reset();
|
|
}
|
|
|
|
|
|
THREADED_TEST(CrossEval) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
LocalContext other;
|
|
LocalContext current;
|
|
|
|
Local<String> token = v8_str("<security token>");
|
|
other->SetSecurityToken(token);
|
|
current->SetSecurityToken(token);
|
|
|
|
// Set up reference from current to other.
|
|
CHECK(current->Global()
|
|
->Set(current.local(), v8_str("other"), other->Global())
|
|
.FromJust());
|
|
|
|
// Check that new variables are introduced in other context.
|
|
Local<Script> script = v8_compile("other.eval('var foo = 1234')");
|
|
script->Run(current.local()).ToLocalChecked();
|
|
Local<Value> foo =
|
|
other->Global()->Get(current.local(), v8_str("foo")).ToLocalChecked();
|
|
CHECK_EQ(1234, foo->Int32Value(other.local()).FromJust());
|
|
CHECK(!current->Global()->Has(current.local(), v8_str("foo")).FromJust());
|
|
|
|
// Check that writing to non-existing properties introduces them in
|
|
// the other context.
|
|
script = v8_compile("other.eval('na = 1234')");
|
|
script->Run(current.local()).ToLocalChecked();
|
|
CHECK_EQ(1234, other->Global()
|
|
->Get(current.local(), v8_str("na"))
|
|
.ToLocalChecked()
|
|
->Int32Value(other.local())
|
|
.FromJust());
|
|
CHECK(!current->Global()->Has(current.local(), v8_str("na")).FromJust());
|
|
|
|
// Check that global variables in current context are not visible in other
|
|
// context.
|
|
v8::TryCatch try_catch(CcTest::isolate());
|
|
script = v8_compile("var bar = 42; other.eval('bar');");
|
|
CHECK(script->Run(current.local()).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Check that local variables in current context are not visible in other
|
|
// context.
|
|
script = v8_compile(
|
|
"(function() { "
|
|
" var baz = 87;"
|
|
" return other.eval('baz');"
|
|
"})();");
|
|
CHECK(script->Run(current.local()).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Check that global variables in the other environment are visible
|
|
// when evaluting code.
|
|
CHECK(other->Global()
|
|
->Set(other.local(), v8_str("bis"), v8_num(1234))
|
|
.FromJust());
|
|
script = v8_compile("other.eval('bis')");
|
|
CHECK_EQ(1234, script->Run(current.local())
|
|
.ToLocalChecked()
|
|
->Int32Value(current.local())
|
|
.FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
// Check that the 'this' pointer points to the global object evaluating
|
|
// code.
|
|
CHECK(other->Global()
|
|
->Set(current.local(), v8_str("t"), other->Global())
|
|
.FromJust());
|
|
script = v8_compile("other.eval('this == t')");
|
|
Local<Value> result = script->Run(current.local()).ToLocalChecked();
|
|
CHECK(result->IsTrue());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
// Check that variables introduced in with-statement are not visible in
|
|
// other context.
|
|
script = v8_compile("with({x:2}){other.eval('x')}");
|
|
CHECK(script->Run(current.local()).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Check that you cannot use 'eval.call' with another object than the
|
|
// current global object.
|
|
script = v8_compile("other.y = 1; eval.call(other, 'y')");
|
|
CHECK(script->Run(current.local()).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
// Test that calling eval in a context which has been detached from
|
|
// its global proxy works.
|
|
THREADED_TEST(EvalInDetachedGlobal) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<Context> context0 = Context::New(isolate);
|
|
v8::Local<Context> context1 = Context::New(isolate);
|
|
Local<String> token = v8_str("<security token>");
|
|
context0->SetSecurityToken(token);
|
|
context1->SetSecurityToken(token);
|
|
|
|
// Set up function in context0 that uses eval from context0.
|
|
context0->Enter();
|
|
v8::Local<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 undefined is returned.
|
|
context1->Enter();
|
|
CHECK(context1->Global()->Set(context1, v8_str("fun"), fun).FromJust());
|
|
v8::Local<v8::Value> x_value = CompileRun("fun('x')");
|
|
CHECK_EQ(42, x_value->Int32Value(context1).FromJust());
|
|
context0->DetachGlobal();
|
|
x_value = CompileRun("fun('x')");
|
|
CHECK(x_value->IsUndefined());
|
|
context1->Exit();
|
|
}
|
|
|
|
|
|
THREADED_TEST(CrossLazyLoad) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
LocalContext other;
|
|
LocalContext current;
|
|
|
|
Local<String> token = v8_str("<security token>");
|
|
other->SetSecurityToken(token);
|
|
current->SetSecurityToken(token);
|
|
|
|
// Set up reference from current to other.
|
|
CHECK(current->Global()
|
|
->Set(current.local(), v8_str("other"), other->Global())
|
|
.FromJust());
|
|
|
|
// Trigger lazy loading in other context.
|
|
Local<Script> script = v8_compile("other.eval('new Date(42)')");
|
|
Local<Value> value = script->Run(current.local()).ToLocalChecked();
|
|
CHECK_EQ(42.0, value->NumberValue(current.local()).FromJust());
|
|
}
|
|
|
|
|
|
static void call_as_function(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
if (args.IsConstructCall()) {
|
|
if (args[0]->IsInt32()) {
|
|
args.GetReturnValue().Set(
|
|
v8_num(-args[0]
|
|
->Int32Value(args.GetIsolate()->GetCurrentContext())
|
|
.FromJust()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
args.GetReturnValue().Set(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) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
{
|
|
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
|
|
Local<ObjectTemplate> instance_template = t->InstanceTemplate();
|
|
instance_template->SetCallAsFunctionHandler(call_as_function);
|
|
Local<v8::Object> instance = t->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"), instance)
|
|
.FromJust());
|
|
v8::TryCatch try_catch(isolate);
|
|
Local<Value> value;
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
value = CompileRun("obj(42)");
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
|
|
|
|
value = CompileRun("(function(o){return o(49)})(obj)");
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(49, value->Int32Value(context.local()).FromJust());
|
|
|
|
// test special case of call as function
|
|
value = CompileRun("[obj]['0'](45)");
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(45, value->Int32Value(context.local()).FromJust());
|
|
|
|
value = CompileRun(
|
|
"obj.call = Function.prototype.call;"
|
|
"obj.call(null, 87)");
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(87, value->Int32Value(context.local()).FromJust());
|
|
|
|
// 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(context.local()).FromJust());
|
|
|
|
const char* call_17 = "Function.prototype.call.call(obj, this, 17)";
|
|
value = CompileRun(call_17);
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(17, value->Int32Value(context.local()).FromJust());
|
|
|
|
// 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(context.local()).FromJust());
|
|
|
|
// Check that the call-as-function handler can be called through
|
|
// the API.
|
|
v8::Local<Value> args[] = {v8_num(28)};
|
|
value = instance->CallAsFunction(context.local(), instance, 1, args)
|
|
.ToLocalChecked();
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(28, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
|
|
Local<ObjectTemplate> instance_template(t->InstanceTemplate());
|
|
USE(instance_template);
|
|
Local<v8::Object> instance = t->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj2"), instance)
|
|
.FromJust());
|
|
v8::TryCatch try_catch(isolate);
|
|
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::Utf8Value exception_value1(isolate, try_catch.Exception());
|
|
// TODO(verwaest): Better message
|
|
CHECK_EQ(0, strcmp("TypeError: obj2 is not a function", *exception_value1));
|
|
try_catch.Reset();
|
|
|
|
// Call an object without call-as-function handler through the API
|
|
v8::Local<Value> args[] = {v8_num(28)};
|
|
CHECK(
|
|
instance->CallAsFunction(context.local(), instance, 1, args).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
String::Utf8Value exception_value2(isolate, try_catch.Exception());
|
|
CHECK_EQ(0,
|
|
strcmp("TypeError: object is not a function", *exception_value2));
|
|
try_catch.Reset();
|
|
}
|
|
|
|
{
|
|
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
|
|
Local<ObjectTemplate> instance_template = t->InstanceTemplate();
|
|
instance_template->SetCallAsFunctionHandler(ThrowValue);
|
|
Local<v8::Object> instance = t->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj3"), instance)
|
|
.FromJust());
|
|
v8::TryCatch try_catch(isolate);
|
|
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::Utf8Value exception_value1(isolate, try_catch.Exception());
|
|
CHECK_EQ(0, strcmp("22", *exception_value1));
|
|
try_catch.Reset();
|
|
|
|
v8::Local<Value> args[] = {v8_num(23)};
|
|
CHECK(
|
|
instance->CallAsFunction(context.local(), instance, 1, args).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
String::Utf8Value exception_value2(isolate, try_catch.Exception());
|
|
CHECK_EQ(0, strcmp("23", *exception_value2));
|
|
try_catch.Reset();
|
|
}
|
|
|
|
{
|
|
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
|
|
Local<ObjectTemplate> instance_template = t->InstanceTemplate();
|
|
instance_template->SetCallAsFunctionHandler(ReturnThis);
|
|
Local<v8::Object> instance = t->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
|
|
Local<v8::Value> a1 =
|
|
instance
|
|
->CallAsFunction(context.local(), v8::Undefined(isolate), 0,
|
|
nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a1->StrictEquals(instance));
|
|
Local<v8::Value> a2 =
|
|
instance->CallAsFunction(context.local(), v8::Null(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a2->StrictEquals(instance));
|
|
Local<v8::Value> a3 =
|
|
instance->CallAsFunction(context.local(), v8_num(42), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a3->StrictEquals(instance));
|
|
Local<v8::Value> a4 =
|
|
instance->CallAsFunction(context.local(), v8_str("hello"), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a4->StrictEquals(instance));
|
|
Local<v8::Value> a5 =
|
|
instance->CallAsFunction(context.local(), v8::True(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a5->StrictEquals(instance));
|
|
}
|
|
|
|
{
|
|
CompileRun(
|
|
"function ReturnThisSloppy() {"
|
|
" return this;"
|
|
"}"
|
|
"function ReturnThisStrict() {"
|
|
" 'use strict';"
|
|
" return this;"
|
|
"}");
|
|
Local<Function> ReturnThisSloppy = Local<Function>::Cast(
|
|
context->Global()
|
|
->Get(context.local(), v8_str("ReturnThisSloppy"))
|
|
.ToLocalChecked());
|
|
Local<Function> ReturnThisStrict = Local<Function>::Cast(
|
|
context->Global()
|
|
->Get(context.local(), v8_str("ReturnThisStrict"))
|
|
.ToLocalChecked());
|
|
|
|
Local<v8::Value> a1 =
|
|
ReturnThisSloppy
|
|
->CallAsFunction(context.local(), v8::Undefined(isolate), 0,
|
|
nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a1->StrictEquals(context->Global()));
|
|
Local<v8::Value> a2 =
|
|
ReturnThisSloppy
|
|
->CallAsFunction(context.local(), v8::Null(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a2->StrictEquals(context->Global()));
|
|
Local<v8::Value> a3 =
|
|
ReturnThisSloppy
|
|
->CallAsFunction(context.local(), v8_num(42), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a3->IsNumberObject());
|
|
CHECK_EQ(42.0, a3.As<v8::NumberObject>()->ValueOf());
|
|
Local<v8::Value> a4 =
|
|
ReturnThisSloppy
|
|
->CallAsFunction(context.local(), v8_str("hello"), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a4->IsStringObject());
|
|
CHECK(a4.As<v8::StringObject>()->ValueOf()->StrictEquals(v8_str("hello")));
|
|
Local<v8::Value> a5 =
|
|
ReturnThisSloppy
|
|
->CallAsFunction(context.local(), v8::True(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a5->IsBooleanObject());
|
|
CHECK(a5.As<v8::BooleanObject>()->ValueOf());
|
|
|
|
Local<v8::Value> a6 =
|
|
ReturnThisStrict
|
|
->CallAsFunction(context.local(), v8::Undefined(isolate), 0,
|
|
nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a6->IsUndefined());
|
|
Local<v8::Value> a7 =
|
|
ReturnThisStrict
|
|
->CallAsFunction(context.local(), v8::Null(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a7->IsNull());
|
|
Local<v8::Value> a8 =
|
|
ReturnThisStrict
|
|
->CallAsFunction(context.local(), v8_num(42), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a8->StrictEquals(v8_num(42)));
|
|
Local<v8::Value> a9 =
|
|
ReturnThisStrict
|
|
->CallAsFunction(context.local(), v8_str("hello"), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a9->StrictEquals(v8_str("hello")));
|
|
Local<v8::Value> a10 =
|
|
ReturnThisStrict
|
|
->CallAsFunction(context.local(), v8::True(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
CHECK(a10->StrictEquals(v8::True(isolate)));
|
|
}
|
|
}
|
|
|
|
|
|
// Check whether a non-function object is callable.
|
|
THREADED_TEST(CallableObject) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
{
|
|
Local<ObjectTemplate> instance_template = ObjectTemplate::New(isolate);
|
|
instance_template->SetCallAsFunctionHandler(call_as_function);
|
|
Local<Object> instance =
|
|
instance_template->NewInstance(context.local()).ToLocalChecked();
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
CHECK(instance->IsCallable());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
Local<ObjectTemplate> instance_template = ObjectTemplate::New(isolate);
|
|
Local<Object> instance =
|
|
instance_template->NewInstance(context.local()).ToLocalChecked();
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
CHECK(!instance->IsCallable());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
Local<FunctionTemplate> function_template =
|
|
FunctionTemplate::New(isolate, call_as_function);
|
|
Local<Function> function =
|
|
function_template->GetFunction(context.local()).ToLocalChecked();
|
|
Local<Object> instance = function;
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
CHECK(instance->IsCallable());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
Local<FunctionTemplate> function_template = FunctionTemplate::New(isolate);
|
|
Local<Function> function =
|
|
function_template->GetFunction(context.local()).ToLocalChecked();
|
|
Local<Object> instance = function;
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
CHECK(instance->IsCallable());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(Regress567998) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
|
|
Local<v8::FunctionTemplate> desc =
|
|
v8::FunctionTemplate::New(env->GetIsolate());
|
|
desc->InstanceTemplate()->MarkAsUndetectable(); // undetectable
|
|
desc->InstanceTemplate()->SetCallAsFunctionHandler(ReturnThis); // callable
|
|
|
|
Local<v8::Object> obj = desc->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked();
|
|
CHECK(
|
|
env->Global()->Set(env.local(), v8_str("undetectable"), obj).FromJust());
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
static int Recurse(v8::Isolate* isolate, int depth, int iterations) {
|
|
v8::HandleScope scope(isolate);
|
|
if (depth == 0) return v8::HandleScope::NumberOfHandles(isolate);
|
|
for (int i = 0; i < iterations; i++) {
|
|
Local<v8::Number> n(v8::Integer::New(isolate, 42));
|
|
}
|
|
return Recurse(isolate, depth - 1, iterations);
|
|
}
|
|
|
|
|
|
THREADED_TEST(HandleIteration) {
|
|
static const int kIterations = 500;
|
|
static const int kNesting = 200;
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope0(isolate);
|
|
CHECK_EQ(0, v8::HandleScope::NumberOfHandles(isolate));
|
|
{
|
|
v8::HandleScope scope1(isolate);
|
|
CHECK_EQ(0, v8::HandleScope::NumberOfHandles(isolate));
|
|
for (int i = 0; i < kIterations; i++) {
|
|
Local<v8::Number> n(v8::Integer::New(CcTest::isolate(), 42));
|
|
CHECK_EQ(i + 1, v8::HandleScope::NumberOfHandles(isolate));
|
|
}
|
|
|
|
CHECK_EQ(kIterations, v8::HandleScope::NumberOfHandles(isolate));
|
|
{
|
|
v8::HandleScope scope2(CcTest::isolate());
|
|
for (int j = 0; j < kIterations; j++) {
|
|
Local<v8::Number> n(v8::Integer::New(CcTest::isolate(), 42));
|
|
CHECK_EQ(j + 1 + kIterations,
|
|
v8::HandleScope::NumberOfHandles(isolate));
|
|
}
|
|
}
|
|
CHECK_EQ(kIterations, v8::HandleScope::NumberOfHandles(isolate));
|
|
}
|
|
CHECK_EQ(0, v8::HandleScope::NumberOfHandles(isolate));
|
|
CHECK_EQ(kNesting * kIterations, Recurse(isolate, kNesting, kIterations));
|
|
}
|
|
|
|
|
|
static void InterceptorCallICFastApi(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CheckReturnValue(info, FUNCTION_ADDR(InterceptorCallICFastApi));
|
|
int* call_count =
|
|
reinterpret_cast<int*>(v8::External::Cast(*info.Data())->Value());
|
|
++(*call_count);
|
|
if ((*call_count) % 20 == 0) {
|
|
CcTest::CollectAllGarbage();
|
|
}
|
|
}
|
|
|
|
static void FastApiCallback_TrivialSignature(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CheckReturnValue(args, FUNCTION_ADDR(FastApiCallback_TrivialSignature));
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
CHECK_EQ(isolate, args.GetIsolate());
|
|
CHECK(args.This()
|
|
->Equals(isolate->GetCurrentContext(), args.Holder())
|
|
.FromJust());
|
|
CHECK(args.Data()
|
|
->Equals(isolate->GetCurrentContext(), v8_str("method_data"))
|
|
.FromJust());
|
|
args.GetReturnValue().Set(
|
|
args[0]->Int32Value(isolate->GetCurrentContext()).FromJust() + 1);
|
|
}
|
|
|
|
static void FastApiCallback_SimpleSignature(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
CheckReturnValue(args, FUNCTION_ADDR(FastApiCallback_SimpleSignature));
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
CHECK_EQ(isolate, args.GetIsolate());
|
|
CHECK(args.This()
|
|
->GetPrototype()
|
|
->Equals(isolate->GetCurrentContext(), args.Holder())
|
|
.FromJust());
|
|
CHECK(args.Data()
|
|
->Equals(isolate->GetCurrentContext(), v8_str("method_data"))
|
|
.FromJust());
|
|
// Note, we're using HasRealNamedProperty instead of Has to avoid
|
|
// invoking the interceptor again.
|
|
CHECK(args.Holder()
|
|
->HasRealNamedProperty(isolate->GetCurrentContext(), v8_str("foo"))
|
|
.FromJust());
|
|
args.GetReturnValue().Set(
|
|
args[0]->Int32Value(isolate->GetCurrentContext()).FromJust() + 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;");
|
|
}
|
|
|
|
|
|
void DirectApiCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
static int count = 0;
|
|
if (count++ % 3 == 0) {
|
|
CcTest::CollectAllGarbage();
|
|
// This should move the stub
|
|
GenerateSomeGarbage(); // This should ensure the old stub memory is flushed
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(CallICFastApi_DirectCall_GCMoveStub) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> nativeobject_templ =
|
|
v8::ObjectTemplate::New(isolate);
|
|
nativeobject_templ->Set(isolate, "callback",
|
|
v8::FunctionTemplate::New(isolate,
|
|
DirectApiCallback));
|
|
v8::Local<v8::Object> nativeobject_obj =
|
|
nativeobject_templ->NewInstance(context.local()).ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("nativeobject"), nativeobject_obj)
|
|
.FromJust());
|
|
// 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();");
|
|
}
|
|
|
|
void ThrowingDirectApiCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
args.GetIsolate()->ThrowException(v8_str("g"));
|
|
}
|
|
|
|
THREADED_TEST(CallICFastApi_DirectCall_Throw) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> nativeobject_templ =
|
|
v8::ObjectTemplate::New(isolate);
|
|
nativeobject_templ->Set(
|
|
isolate, "callback",
|
|
v8::FunctionTemplate::New(isolate, ThrowingDirectApiCallback));
|
|
v8::Local<v8::Object> nativeobject_obj =
|
|
nativeobject_templ->NewInstance(context.local()).ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("nativeobject"), nativeobject_obj)
|
|
.FromJust());
|
|
// call the api function multiple times to ensure direct call stub creation.
|
|
v8::Local<Value> result = CompileRun(
|
|
"var result = '';"
|
|
"function f() {"
|
|
" for (var i = 1; i <= 5; i++) {"
|
|
" try { nativeobject.callback(); } catch (e) { result += e; }"
|
|
" }"
|
|
"}"
|
|
"f(); result;");
|
|
CHECK(v8_str("ggggg")->Equals(context.local(), result).FromJust());
|
|
}
|
|
|
|
static int p_getter_count_3;
|
|
|
|
static Local<Value> DoDirectGetter() {
|
|
if (++p_getter_count_3 % 3 == 0) {
|
|
CcTest::CollectAllGarbage();
|
|
GenerateSomeGarbage();
|
|
}
|
|
return v8_str("Direct Getter Result");
|
|
}
|
|
|
|
static void DirectGetterCallback(
|
|
Local<String> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CheckReturnValue(info, FUNCTION_ADDR(DirectGetterCallback));
|
|
info.GetReturnValue().Set(DoDirectGetter());
|
|
}
|
|
|
|
template <typename Accessor>
|
|
static void LoadICFastApi_DirectCall_GCMoveStub(Accessor accessor) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = v8::ObjectTemplate::New(isolate);
|
|
obj->SetAccessor(v8_str("p1"), accessor);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("o1"),
|
|
obj->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
p_getter_count_3 = 0;
|
|
v8::Local<v8::Value> result = CompileRun(
|
|
"function f() {"
|
|
" for (var i = 0; i < 30; i++) o1.p1;"
|
|
" return o1.p1"
|
|
"}"
|
|
"f();");
|
|
CHECK(v8_str("Direct Getter Result")
|
|
->Equals(context.local(), result)
|
|
.FromJust());
|
|
CHECK_EQ(31, p_getter_count_3);
|
|
}
|
|
|
|
THREADED_PROFILED_TEST(LoadICFastApi_DirectCall_GCMoveStub) {
|
|
LoadICFastApi_DirectCall_GCMoveStub(DirectGetterCallback);
|
|
}
|
|
|
|
void ThrowingDirectGetterCallback(
|
|
Local<String> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetIsolate()->ThrowException(v8_str("g"));
|
|
}
|
|
|
|
THREADED_TEST(LoadICFastApi_DirectCall_Throw) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> obj = v8::ObjectTemplate::New(isolate);
|
|
obj->SetAccessor(v8_str("p1"), ThrowingDirectGetterCallback);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("o1"),
|
|
obj->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
v8::Local<Value> result = CompileRun(
|
|
"var result = '';"
|
|
"for (var i = 0; i < 5; i++) {"
|
|
" try { o1.p1; } catch (e) { result += e; }"
|
|
"}"
|
|
"result;");
|
|
CHECK(v8_str("ggggg")->Equals(context.local(), result).FromJust());
|
|
}
|
|
|
|
THREADED_PROFILED_TEST(InterceptorCallICFastApi_TrivialSignature) {
|
|
int interceptor_call_count = 0;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(isolate);
|
|
v8::Local<v8::FunctionTemplate> method_templ = v8::FunctionTemplate::New(
|
|
isolate, FastApiCallback_TrivialSignature, v8_str("method_data"),
|
|
v8::Local<v8::Signature>());
|
|
v8::Local<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
|
|
proto_templ->Set(isolate, "method", method_templ);
|
|
v8::Local<v8::ObjectTemplate> templ = fun_templ->InstanceTemplate();
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
InterceptorCallICFastApi, nullptr, nullptr, nullptr, nullptr,
|
|
v8::External::New(isolate, &interceptor_call_count)));
|
|
LocalContext context;
|
|
v8::Local<v8::Function> fun =
|
|
fun_templ->GetFunction(context.local()).ToLocalChecked();
|
|
GenerateSomeGarbage();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
fun->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CompileRun(
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 100; i++) {"
|
|
" result = o.method(41);"
|
|
"}");
|
|
CHECK_EQ(42, context->Global()
|
|
->Get(context.local(), v8_str("result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(100, interceptor_call_count);
|
|
}
|
|
|
|
THREADED_PROFILED_TEST(CallICFastApi_TrivialSignature) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(isolate);
|
|
v8::Local<v8::FunctionTemplate> method_templ = v8::FunctionTemplate::New(
|
|
isolate, FastApiCallback_TrivialSignature, v8_str("method_data"),
|
|
v8::Local<v8::Signature>());
|
|
v8::Local<v8::ObjectTemplate> proto_templ = fun_templ->PrototypeTemplate();
|
|
proto_templ->Set(isolate, "method", method_templ);
|
|
v8::Local<v8::ObjectTemplate> templ(fun_templ->InstanceTemplate());
|
|
USE(templ);
|
|
LocalContext context;
|
|
v8::Local<v8::Function> fun =
|
|
fun_templ->GetFunction(context.local()).ToLocalChecked();
|
|
GenerateSomeGarbage();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
fun->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CompileRun(
|
|
"var result = 0;"
|
|
"for (var i = 0; i < 100; i++) {"
|
|
" result = o.method(41);"
|
|
"}");
|
|
|
|
CHECK_EQ(42, context->Global()
|
|
->Get(context.local(), v8_str("result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
static void ThrowingGetter(Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
info.GetIsolate()->ThrowException(Local<Value>());
|
|
info.GetReturnValue().SetUndefined();
|
|
}
|
|
|
|
|
|
THREADED_TEST(VariousGetPropertiesAndThrowingCallbacks) {
|
|
LocalContext context;
|
|
HandleScope scope(context->GetIsolate());
|
|
|
|
Local<FunctionTemplate> templ = FunctionTemplate::New(context->GetIsolate());
|
|
Local<ObjectTemplate> instance_templ = templ->InstanceTemplate();
|
|
instance_templ->SetAccessor(v8_str("f"), ThrowingGetter);
|
|
|
|
Local<Object> instance = templ->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
|
|
Local<Object> another = Object::New(context->GetIsolate());
|
|
CHECK(another->SetPrototype(context.local(), instance).FromJust());
|
|
|
|
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(context->GetIsolate());
|
|
|
|
v8::MaybeLocal<Value> result =
|
|
instance->GetRealNamedProperty(context.local(), v8_str("f"));
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
CHECK(result.IsEmpty());
|
|
|
|
Maybe<PropertyAttribute> attr =
|
|
instance->GetRealNamedPropertyAttributes(context.local(), v8_str("f"));
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK(Just(None) == attr);
|
|
|
|
result = another->GetRealNamedProperty(context.local(), v8_str("f"));
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
CHECK(result.IsEmpty());
|
|
|
|
attr = another->GetRealNamedPropertyAttributes(context.local(), v8_str("f"));
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK(Just(None) == attr);
|
|
|
|
result = another->GetRealNamedPropertyInPrototypeChain(context.local(),
|
|
v8_str("f"));
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
CHECK(result.IsEmpty());
|
|
|
|
attr = another->GetRealNamedPropertyAttributesInPrototypeChain(
|
|
context.local(), v8_str("f"));
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK(Just(None) == attr);
|
|
|
|
result = another->Get(context.local(), v8_str("f"));
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
CHECK(result.IsEmpty());
|
|
|
|
result = with_js_getter->GetRealNamedProperty(context.local(), v8_str("f"));
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
CHECK(result.IsEmpty());
|
|
|
|
attr = with_js_getter->GetRealNamedPropertyAttributes(context.local(),
|
|
v8_str("f"));
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK(Just(None) == attr);
|
|
|
|
result = with_js_getter->Get(context.local(), v8_str("f"));
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
CHECK(result.IsEmpty());
|
|
|
|
Local<Object> target = CompileRun("({})").As<Object>();
|
|
Local<Object> handler = CompileRun("({})").As<Object>();
|
|
Local<v8::Proxy> proxy =
|
|
v8::Proxy::New(context.local(), target, handler).ToLocalChecked();
|
|
|
|
result = target->GetRealNamedProperty(context.local(), v8_str("f"));
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK(result.IsEmpty());
|
|
|
|
result = proxy->GetRealNamedProperty(context.local(), v8_str("f"));
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK(result.IsEmpty());
|
|
}
|
|
|
|
THREADED_TEST(GetRealNamedPropertyAttributes_With_Proxy) {
|
|
LocalContext context;
|
|
HandleScope scope(context->GetIsolate());
|
|
|
|
{
|
|
Local<Object> proxy =
|
|
CompileRun(
|
|
"new Proxy({ p: 1 }, { getOwnPropertyDescriptor: _ => { "
|
|
" throw new Error('xyz'); } });")
|
|
.As<Object>();
|
|
TryCatch try_catch(context->GetIsolate());
|
|
v8::Maybe<v8::PropertyAttribute> result =
|
|
proxy->GetRealNamedPropertyAttributes(context.local(), v8_str("p"));
|
|
CHECK(result.IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()
|
|
.As<Object>()
|
|
->Get(context.local(), v8_str("message"))
|
|
.ToLocalChecked()
|
|
->StrictEquals(v8_str("xyz")));
|
|
}
|
|
|
|
{
|
|
Local<Object> proxy =
|
|
CompileRun(
|
|
"Object.create("
|
|
" new Proxy({ p: 1 }, { getOwnPropertyDescriptor: _ => { "
|
|
" throw new Error('abc'); } }))")
|
|
.As<Object>();
|
|
TryCatch try_catch(context->GetIsolate());
|
|
v8::Maybe<v8::PropertyAttribute> result =
|
|
proxy->GetRealNamedPropertyAttributesInPrototypeChain(context.local(),
|
|
v8_str("p"));
|
|
CHECK(result.IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()
|
|
.As<Object>()
|
|
->Get(context.local(), v8_str("message"))
|
|
.ToLocalChecked()
|
|
->StrictEquals(v8_str("abc")));
|
|
}
|
|
}
|
|
|
|
static void ThrowingCallbackWithTryCatch(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
TryCatch try_catch(args.GetIsolate());
|
|
// 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(!CcTest::i_isolate()->has_pending_exception());
|
|
CHECK(!CcTest::i_isolate()->has_scheduled_exception());
|
|
}
|
|
|
|
|
|
static int call_depth;
|
|
|
|
|
|
static void WithTryCatch(Local<Message> message, Local<Value> data) {
|
|
TryCatch try_catch(CcTest::isolate());
|
|
}
|
|
|
|
|
|
static void ThrowFromJS(Local<Message> message, Local<Value> data) {
|
|
if (--call_depth) CompileRun("throw 'ThrowInJS';");
|
|
}
|
|
|
|
|
|
static void ThrowViaApi(Local<Message> message, Local<Value> data) {
|
|
if (--call_depth) CcTest::isolate()->ThrowException(v8_str("ThrowViaApi"));
|
|
}
|
|
|
|
|
|
static void WebKitLike(Local<Message> message, Local<Value> data) {
|
|
Local<String> errorMessageString = message->Get();
|
|
CHECK(!errorMessageString.IsEmpty());
|
|
message->GetStackTrace();
|
|
message->GetScriptOrigin().ResourceName();
|
|
}
|
|
|
|
|
|
THREADED_TEST(ExceptionsDoNotPropagatePastTryCatch) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
HandleScope scope(isolate);
|
|
|
|
Local<Function> func =
|
|
FunctionTemplate::New(isolate, ThrowingCallbackWithTryCatch)
|
|
->GetFunction(context.local())
|
|
.ToLocalChecked();
|
|
CHECK(
|
|
context->Global()->Set(context.local(), v8_str("func"), func).FromJust());
|
|
|
|
MessageCallback callbacks[] = {nullptr, WebKitLike, ThrowViaApi, ThrowFromJS,
|
|
WithTryCatch};
|
|
for (unsigned i = 0; i < sizeof(callbacks)/sizeof(callbacks[0]); i++) {
|
|
MessageCallback callback = callbacks[i];
|
|
if (callback != nullptr) {
|
|
isolate->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 != nullptr) {
|
|
isolate->RemoveMessageListeners(callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void ParentGetter(Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
info.GetReturnValue().Set(v8_num(1));
|
|
}
|
|
|
|
|
|
static void ChildGetter(Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
info.GetReturnValue().Set(v8_num(42));
|
|
}
|
|
|
|
|
|
THREADED_TEST(Overriding) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Parent template.
|
|
Local<v8::FunctionTemplate> parent_templ = v8::FunctionTemplate::New(isolate);
|
|
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(isolate);
|
|
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, nullptr,
|
|
v8::Local<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
|
|
// nullptr setter.
|
|
child_instance_templ->SetAccessor(v8_str("i"), ChildGetter, nullptr,
|
|
v8::Local<Value>(), v8::DEFAULT,
|
|
v8::ReadOnly);
|
|
|
|
// Instantiate the child template.
|
|
Local<v8::Object> instance = child_templ->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
|
|
// Check that the child function overrides the parent one.
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("o"), instance)
|
|
.FromJust());
|
|
Local<Value> value = v8_compile("o.f")->Run(context.local()).ToLocalChecked();
|
|
// Check that the 'g' that was added last is hit.
|
|
CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
|
|
value = v8_compile("o.g")->Run(context.local()).ToLocalChecked();
|
|
CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
|
|
|
|
// Check that 'h' cannot be shadowed.
|
|
value = v8_compile("o.h = 3; o.h")->Run(context.local()).ToLocalChecked();
|
|
CHECK_EQ(1, value->Int32Value(context.local()).FromJust());
|
|
|
|
// Check that 'i' cannot be shadowed or changed.
|
|
value = v8_compile("o.i = 3; o.i")->Run(context.local()).ToLocalChecked();
|
|
CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
static void ShouldThrowOnErrorGetter(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
Local<Boolean> should_throw_on_error =
|
|
Boolean::New(isolate, info.ShouldThrowOnError());
|
|
info.GetReturnValue().Set(should_throw_on_error);
|
|
}
|
|
|
|
|
|
template <typename T>
|
|
static void ShouldThrowOnErrorSetter(Local<Name> name, Local<v8::Value> value,
|
|
const v8::PropertyCallbackInfo<T>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
auto context = isolate->GetCurrentContext();
|
|
Local<Boolean> should_throw_on_error_value =
|
|
Boolean::New(isolate, info.ShouldThrowOnError());
|
|
CHECK(context->Global()
|
|
->Set(isolate->GetCurrentContext(), v8_str("should_throw_setter"),
|
|
should_throw_on_error_value)
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(AccessorShouldThrowOnError) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<Object> global = context->Global();
|
|
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
Local<ObjectTemplate> instance_templ = templ->InstanceTemplate();
|
|
instance_templ->SetAccessor(v8_str("f"), ShouldThrowOnErrorGetter,
|
|
ShouldThrowOnErrorSetter<void>);
|
|
|
|
Local<v8::Object> instance = templ->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
|
|
CHECK(global->Set(context.local(), v8_str("o"), instance).FromJust());
|
|
|
|
// SLOPPY mode
|
|
Local<Value> value = v8_compile("o.f")->Run(context.local()).ToLocalChecked();
|
|
CHECK(value->IsFalse());
|
|
v8_compile("o.f = 153")->Run(context.local()).ToLocalChecked();
|
|
value = global->Get(context.local(), v8_str("should_throw_setter"))
|
|
.ToLocalChecked();
|
|
CHECK(value->IsFalse());
|
|
|
|
// STRICT mode
|
|
value = v8_compile("'use strict';o.f")->Run(context.local()).ToLocalChecked();
|
|
CHECK(value->IsFalse());
|
|
v8_compile("'use strict'; o.f = 153")->Run(context.local()).ToLocalChecked();
|
|
value = global->Get(context.local(), v8_str("should_throw_setter"))
|
|
.ToLocalChecked();
|
|
CHECK(value->IsTrue());
|
|
}
|
|
|
|
|
|
static void ShouldThrowOnErrorQuery(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Integer>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
info.GetReturnValue().Set(v8::None);
|
|
|
|
auto context = isolate->GetCurrentContext();
|
|
Local<Boolean> should_throw_on_error_value =
|
|
Boolean::New(isolate, info.ShouldThrowOnError());
|
|
CHECK(context->Global()
|
|
->Set(isolate->GetCurrentContext(), v8_str("should_throw_query"),
|
|
should_throw_on_error_value)
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
static void ShouldThrowOnErrorDeleter(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Boolean>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
info.GetReturnValue().Set(v8::True(isolate));
|
|
|
|
auto context = isolate->GetCurrentContext();
|
|
Local<Boolean> should_throw_on_error_value =
|
|
Boolean::New(isolate, info.ShouldThrowOnError());
|
|
CHECK(context->Global()
|
|
->Set(isolate->GetCurrentContext(), v8_str("should_throw_deleter"),
|
|
should_throw_on_error_value)
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
static void ShouldThrowOnErrorPropertyEnumerator(
|
|
const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
Local<v8::Array> names = v8::Array::New(isolate, 1);
|
|
CHECK(names->Set(isolate->GetCurrentContext(), names, v8_num(1)).FromJust());
|
|
info.GetReturnValue().Set(names);
|
|
|
|
auto context = isolate->GetCurrentContext();
|
|
Local<Boolean> should_throw_on_error_value =
|
|
Boolean::New(isolate, info.ShouldThrowOnError());
|
|
CHECK(context->Global()
|
|
->Set(isolate->GetCurrentContext(),
|
|
v8_str("should_throw_enumerator"),
|
|
should_throw_on_error_value)
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(InterceptorShouldThrowOnError) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<Object> global = context->Global();
|
|
|
|
auto interceptor_templ = v8::ObjectTemplate::New(isolate);
|
|
v8::NamedPropertyHandlerConfiguration handler(
|
|
ShouldThrowOnErrorGetter, ShouldThrowOnErrorSetter<Value>,
|
|
ShouldThrowOnErrorQuery, ShouldThrowOnErrorDeleter,
|
|
ShouldThrowOnErrorPropertyEnumerator);
|
|
interceptor_templ->SetHandler(handler);
|
|
|
|
Local<v8::Object> instance =
|
|
interceptor_templ->NewInstance(context.local()).ToLocalChecked();
|
|
|
|
CHECK(global->Set(context.local(), v8_str("o"), instance).FromJust());
|
|
|
|
// SLOPPY mode
|
|
Local<Value> value = v8_compile("o.f")->Run(context.local()).ToLocalChecked();
|
|
CHECK(value->IsFalse());
|
|
v8_compile("o.f = 153")->Run(context.local()).ToLocalChecked();
|
|
value = global->Get(context.local(), v8_str("should_throw_setter"))
|
|
.ToLocalChecked();
|
|
CHECK(value->IsFalse());
|
|
|
|
v8_compile("delete o.f")->Run(context.local()).ToLocalChecked();
|
|
value = global->Get(context.local(), v8_str("should_throw_deleter"))
|
|
.ToLocalChecked();
|
|
CHECK(value->IsFalse());
|
|
|
|
v8_compile("Object.getOwnPropertyNames(o)")
|
|
->Run(context.local())
|
|
.ToLocalChecked();
|
|
value = global->Get(context.local(), v8_str("should_throw_enumerator"))
|
|
.ToLocalChecked();
|
|
CHECK(value->IsFalse());
|
|
|
|
// STRICT mode
|
|
value = v8_compile("'use strict';o.f")->Run(context.local()).ToLocalChecked();
|
|
CHECK(value->IsFalse());
|
|
v8_compile("'use strict'; o.f = 153")->Run(context.local()).ToLocalChecked();
|
|
value = global->Get(context.local(), v8_str("should_throw_setter"))
|
|
.ToLocalChecked();
|
|
CHECK(value->IsTrue());
|
|
|
|
v8_compile("'use strict'; delete o.f")->Run(context.local()).ToLocalChecked();
|
|
value = global->Get(context.local(), v8_str("should_throw_deleter"))
|
|
.ToLocalChecked();
|
|
CHECK(value->IsTrue());
|
|
|
|
v8_compile("'use strict'; Object.getOwnPropertyNames(o)")
|
|
->Run(context.local())
|
|
.ToLocalChecked();
|
|
value = global->Get(context.local(), v8_str("should_throw_enumerator"))
|
|
.ToLocalChecked();
|
|
CHECK(value->IsFalse());
|
|
}
|
|
|
|
static void EmptyHandler(const v8::FunctionCallbackInfo<v8::Value>& args) {}
|
|
|
|
TEST(CallHandlerHasNoSideEffect) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext context;
|
|
|
|
// Function template with call handler.
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
templ->SetCallHandler(EmptyHandler);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f"),
|
|
templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("new f()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
|
|
// Side-effect-free version.
|
|
Local<v8::FunctionTemplate> templ2 = v8::FunctionTemplate::New(isolate);
|
|
templ2->SetCallHandler(EmptyHandler, v8::Local<Value>(),
|
|
v8::SideEffectType::kHasNoSideEffect);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f2"),
|
|
templ2->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f2()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.ToLocalChecked();
|
|
v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("new f2()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.ToLocalChecked();
|
|
}
|
|
|
|
TEST(FunctionTemplateNewHasNoSideEffect) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext context;
|
|
|
|
// Function template with call handler.
|
|
Local<v8::FunctionTemplate> templ =
|
|
v8::FunctionTemplate::New(isolate, EmptyHandler);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f"),
|
|
templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("new f()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
|
|
// Side-effect-free version.
|
|
Local<v8::FunctionTemplate> templ2 = v8::FunctionTemplate::New(
|
|
isolate, EmptyHandler, v8::Local<Value>(), v8::Local<v8::Signature>(), 0,
|
|
v8::ConstructorBehavior::kAllow, v8::SideEffectType::kHasNoSideEffect);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f2"),
|
|
templ2->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f2()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.ToLocalChecked();
|
|
v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("new f2()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.ToLocalChecked();
|
|
}
|
|
|
|
TEST(FunctionTemplateNewWithCacheHasNoSideEffect) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext context;
|
|
v8::Local<v8::Private> priv =
|
|
v8::Private::ForApi(isolate, v8_str("Foo#draft"));
|
|
|
|
// Function template with call handler.
|
|
Local<v8::FunctionTemplate> templ =
|
|
v8::FunctionTemplate::NewWithCache(isolate, EmptyHandler, priv);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f"),
|
|
templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("new f()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
|
|
// Side-effect-free version.
|
|
Local<v8::FunctionTemplate> templ2 = v8::FunctionTemplate::NewWithCache(
|
|
isolate, EmptyHandler, priv, v8::Local<Value>(),
|
|
v8::Local<v8::Signature>(), 0, v8::SideEffectType::kHasNoSideEffect);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f2"),
|
|
templ2->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f2()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.ToLocalChecked();
|
|
v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("new f2()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.ToLocalChecked();
|
|
}
|
|
|
|
TEST(FunctionNewHasNoSideEffect) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext context;
|
|
|
|
// Function with side-effect.
|
|
Local<Function> func =
|
|
Function::New(context.local(), EmptyHandler).ToLocalChecked();
|
|
CHECK(context->Global()->Set(context.local(), v8_str("f"), func).FromJust());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("new f()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
|
|
// Side-effect-free version.
|
|
Local<Function> func2 =
|
|
Function::New(context.local(), EmptyHandler, Local<Value>(), 0,
|
|
v8::ConstructorBehavior::kAllow,
|
|
v8::SideEffectType::kHasNoSideEffect)
|
|
.ToLocalChecked();
|
|
CHECK(
|
|
context->Global()->Set(context.local(), v8_str("f2"), func2).FromJust());
|
|
v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f2()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.ToLocalChecked();
|
|
v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("new f2()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.ToLocalChecked();
|
|
}
|
|
|
|
// These handlers instantiate a function the embedder considers safe in some
|
|
// cases (e.g. "building object wrappers"), but those functions themselves were
|
|
// not explicitly marked as side-effect-free.
|
|
static void DefaultConstructHandler(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
v8::Context::Scope context_scope(context);
|
|
v8::MaybeLocal<v8::Object> instance = Function::New(context, EmptyHandler)
|
|
.ToLocalChecked()
|
|
->NewInstance(context, 0, nullptr);
|
|
USE(instance);
|
|
}
|
|
|
|
static void NoSideEffectConstructHandler(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
v8::Context::Scope context_scope(context);
|
|
v8::MaybeLocal<v8::Object> instance =
|
|
Function::New(context, EmptyHandler)
|
|
.ToLocalChecked()
|
|
->NewInstanceWithSideEffectType(context, 0, nullptr,
|
|
v8::SideEffectType::kHasNoSideEffect);
|
|
USE(instance);
|
|
}
|
|
|
|
static void NoSideEffectAndSideEffectConstructHandler(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
v8::Context::Scope context_scope(context);
|
|
// Constructs an instance in a side-effect-free way, followed by another with
|
|
// side effects.
|
|
v8::MaybeLocal<v8::Object> instance =
|
|
Function::New(context, EmptyHandler)
|
|
.ToLocalChecked()
|
|
->NewInstanceWithSideEffectType(context, 0, nullptr,
|
|
v8::SideEffectType::kHasNoSideEffect);
|
|
v8::MaybeLocal<v8::Object> instance2 = Function::New(context, EmptyHandler)
|
|
.ToLocalChecked()
|
|
->NewInstance(context, 0, nullptr);
|
|
USE(instance);
|
|
USE(instance2);
|
|
}
|
|
|
|
TEST(FunctionNewInstanceHasNoSideEffect) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext context;
|
|
|
|
// A allowlisted function that creates a new object with both side-effect
|
|
// free/full instantiations. Should throw.
|
|
Local<Function> func0 =
|
|
Function::New(context.local(), NoSideEffectAndSideEffectConstructHandler,
|
|
Local<Value>(), 0, v8::ConstructorBehavior::kAllow,
|
|
v8::SideEffectType::kHasNoSideEffect)
|
|
.ToLocalChecked();
|
|
CHECK(context->Global()->Set(context.local(), v8_str("f"), func0).FromJust());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
|
|
// A allowlisted function that creates a new object. Should throw.
|
|
Local<Function> func =
|
|
Function::New(context.local(), DefaultConstructHandler, Local<Value>(), 0,
|
|
v8::ConstructorBehavior::kAllow,
|
|
v8::SideEffectType::kHasNoSideEffect)
|
|
.ToLocalChecked();
|
|
CHECK(context->Global()->Set(context.local(), v8_str("f"), func).FromJust());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
|
|
// A allowlisted function that creates a new object with explicit intent to
|
|
// have no side-effects (e.g. building an "object wrapper"). Should not throw.
|
|
Local<Function> func2 =
|
|
Function::New(context.local(), NoSideEffectConstructHandler,
|
|
Local<Value>(), 0, v8::ConstructorBehavior::kAllow,
|
|
v8::SideEffectType::kHasNoSideEffect)
|
|
.ToLocalChecked();
|
|
CHECK(
|
|
context->Global()->Set(context.local(), v8_str("f2"), func2).FromJust());
|
|
v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f2()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.ToLocalChecked();
|
|
|
|
// Check that side effect skipping did not leak outside to future evaluations.
|
|
Local<Function> func3 =
|
|
Function::New(context.local(), EmptyHandler).ToLocalChecked();
|
|
CHECK(
|
|
context->Global()->Set(context.local(), v8_str("f3"), func3).FromJust());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("f3()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
|
|
// Check that using side effect free NewInstance works in normal evaluation
|
|
// (without throwOnSideEffect).
|
|
v8::debug::EvaluateGlobal(isolate, v8_str("f2()"),
|
|
v8::debug::EvaluateGlobalMode::kDefault)
|
|
.ToLocalChecked();
|
|
}
|
|
|
|
TEST(CallHandlerAsFunctionHasNoSideEffectNotSupported) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext context;
|
|
|
|
// Object template with call as function handler.
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetCallAsFunctionHandler(EmptyHandler);
|
|
Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
|
|
CHECK(context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("obj()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
|
|
// Side-effect-free version is not supported.
|
|
i::FunctionTemplateInfo cons = i::FunctionTemplateInfo::cast(
|
|
v8::Utils::OpenHandle(*templ)->constructor());
|
|
i::Heap* heap = reinterpret_cast<i::Isolate*>(isolate)->heap();
|
|
i::CallHandlerInfo handler_info =
|
|
i::CallHandlerInfo::cast(cons.GetInstanceCallHandler());
|
|
CHECK(!handler_info.IsSideEffectFreeCallHandlerInfo());
|
|
handler_info.set_map(
|
|
i::ReadOnlyRoots(heap).side_effect_free_call_handler_info_map());
|
|
CHECK(v8::debug::EvaluateGlobal(
|
|
isolate, v8_str("obj()"),
|
|
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
|
|
.IsEmpty());
|
|
}
|
|
|
|
static void IsConstructHandler(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
args.GetReturnValue().Set(args.IsConstructCall());
|
|
}
|
|
|
|
|
|
THREADED_TEST(IsConstructCall) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Function template with call handler.
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
templ->SetCallHandler(IsConstructHandler);
|
|
|
|
LocalContext context;
|
|
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f"),
|
|
templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
Local<Value> value = v8_compile("f()")->Run(context.local()).ToLocalChecked();
|
|
CHECK(!value->BooleanValue(isolate));
|
|
value = v8_compile("new f()")->Run(context.local()).ToLocalChecked();
|
|
CHECK(value->BooleanValue(isolate));
|
|
}
|
|
|
|
static void NewTargetHandler(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
args.GetReturnValue().Set(args.NewTarget());
|
|
}
|
|
|
|
THREADED_TEST(NewTargetHandler) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Function template with call handler.
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
templ->SetCallHandler(NewTargetHandler);
|
|
|
|
LocalContext context;
|
|
|
|
Local<Function> function =
|
|
templ->GetFunction(context.local()).ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f"), function)
|
|
.FromJust());
|
|
Local<Value> value = CompileRun("f()");
|
|
CHECK(value->IsUndefined());
|
|
value = CompileRun("new f()");
|
|
CHECK(value->IsFunction());
|
|
CHECK(value == function);
|
|
Local<Value> subclass = CompileRun("var g = class extends f { }; g");
|
|
CHECK(subclass->IsFunction());
|
|
value = CompileRun("new g()");
|
|
CHECK(value->IsFunction());
|
|
CHECK(value == subclass);
|
|
value = CompileRun("Reflect.construct(f, [], Array)");
|
|
CHECK(value->IsFunction());
|
|
CHECK(value ==
|
|
context->Global()
|
|
->Get(context.local(), v8_str("Array"))
|
|
.ToLocalChecked());
|
|
}
|
|
|
|
THREADED_TEST(ObjectProtoToString) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
templ->SetClassName(v8_str("MyClass"));
|
|
|
|
|
|
Local<String> customized_tostring = v8_str("customized toString");
|
|
|
|
// Replace Object.prototype.toString
|
|
CompileRun(R"(
|
|
Object.prototype.toString = function() {
|
|
return 'customized toString';
|
|
})");
|
|
|
|
// Normal ToString call should call replaced Object.prototype.toString
|
|
Local<v8::Object> instance = templ->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
Local<String> value = instance->ToString(context.local()).ToLocalChecked();
|
|
CHECK(value->IsString() &&
|
|
value->Equals(context.local(), customized_tostring).FromJust());
|
|
|
|
// ObjectProtoToString should not call replace toString function. It should
|
|
// not look at the class name either.
|
|
value = instance->ObjectProtoToString(context.local()).ToLocalChecked();
|
|
CHECK(value->IsString() &&
|
|
value->Equals(context.local(), v8_str("[object Object]")).FromJust());
|
|
|
|
// Check global
|
|
value =
|
|
context->Global()->ObjectProtoToString(context.local()).ToLocalChecked();
|
|
CHECK(value->IsString() &&
|
|
value->Equals(context.local(), v8_str("[object Object]")).FromJust());
|
|
|
|
// Check ordinary object
|
|
Local<Value> object = CompileRun("new Object()");
|
|
value = object.As<v8::Object>()
|
|
->ObjectProtoToString(context.local())
|
|
.ToLocalChecked();
|
|
CHECK(value->IsString() &&
|
|
value->Equals(context.local(), v8_str("[object Object]")).FromJust());
|
|
}
|
|
|
|
|
|
TEST(ObjectProtoToStringES6) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Check that ES6 semantics using @@toStringTag work.
|
|
Local<v8::Symbol> toStringTag = v8::Symbol::GetToStringTag(isolate);
|
|
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
templ->SetClassName(v8_str("MyClass"));
|
|
templ->PrototypeTemplate()->Set(
|
|
toStringTag, v8_str("MyClassToStringTag"),
|
|
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontEnum));
|
|
|
|
Local<String> customized_tostring = v8_str("customized toString");
|
|
|
|
// Replace Object.prototype.toString
|
|
CompileRun(R"(
|
|
Object.prototype.toString = function() {
|
|
return 'customized toString';
|
|
})");
|
|
|
|
// Normal ToString call should call replaced Object.prototype.toString
|
|
Local<v8::Object> instance = templ->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
Local<String> value = instance->ToString(context.local()).ToLocalChecked();
|
|
CHECK(value->IsString() &&
|
|
value->Equals(context.local(), customized_tostring).FromJust());
|
|
|
|
// ObjectProtoToString should not call replace toString function. Instead it
|
|
// should look at the @@toStringTag property.
|
|
value = instance->ObjectProtoToString(context.local()).ToLocalChecked();
|
|
CHECK(value->IsString() &&
|
|
value->Equals(context.local(), v8_str("[object MyClassToStringTag]"))
|
|
.FromJust());
|
|
|
|
Local<Value> object;
|
|
|
|
#define TEST_TOSTRINGTAG(type, tag, expected) \
|
|
do { \
|
|
object = CompileRun("new " #type "()"); \
|
|
CHECK(object.As<v8::Object>() \
|
|
->Set(context.local(), toStringTag, v8_str(#tag)) \
|
|
.FromJust()); \
|
|
value = object.As<v8::Object>() \
|
|
->ObjectProtoToString(context.local()) \
|
|
.ToLocalChecked(); \
|
|
CHECK(value->IsString() && \
|
|
value->Equals(context.local(), v8_str("[object " #expected "]")) \
|
|
.FromJust()); \
|
|
} while (false)
|
|
|
|
TEST_TOSTRINGTAG(Array, Object, Object);
|
|
TEST_TOSTRINGTAG(Object, Arguments, Arguments);
|
|
TEST_TOSTRINGTAG(Object, Array, Array);
|
|
TEST_TOSTRINGTAG(Object, Boolean, Boolean);
|
|
TEST_TOSTRINGTAG(Object, Date, Date);
|
|
TEST_TOSTRINGTAG(Object, Error, Error);
|
|
TEST_TOSTRINGTAG(Object, Function, Function);
|
|
TEST_TOSTRINGTAG(Object, Number, Number);
|
|
TEST_TOSTRINGTAG(Object, RegExp, RegExp);
|
|
TEST_TOSTRINGTAG(Object, String, String);
|
|
TEST_TOSTRINGTAG(Object, Foo, Foo);
|
|
|
|
#undef TEST_TOSTRINGTAG
|
|
|
|
Local<v8::RegExp> valueRegExp =
|
|
v8::RegExp::New(context.local(), v8_str("^$"), v8::RegExp::kNone)
|
|
.ToLocalChecked();
|
|
Local<Value> valueNumber = v8_num(123);
|
|
Local<v8::Symbol> valueSymbol = v8_symbol("TestSymbol");
|
|
Local<v8::Function> valueFunction =
|
|
CompileRun("(function fn() {})").As<v8::Function>();
|
|
Local<v8::Object> valueObject = v8::Object::New(isolate);
|
|
Local<v8::Primitive> valueNull = v8::Null(isolate);
|
|
Local<v8::Primitive> valueUndef = v8::Undefined(isolate);
|
|
|
|
#define TEST_TOSTRINGTAG(type, tagValue, expected) \
|
|
do { \
|
|
object = CompileRun("new " #type "()"); \
|
|
CHECK(object.As<v8::Object>() \
|
|
->Set(context.local(), toStringTag, tagValue) \
|
|
.FromJust()); \
|
|
value = object.As<v8::Object>() \
|
|
->ObjectProtoToString(context.local()) \
|
|
.ToLocalChecked(); \
|
|
CHECK(value->IsString() && \
|
|
value->Equals(context.local(), v8_str("[object " #expected "]")) \
|
|
.FromJust()); \
|
|
} while (false)
|
|
|
|
#define TEST_TOSTRINGTAG_TYPES(tagValue) \
|
|
TEST_TOSTRINGTAG(Array, tagValue, Array); \
|
|
TEST_TOSTRINGTAG(Object, tagValue, Object); \
|
|
TEST_TOSTRINGTAG(Function, tagValue, Function); \
|
|
TEST_TOSTRINGTAG(Date, tagValue, Date); \
|
|
TEST_TOSTRINGTAG(RegExp, tagValue, RegExp); \
|
|
TEST_TOSTRINGTAG(Error, tagValue, Error); \
|
|
|
|
// Test non-String-valued @@toStringTag
|
|
TEST_TOSTRINGTAG_TYPES(valueRegExp);
|
|
TEST_TOSTRINGTAG_TYPES(valueNumber);
|
|
TEST_TOSTRINGTAG_TYPES(valueSymbol);
|
|
TEST_TOSTRINGTAG_TYPES(valueFunction);
|
|
TEST_TOSTRINGTAG_TYPES(valueObject);
|
|
TEST_TOSTRINGTAG_TYPES(valueNull);
|
|
TEST_TOSTRINGTAG_TYPES(valueUndef);
|
|
|
|
#undef TEST_TOSTRINGTAG
|
|
#undef TEST_TOSTRINGTAG_TYPES
|
|
|
|
// @@toStringTag getter throws
|
|
Local<Value> obj = v8::Object::New(isolate);
|
|
obj.As<v8::Object>()
|
|
->SetAccessor(context.local(), toStringTag, ThrowingSymbolAccessorGetter)
|
|
.FromJust();
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
CHECK(obj.As<v8::Object>()->ObjectProtoToString(context.local()).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
// @@toStringTag getter does not throw
|
|
obj = v8::Object::New(isolate);
|
|
obj.As<v8::Object>()
|
|
->SetAccessor(context.local(), toStringTag,
|
|
SymbolAccessorGetterReturnsDefault, nullptr, v8_str("Test"))
|
|
.FromJust();
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
value = obj.As<v8::Object>()
|
|
->ObjectProtoToString(context.local())
|
|
.ToLocalChecked();
|
|
CHECK(value->IsString() &&
|
|
value->Equals(context.local(), v8_str("[object Test]")).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
// JS @@toStringTag value
|
|
obj = CompileRun("obj = {}; obj[Symbol.toStringTag] = 'Test'; obj");
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
value = obj.As<v8::Object>()
|
|
->ObjectProtoToString(context.local())
|
|
.ToLocalChecked();
|
|
CHECK(value->IsString() &&
|
|
value->Equals(context.local(), v8_str("[object Test]")).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
// JS @@toStringTag getter throws
|
|
obj = CompileRun(
|
|
"obj = {}; Object.defineProperty(obj, Symbol.toStringTag, {"
|
|
" get: function() { throw 'Test'; }"
|
|
"}); obj");
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
CHECK(obj.As<v8::Object>()->ObjectProtoToString(context.local()).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
// JS @@toStringTag getter does not throw
|
|
obj = CompileRun(
|
|
"obj = {}; Object.defineProperty(obj, Symbol.toStringTag, {"
|
|
" get: function() { return 'Test'; }"
|
|
"}); obj");
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
value = obj.As<v8::Object>()
|
|
->ObjectProtoToString(context.local())
|
|
.ToLocalChecked();
|
|
CHECK(value->IsString() &&
|
|
value->Equals(context.local(), v8_str("[object Test]")).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(ObjectGetConstructorName) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
LocalContext context;
|
|
v8::HandleScope scope(isolate);
|
|
v8_compile(
|
|
"function Parent() {};"
|
|
"function Child() {};"
|
|
"Child.prototype = new Parent();"
|
|
"Child.prototype.constructor = Child;"
|
|
"var outer = { inner: (0, function() { }) };"
|
|
"var p = new Parent();"
|
|
"var c = new Child();"
|
|
"var x = new outer.inner();"
|
|
"var proto = Child.prototype;")
|
|
->Run(context.local())
|
|
.ToLocalChecked();
|
|
|
|
Local<v8::Value> p =
|
|
context->Global()->Get(context.local(), v8_str("p")).ToLocalChecked();
|
|
CHECK(p->IsObject() &&
|
|
p->ToObject(context.local())
|
|
.ToLocalChecked()
|
|
->GetConstructorName()
|
|
->Equals(context.local(), v8_str("Parent"))
|
|
.FromJust());
|
|
|
|
Local<v8::Value> c =
|
|
context->Global()->Get(context.local(), v8_str("c")).ToLocalChecked();
|
|
CHECK(c->IsObject() &&
|
|
c->ToObject(context.local())
|
|
.ToLocalChecked()
|
|
->GetConstructorName()
|
|
->Equals(context.local(), v8_str("Child"))
|
|
.FromJust());
|
|
|
|
Local<v8::Value> x =
|
|
context->Global()->Get(context.local(), v8_str("x")).ToLocalChecked();
|
|
CHECK(x->IsObject() &&
|
|
x->ToObject(context.local())
|
|
.ToLocalChecked()
|
|
->GetConstructorName()
|
|
->Equals(context.local(), v8_str("outer.inner"))
|
|
.FromJust());
|
|
|
|
Local<v8::Value> child_prototype =
|
|
context->Global()->Get(context.local(), v8_str("proto")).ToLocalChecked();
|
|
CHECK(child_prototype->IsObject() &&
|
|
child_prototype->ToObject(context.local())
|
|
.ToLocalChecked()
|
|
->GetConstructorName()
|
|
->Equals(context.local(), v8_str("Parent"))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(SubclassGetConstructorName) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
LocalContext context;
|
|
v8::HandleScope scope(isolate);
|
|
v8_compile(
|
|
"\"use strict\";"
|
|
"class Parent {}"
|
|
"class Child extends Parent {}"
|
|
"var p = new Parent();"
|
|
"var c = new Child();")
|
|
->Run(context.local())
|
|
.ToLocalChecked();
|
|
|
|
Local<v8::Value> p =
|
|
context->Global()->Get(context.local(), v8_str("p")).ToLocalChecked();
|
|
CHECK(p->IsObject() &&
|
|
p->ToObject(context.local())
|
|
.ToLocalChecked()
|
|
->GetConstructorName()
|
|
->Equals(context.local(), v8_str("Parent"))
|
|
.FromJust());
|
|
|
|
Local<v8::Value> c =
|
|
context->Global()->Get(context.local(), v8_str("c")).ToLocalChecked();
|
|
CHECK(c->IsObject() &&
|
|
c->ToObject(context.local())
|
|
.ToLocalChecked()
|
|
->GetConstructorName()
|
|
->Equals(context.local(), v8_str("Child"))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
bool ApiTestFuzzer::fuzzing_ = false;
|
|
v8::base::Semaphore ApiTestFuzzer::all_tests_done_(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::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++) {
|
|
CHECK(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::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 != nullptr) fuzzer->Join();
|
|
}
|
|
}
|
|
|
|
void ApiTestFuzzer::CallTest() {
|
|
v8::Isolate::Scope scope(CcTest::isolate());
|
|
if (kLogThreading)
|
|
printf("Start test %s #%d\n",
|
|
RegisterThreadedTest::nth(test_number_)->name(), test_number_);
|
|
CallTestNumber(test_number_);
|
|
if (kLogThreading)
|
|
printf("End test %s #%d\n", RegisterThreadedTest::nth(test_number_)->name(),
|
|
test_number_);
|
|
}
|
|
|
|
#define THREADING_TEST(INDEX, NAME) \
|
|
TEST(Threading##INDEX) { \
|
|
ApiTestFuzzer::SetUp(ApiTestFuzzer::NAME); \
|
|
ApiTestFuzzer::RunAllTests(); \
|
|
ApiTestFuzzer::TearDown(); \
|
|
}
|
|
|
|
THREADING_TEST(1, FIRST_PART)
|
|
THREADING_TEST(2, SECOND_PART)
|
|
THREADING_TEST(3, THIRD_PART)
|
|
THREADING_TEST(4, FOURTH_PART)
|
|
THREADING_TEST(5, FIFTH_PART)
|
|
THREADING_TEST(6, SIXTH_PART)
|
|
THREADING_TEST(7, SEVENTH_PART)
|
|
THREADING_TEST(8, EIGHTH_PART)
|
|
|
|
#undef THREADING_TEST
|
|
|
|
static void ThrowInJS(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::Isolate* isolate = args.GetIsolate();
|
|
CHECK(v8::Locker::IsLocked(isolate));
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Unlocker unlocker(isolate);
|
|
const char* code = "throw 7;";
|
|
{
|
|
v8::Locker nested_locker(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<Value> exception;
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
v8::Local<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(isolate, try_catch.Exception());
|
|
}
|
|
args.GetIsolate()->ThrowException(exception);
|
|
}
|
|
}
|
|
|
|
|
|
static void ThrowInJSNoCatch(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
CHECK(v8::Locker::IsLocked(CcTest::isolate()));
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Unlocker unlocker(CcTest::isolate());
|
|
const char* code = "throw 7;";
|
|
{
|
|
v8::Locker nested_locker(CcTest::isolate());
|
|
v8::HandleScope scope(args.GetIsolate());
|
|
v8::Local<Value> value = CompileRun(code);
|
|
CHECK(value.IsEmpty());
|
|
args.GetReturnValue().Set(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::Isolate* isolate = CcTest::isolate();
|
|
v8::Locker locker(isolate);
|
|
CHECK(v8::Locker::IsLocked(isolate));
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(isolate, ThrowInJS);
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("throw_in_js"), fun).FromJust());
|
|
Local<Script> script = v8_compile("(function () {"
|
|
" try {"
|
|
" throw_in_js();"
|
|
" return 42;"
|
|
" } catch (e) {"
|
|
" return e * 13;"
|
|
" }"
|
|
"})();");
|
|
CHECK_EQ(91, script->Run(env.local())
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
// 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::isolate());
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(env->GetIsolate(), ThrowInJSNoCatch);
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("throw_in_js"), fun).FromJust());
|
|
Local<Script> script = v8_compile("(function () {"
|
|
" try {"
|
|
" throw_in_js();"
|
|
" return 42;"
|
|
" } catch (e) {"
|
|
" return e * 13;"
|
|
" }"
|
|
"})();");
|
|
CHECK_EQ(91, script->Run(env.local())
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(RecursiveLocking) {
|
|
v8::Locker locker(CcTest::isolate());
|
|
{
|
|
v8::Locker locker2(CcTest::isolate());
|
|
CHECK(v8::Locker::IsLocked(CcTest::isolate()));
|
|
}
|
|
}
|
|
|
|
|
|
static void UnlockForAMoment(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Unlocker unlocker(CcTest::isolate());
|
|
}
|
|
|
|
|
|
THREADED_TEST(LockUnlockLock) {
|
|
{
|
|
v8::Locker locker(CcTest::isolate());
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
LocalContext env;
|
|
Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(CcTest::isolate(), UnlockForAMoment);
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("unlock_for_a_moment"), fun)
|
|
.FromJust());
|
|
Local<Script> script = v8_compile("(function () {"
|
|
" unlock_for_a_moment();"
|
|
" return 42;"
|
|
"})();");
|
|
CHECK_EQ(42, script->Run(env.local())
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
}
|
|
{
|
|
v8::Locker locker(CcTest::isolate());
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
LocalContext env;
|
|
Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(CcTest::isolate(), UnlockForAMoment);
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("unlock_for_a_moment"), fun)
|
|
.FromJust());
|
|
Local<Script> script = v8_compile("(function () {"
|
|
" unlock_for_a_moment();"
|
|
" return 42;"
|
|
"})();");
|
|
CHECK_EQ(42, script->Run(env.local())
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
static int GetGlobalObjectsCount() {
|
|
int count = 0;
|
|
i::HeapObjectIterator it(CcTest::heap());
|
|
for (i::HeapObject object = it.Next(); !object.is_null();
|
|
object = it.Next()) {
|
|
if (object.IsJSGlobalObject()) {
|
|
i::JSGlobalObject g = i::JSGlobalObject::cast(object);
|
|
// Skip dummy global object.
|
|
if (g.global_dictionary(v8::kAcquireLoad).NumberOfElements() != 0) {
|
|
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.
|
|
CcTest::CollectAllGarbage();
|
|
CcTest::CollectAllGarbage();
|
|
int count = GetGlobalObjectsCount();
|
|
CHECK_EQ(expected, count);
|
|
}
|
|
|
|
|
|
TEST(DontLeakGlobalObjects) {
|
|
// Regression test for issues 1139850 and 1174891.
|
|
i::FLAG_expose_gc = true;
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
{ v8::HandleScope scope(CcTest::isolate());
|
|
LocalContext context;
|
|
}
|
|
CcTest::isolate()->ContextDisposedNotification();
|
|
CheckSurvivingGlobalObjectsCount(0);
|
|
|
|
{ v8::HandleScope scope(CcTest::isolate());
|
|
LocalContext context;
|
|
v8_compile("Date")->Run(context.local()).ToLocalChecked();
|
|
}
|
|
CcTest::isolate()->ContextDisposedNotification();
|
|
CheckSurvivingGlobalObjectsCount(0);
|
|
|
|
{ v8::HandleScope scope(CcTest::isolate());
|
|
LocalContext context;
|
|
v8_compile("/aaa/")->Run(context.local()).ToLocalChecked();
|
|
}
|
|
CcTest::isolate()->ContextDisposedNotification();
|
|
CheckSurvivingGlobalObjectsCount(0);
|
|
|
|
{ v8::HandleScope scope(CcTest::isolate());
|
|
const char* extension_list[] = { "v8/gc" };
|
|
v8::ExtensionConfiguration extensions(1, extension_list);
|
|
LocalContext context(&extensions);
|
|
v8_compile("gc();")->Run(context.local()).ToLocalChecked();
|
|
}
|
|
CcTest::isolate()->ContextDisposedNotification();
|
|
CheckSurvivingGlobalObjectsCount(0);
|
|
}
|
|
}
|
|
|
|
|
|
TEST(CopyablePersistent) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
i::GlobalHandles* globals =
|
|
reinterpret_cast<i::Isolate*>(isolate)->global_handles();
|
|
size_t initial_handles = globals->handles_count();
|
|
using CopyableObject =
|
|
v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object>>;
|
|
{
|
|
CopyableObject handle1;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
handle1.Reset(isolate, v8::Object::New(isolate));
|
|
}
|
|
CHECK_EQ(initial_handles + 1, globals->handles_count());
|
|
CopyableObject handle2;
|
|
handle2 = handle1;
|
|
CHECK(handle1 == handle2);
|
|
CHECK_EQ(initial_handles + 2, globals->handles_count());
|
|
CopyableObject handle3(handle2);
|
|
CHECK(handle1 == handle3);
|
|
CHECK_EQ(initial_handles + 3, globals->handles_count());
|
|
}
|
|
// Verify autodispose
|
|
CHECK_EQ(initial_handles, globals->handles_count());
|
|
}
|
|
|
|
|
|
static void WeakApiCallback(
|
|
const v8::WeakCallbackInfo<Persistent<v8::Object>>& data) {
|
|
data.GetParameter()->Reset();
|
|
delete data.GetParameter();
|
|
}
|
|
|
|
|
|
TEST(WeakCallbackApi) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
i::GlobalHandles* globals =
|
|
reinterpret_cast<i::Isolate*>(isolate)->global_handles();
|
|
size_t initial_handles = globals->handles_count();
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
|
CHECK(
|
|
obj->Set(context.local(), v8_str("key"), v8::Integer::New(isolate, 231))
|
|
.FromJust());
|
|
v8::Persistent<v8::Object>* handle =
|
|
new v8::Persistent<v8::Object>(isolate, obj);
|
|
handle->SetWeak<v8::Persistent<v8::Object>>(
|
|
handle, WeakApiCallback, v8::WeakCallbackType::kParameter);
|
|
}
|
|
CcTest::PreciseCollectAllGarbage();
|
|
// Verify disposed.
|
|
CHECK_EQ(initial_handles, globals->handles_count());
|
|
}
|
|
|
|
|
|
v8::Persistent<v8::Object> some_object;
|
|
v8::Persistent<v8::Object> bad_handle;
|
|
|
|
|
|
void NewPersistentHandleCallback2(
|
|
const v8::WeakCallbackInfo<v8::Persistent<v8::Object>>& data) {
|
|
v8::HandleScope scope(data.GetIsolate());
|
|
bad_handle.Reset(data.GetIsolate(), some_object);
|
|
}
|
|
|
|
|
|
void NewPersistentHandleCallback1(
|
|
const v8::WeakCallbackInfo<v8::Persistent<v8::Object>>& data) {
|
|
data.GetParameter()->Reset();
|
|
data.SetSecondPassCallback(NewPersistentHandleCallback2);
|
|
}
|
|
|
|
|
|
THREADED_TEST(NewPersistentHandleFromWeakCallback) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
|
|
v8::Persistent<v8::Object> handle1, handle2;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
some_object.Reset(isolate, v8::Object::New(isolate));
|
|
handle1.Reset(isolate, v8::Object::New(isolate));
|
|
handle2.Reset(isolate, v8::Object::New(isolate));
|
|
}
|
|
// 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.SetWeak(&handle1, NewPersistentHandleCallback1,
|
|
v8::WeakCallbackType::kParameter);
|
|
handle2.Reset();
|
|
CcTest::CollectAllGarbage();
|
|
}
|
|
|
|
|
|
v8::Persistent<v8::Object> to_be_disposed;
|
|
|
|
|
|
void DisposeAndForceGcCallback2(
|
|
const v8::WeakCallbackInfo<v8::Persistent<v8::Object>>& data) {
|
|
to_be_disposed.Reset();
|
|
CcTest::CollectAllGarbage();
|
|
}
|
|
|
|
|
|
void DisposeAndForceGcCallback1(
|
|
const v8::WeakCallbackInfo<v8::Persistent<v8::Object>>& data) {
|
|
data.GetParameter()->Reset();
|
|
data.SetSecondPassCallback(DisposeAndForceGcCallback2);
|
|
}
|
|
|
|
|
|
THREADED_TEST(DoNotUseDeletedNodesInSecondLevelGc) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
|
|
v8::Persistent<v8::Object> handle1, handle2;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
handle1.Reset(isolate, v8::Object::New(isolate));
|
|
handle2.Reset(isolate, v8::Object::New(isolate));
|
|
}
|
|
handle1.SetWeak(&handle1, DisposeAndForceGcCallback1,
|
|
v8::WeakCallbackType::kParameter);
|
|
to_be_disposed.Reset(isolate, handle2);
|
|
CcTest::CollectAllGarbage();
|
|
}
|
|
|
|
void DisposingCallback(
|
|
const v8::WeakCallbackInfo<v8::Persistent<v8::Object>>& data) {
|
|
data.GetParameter()->Reset();
|
|
}
|
|
|
|
void HandleCreatingCallback2(
|
|
const v8::WeakCallbackInfo<v8::Persistent<v8::Object>>& data) {
|
|
v8::HandleScope scope(data.GetIsolate());
|
|
v8::Global<v8::Object>(data.GetIsolate(), v8::Object::New(data.GetIsolate()));
|
|
}
|
|
|
|
|
|
void HandleCreatingCallback1(
|
|
const v8::WeakCallbackInfo<v8::Persistent<v8::Object>>& data) {
|
|
data.GetParameter()->Reset();
|
|
data.SetSecondPassCallback(HandleCreatingCallback2);
|
|
}
|
|
|
|
|
|
THREADED_TEST(NoGlobalHandlesOrphaningDueToWeakCallback) {
|
|
v8::Locker locker(CcTest::isolate());
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
|
|
v8::Persistent<v8::Object> handle1, handle2, handle3;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
handle3.Reset(isolate, v8::Object::New(isolate));
|
|
handle2.Reset(isolate, v8::Object::New(isolate));
|
|
handle1.Reset(isolate, v8::Object::New(isolate));
|
|
}
|
|
handle2.SetWeak(&handle2, DisposingCallback,
|
|
v8::WeakCallbackType::kParameter);
|
|
handle3.SetWeak(&handle3, HandleCreatingCallback1,
|
|
v8::WeakCallbackType::kParameter);
|
|
CcTest::CollectAllGarbage();
|
|
EmptyMessageQueues(isolate);
|
|
}
|
|
|
|
THREADED_TEST(CheckForCrossContextObjectLiterals) {
|
|
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(CcTest::isolate());
|
|
LocalContext context;
|
|
CompileRun(source);
|
|
}
|
|
{ v8::HandleScope scope(CcTest::isolate());
|
|
LocalContext context;
|
|
CompileRun(source);
|
|
}
|
|
}
|
|
}
|
|
|
|
static v8::Local<Value> NestedScope(v8::Local<Context> env) {
|
|
v8::EscapableHandleScope inner(env->GetIsolate());
|
|
env->Enter();
|
|
v8::Local<Value> three = v8_num(3);
|
|
v8::Local<Value> value = inner.Escape(three);
|
|
env->Exit();
|
|
return value;
|
|
}
|
|
|
|
|
|
THREADED_TEST(NestedHandleScopeAndContexts) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope outer(isolate);
|
|
v8::Local<Context> env = Context::New(isolate);
|
|
env->Enter();
|
|
v8::Local<Value> value = NestedScope(env);
|
|
v8::Local<String> str(value->ToString(env).ToLocalChecked());
|
|
CHECK(!str.IsEmpty());
|
|
env->Exit();
|
|
}
|
|
|
|
static v8::base::HashMap* code_map = nullptr;
|
|
static v8::base::HashMap* jitcode_line_info = nullptr;
|
|
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>" or Function:<type><function_name>,
|
|
// where the type is one of "*", "~" or "".
|
|
static const char* kPreamble = "Function:";
|
|
if (i::FLAG_lazy &&
|
|
event->code_type != v8::JitCodeEvent::CodeType::WASM_CODE) {
|
|
kPreamble = "LazyCompile:";
|
|
}
|
|
static size_t kPreambleLen = strlen(kPreamble);
|
|
|
|
if (event->name.len < kPreambleLen ||
|
|
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 > 1 && (*tail == '*' || *tail == '~')) {
|
|
--tail_len;
|
|
++tail;
|
|
}
|
|
|
|
// Check for tails like 'bar :1'.
|
|
if (tail_len > expected_len + 2 &&
|
|
tail[expected_len] == ' ' &&
|
|
tail[expected_len + 1] == ':' &&
|
|
tail[expected_len + 2] &&
|
|
!strncmp(tail, expected, expected_len)) {
|
|
return true;
|
|
}
|
|
|
|
if (tail_len != expected_len)
|
|
return false;
|
|
|
|
return strncmp(tail, expected, expected_len) == 0;
|
|
}
|
|
|
|
static void event_handler(const v8::JitCodeEvent* event) {
|
|
CHECK_NOT_NULL(event);
|
|
CHECK_NOT_NULL(code_map);
|
|
CHECK_NOT_NULL(jitcode_line_info);
|
|
|
|
class DummyJitCodeLineInfo {
|
|
};
|
|
|
|
switch (event->type) {
|
|
case v8::JitCodeEvent::CODE_ADDED: {
|
|
CHECK_NOT_NULL(event->code_start);
|
|
CHECK_NE(0, static_cast<int>(event->code_len));
|
|
CHECK_NOT_NULL(event->name.str);
|
|
v8::base::HashMap::Entry* entry = code_map->LookupOrInsert(
|
|
event->code_start, i::ComputePointerHash(event->code_start));
|
|
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.
|
|
v8::base::HashMap::Entry* entry =
|
|
code_map->Lookup(event->code_start, hash);
|
|
if (entry != nullptr) {
|
|
++move_events;
|
|
|
|
CHECK_EQ(reinterpret_cast<void*>(event->code_len), entry->value);
|
|
code_map->Remove(event->code_start, hash);
|
|
|
|
entry = code_map->LookupOrInsert(
|
|
event->new_code_start,
|
|
i::ComputePointerHash(event->new_code_start));
|
|
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.
|
|
UNREACHABLE();
|
|
|
|
// For CODE_START_LINE_INFO_RECORDING event, we will create one
|
|
// DummyJitCodeLineInfo data structure pointed by event->user_dat. We
|
|
// record it in jitcode_line_info.
|
|
case v8::JitCodeEvent::CODE_START_LINE_INFO_RECORDING: {
|
|
DummyJitCodeLineInfo* line_info = new DummyJitCodeLineInfo();
|
|
v8::JitCodeEvent* temp_event = const_cast<v8::JitCodeEvent*>(event);
|
|
temp_event->user_data = line_info;
|
|
v8::base::HashMap::Entry* entry = jitcode_line_info->LookupOrInsert(
|
|
line_info, i::ComputePointerHash(line_info));
|
|
entry->value = reinterpret_cast<void*>(line_info);
|
|
}
|
|
break;
|
|
// For these two events, we will check whether the event->user_data
|
|
// data structure is created before during CODE_START_LINE_INFO_RECORDING
|
|
// event. And delete it in CODE_END_LINE_INFO_RECORDING event handling.
|
|
case v8::JitCodeEvent::CODE_END_LINE_INFO_RECORDING: {
|
|
CHECK_NOT_NULL(event->user_data);
|
|
uint32_t hash = i::ComputePointerHash(event->user_data);
|
|
v8::base::HashMap::Entry* entry =
|
|
jitcode_line_info->Lookup(event->user_data, hash);
|
|
CHECK_NOT_NULL(entry);
|
|
delete reinterpret_cast<DummyJitCodeLineInfo*>(event->user_data);
|
|
}
|
|
break;
|
|
|
|
case v8::JitCodeEvent::CODE_ADD_LINE_POS_INFO: {
|
|
CHECK_NOT_NULL(event->user_data);
|
|
uint32_t hash = i::ComputePointerHash(event->user_data);
|
|
v8::base::HashMap::Entry* entry =
|
|
jitcode_line_info->Lookup(event->user_data, hash);
|
|
CHECK_NOT_NULL(entry);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Impossible event.
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
UNINITIALIZED_TEST(SetJitCodeEventHandler) {
|
|
i::FLAG_stress_compaction = true;
|
|
i::FLAG_incremental_marking = false;
|
|
i::FLAG_stress_concurrent_allocation = false; // For SimulateFullSpace.
|
|
// Batch compilation can cause different owning spaces for foo and bar.
|
|
#if ENABLE_SPARKPLUG
|
|
i::FLAG_baseline_batch_compilation = false;
|
|
#endif
|
|
if (!i::FLAG_compact) return;
|
|
const char* script =
|
|
"function bar() {"
|
|
" var sum = 0;"
|
|
" for (i = 0; i < 10; ++i)"
|
|
" sum = foo(i);"
|
|
" return sum;"
|
|
"}"
|
|
"function foo(i) { return i; };"
|
|
"bar();";
|
|
|
|
// Run this test in a new isolate to make sure we don't
|
|
// have remnants of state from other code.
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
isolate->Enter();
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
i::Heap* heap = i_isolate->heap();
|
|
|
|
// Start with a clean slate.
|
|
heap->CollectAllAvailableGarbage(i::GarbageCollectionReason::kTesting);
|
|
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
v8::base::HashMap code;
|
|
code_map = &code;
|
|
|
|
v8::base::HashMap lineinfo;
|
|
jitcode_line_info = &lineinfo;
|
|
|
|
saw_bar = 0;
|
|
move_events = 0;
|
|
|
|
isolate->SetJitCodeEventHandler(v8::kJitCodeEventDefault, event_handler);
|
|
|
|
// 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(isolate);
|
|
i::AlwaysAllocateScopeForTesting always_allocate(heap);
|
|
CompileRun(script);
|
|
|
|
// Keep a strong reference to the code object in the handle scope.
|
|
i::Handle<i::JSFunction> bar(i::Handle<i::JSFunction>::cast(
|
|
v8::Utils::OpenHandle(*env->Global()
|
|
->Get(env.local(), v8_str("bar"))
|
|
.ToLocalChecked())));
|
|
i::Handle<i::JSFunction> foo(i::Handle<i::JSFunction>::cast(
|
|
v8::Utils::OpenHandle(*env->Global()
|
|
->Get(env.local(), v8_str("foo"))
|
|
.ToLocalChecked())));
|
|
|
|
i::PagedSpace* foo_owning_space = reinterpret_cast<i::PagedSpace*>(
|
|
i::Page::FromHeapObject(foo->abstract_code(i_isolate))->owner());
|
|
i::PagedSpace* bar_owning_space = reinterpret_cast<i::PagedSpace*>(
|
|
i::Page::FromHeapObject(bar->abstract_code(i_isolate))->owner());
|
|
CHECK_EQ(foo_owning_space, bar_owning_space);
|
|
i::heap::SimulateFullSpace(foo_owning_space);
|
|
|
|
// Clear the compilation cache to get more wastage.
|
|
reinterpret_cast<i::Isolate*>(isolate)->compilation_cache()->Clear();
|
|
}
|
|
|
|
// Force code movement.
|
|
heap->CollectAllAvailableGarbage(i::GarbageCollectionReason::kTesting);
|
|
|
|
isolate->SetJitCodeEventHandler(v8::kJitCodeEventDefault, nullptr);
|
|
|
|
CHECK_LE(kIterations, saw_bar);
|
|
CHECK_LT(0, move_events);
|
|
|
|
code_map = nullptr;
|
|
jitcode_line_info = nullptr;
|
|
}
|
|
|
|
isolate->Exit();
|
|
isolate->Dispose();
|
|
|
|
// Do this in a new isolate.
|
|
isolate = v8::Isolate::New(create_params);
|
|
isolate->Enter();
|
|
|
|
// Verify that we get callbacks for existing code objects when we
|
|
// request enumeration of existing code.
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env(isolate);
|
|
CompileRun(script);
|
|
|
|
// Now get code through initial iteration.
|
|
v8::base::HashMap code;
|
|
code_map = &code;
|
|
|
|
v8::base::HashMap lineinfo;
|
|
jitcode_line_info = &lineinfo;
|
|
|
|
isolate->SetJitCodeEventHandler(v8::kJitCodeEventEnumExisting,
|
|
event_handler);
|
|
isolate->SetJitCodeEventHandler(v8::kJitCodeEventDefault, nullptr);
|
|
|
|
jitcode_line_info = nullptr;
|
|
// 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_LT(0u, code.occupancy());
|
|
|
|
code_map = nullptr;
|
|
}
|
|
|
|
isolate->Exit();
|
|
isolate->Dispose();
|
|
}
|
|
|
|
#if V8_ENABLE_WEBASSEMBLY
|
|
static bool saw_wasm_main = false;
|
|
static void wasm_event_handler(const v8::JitCodeEvent* event) {
|
|
switch (event->type) {
|
|
case v8::JitCodeEvent::CODE_ADDED: {
|
|
if (FunctionNameIs("main-0-turbofan", event)) {
|
|
saw_wasm_main = true;
|
|
// Make sure main function has line info.
|
|
auto* entry = jitcode_line_info->Lookup(
|
|
event->code_start, i::ComputePointerHash(event->code_start));
|
|
CHECK_NOT_NULL(entry);
|
|
}
|
|
break;
|
|
}
|
|
case v8::JitCodeEvent::CODE_END_LINE_INFO_RECORDING: {
|
|
jitcode_line_info->LookupOrInsert(
|
|
event->code_start, i::ComputePointerHash(event->code_start));
|
|
break;
|
|
}
|
|
case v8::JitCodeEvent::CODE_ADD_LINE_POS_INFO: {
|
|
break;
|
|
}
|
|
default: {
|
|
// Ignore all other events;
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
namespace wasm {
|
|
TEST(WasmSetJitCodeEventHandler) {
|
|
v8::base::HashMap code;
|
|
code_map = &code;
|
|
|
|
v8::base::HashMap lineinfo;
|
|
jitcode_line_info = &lineinfo;
|
|
|
|
WasmRunner<int32_t, int32_t, int32_t> r(TestExecutionTier::kTurbofan);
|
|
i::Isolate* isolate = r.main_isolate();
|
|
|
|
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
|
|
v8_isolate->SetJitCodeEventHandler(v8::kJitCodeEventDefault,
|
|
wasm_event_handler);
|
|
|
|
TestSignatures sigs;
|
|
auto& f = r.NewFunction(sigs.i_i(), "f");
|
|
BUILD(f, WASM_I32_ADD(WASM_LOCAL_GET(0), WASM_LOCAL_GET(0)));
|
|
|
|
LocalContext env;
|
|
|
|
BUILD(r,
|
|
WASM_I32_ADD(WASM_LOCAL_GET(0), WASM_CALL_FUNCTION(f.function_index(),
|
|
WASM_LOCAL_GET(1))));
|
|
|
|
Handle<JSFunction> func = r.builder().WrapCode(0);
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("func"), v8::Utils::ToLocal(func))
|
|
.FromJust());
|
|
const char* script = R"(
|
|
func(1, 2);
|
|
)";
|
|
CompileRun(script);
|
|
CHECK(saw_wasm_main);
|
|
saw_wasm_main = false;
|
|
}
|
|
} // namespace wasm
|
|
} // namespace internal
|
|
} // namespace v8
|
|
#endif // V8_ENABLE_WEBASSEMBLY
|
|
|
|
TEST(ExternalAllocatedMemory) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope outer(isolate);
|
|
v8::Local<Context> env(Context::New(isolate));
|
|
CHECK(!env.IsEmpty());
|
|
const int64_t kSize = 1024*1024;
|
|
int64_t baseline = isolate->AdjustAmountOfExternalAllocatedMemory(0);
|
|
CHECK_EQ(baseline + kSize,
|
|
isolate->AdjustAmountOfExternalAllocatedMemory(kSize));
|
|
CHECK_EQ(baseline,
|
|
isolate->AdjustAmountOfExternalAllocatedMemory(-kSize));
|
|
const int64_t kTriggerGCSize =
|
|
CcTest::i_isolate()->heap()->external_memory_hard_limit() + 1;
|
|
CHECK_EQ(baseline + kTriggerGCSize,
|
|
isolate->AdjustAmountOfExternalAllocatedMemory(kTriggerGCSize));
|
|
CHECK_EQ(baseline,
|
|
isolate->AdjustAmountOfExternalAllocatedMemory(-kTriggerGCSize));
|
|
}
|
|
|
|
|
|
TEST(Regress51719) {
|
|
i::FLAG_incremental_marking = false;
|
|
CcTest::InitializeVM();
|
|
|
|
const int64_t kTriggerGCSize =
|
|
CcTest::i_isolate()->heap()->external_memory_hard_limit() + 1;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
isolate->AdjustAmountOfExternalAllocatedMemory(kTriggerGCSize);
|
|
}
|
|
|
|
// Regression test for issue 54, object templates with embedder fields
|
|
// but no accessors or interceptors did not get their embedder field
|
|
// count set on instances.
|
|
THREADED_TEST(Regress54) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope outer(isolate);
|
|
static v8::Persistent<v8::ObjectTemplate> templ;
|
|
if (templ.IsEmpty()) {
|
|
v8::EscapableHandleScope inner(isolate);
|
|
v8::Local<v8::ObjectTemplate> local = v8::ObjectTemplate::New(isolate);
|
|
local->SetInternalFieldCount(1);
|
|
templ.Reset(isolate, inner.Escape(local));
|
|
}
|
|
v8::Local<v8::Object> result =
|
|
v8::Local<v8::ObjectTemplate>::New(isolate, templ)
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
CHECK_EQ(1, result->InternalFieldCount());
|
|
}
|
|
|
|
|
|
// If part of the threaded tests, this test makes ThreadingTest fail
|
|
// on mac.
|
|
TEST(CatchStackOverflow) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
v8::Local<v8::Value> result = CompileRun(
|
|
"function f() {"
|
|
" return f();"
|
|
"}"
|
|
""
|
|
"f();");
|
|
CHECK(result.IsEmpty());
|
|
}
|
|
|
|
|
|
static void CheckTryCatchSourceInfo(v8::Local<v8::Script> script,
|
|
const char* resource_name,
|
|
int line_offset) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
v8::TryCatch try_catch(CcTest::isolate());
|
|
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
|
|
CHECK(script->Run(context).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
v8::Local<v8::Message> message = try_catch.Message();
|
|
CHECK(!message.IsEmpty());
|
|
CHECK_EQ(10 + line_offset, message->GetLineNumber(context).FromJust());
|
|
CHECK_EQ(91, message->GetStartPosition());
|
|
CHECK_EQ(92, message->GetEndPosition());
|
|
CHECK_EQ(2, message->GetStartColumn(context).FromJust());
|
|
CHECK_EQ(3, message->GetEndColumn(context).FromJust());
|
|
v8::String::Utf8Value line(CcTest::isolate(),
|
|
message->GetSourceLine(context).ToLocalChecked());
|
|
CHECK_EQ(0, strcmp(" throw 'nirk';", *line));
|
|
v8::String::Utf8Value name(CcTest::isolate(),
|
|
message->GetScriptOrigin().ResourceName());
|
|
CHECK_EQ(0, strcmp(resource_name, *name));
|
|
}
|
|
|
|
|
|
THREADED_TEST(TryCatchSourceInfo) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::String> source = v8_str(
|
|
"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::Local<v8::Script> script;
|
|
resource_name = "test.js";
|
|
script = CompileWithOrigin(source, resource_name, false);
|
|
CheckTryCatchSourceInfo(script, resource_name, 0);
|
|
|
|
resource_name = "test1.js";
|
|
v8::ScriptOrigin origin1(isolate, v8_str(resource_name), 0, 0);
|
|
script =
|
|
v8::Script::Compile(context.local(), source, &origin1).ToLocalChecked();
|
|
CheckTryCatchSourceInfo(script, resource_name, 0);
|
|
|
|
resource_name = "test2.js";
|
|
v8::ScriptOrigin origin2(isolate, v8_str(resource_name), 7, 0);
|
|
script =
|
|
v8::Script::Compile(context.local(), source, &origin2).ToLocalChecked();
|
|
CheckTryCatchSourceInfo(script, resource_name, 7);
|
|
}
|
|
|
|
|
|
THREADED_TEST(TryCatchSourceInfoForEOSError) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
CHECK(v8::Script::Compile(context.local(), v8_str("!\n")).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
v8::Local<v8::Message> message = try_catch.Message();
|
|
CHECK_EQ(2, message->GetLineNumber(context.local()).FromJust());
|
|
CHECK_EQ(0, message->GetStartColumn(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(CompilationCache) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
v8::Local<v8::String> source0 = v8_str("1234");
|
|
v8::Local<v8::String> source1 = v8_str("1234");
|
|
v8::Local<v8::Script> script0 = CompileWithOrigin(source0, "test.js", false);
|
|
v8::Local<v8::Script> script1 = CompileWithOrigin(source1, "test.js", false);
|
|
v8::Local<v8::Script> script2 = v8::Script::Compile(context.local(), source0)
|
|
.ToLocalChecked(); // different origin
|
|
CHECK_EQ(1234, script0->Run(context.local())
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(1234, script1->Run(context.local())
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(1234, script2->Run(context.local())
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
static void FunctionNameCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
ApiTestFuzzer::Fuzz();
|
|
args.GetReturnValue().Set(v8_num(42));
|
|
}
|
|
|
|
|
|
THREADED_TEST(CallbackFunctionName) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> t = ObjectTemplate::New(isolate);
|
|
t->Set(isolate, "asdf",
|
|
v8::FunctionTemplate::New(isolate, FunctionNameCallback));
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
t->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
v8::Local<v8::Value> value = CompileRun("obj.asdf.name");
|
|
CHECK(value->IsString());
|
|
v8::String::Utf8Value name(isolate, value);
|
|
CHECK_EQ(0, strcmp("asdf", *name));
|
|
}
|
|
|
|
|
|
THREADED_TEST(DateAccess) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
v8::Local<v8::Value> date =
|
|
v8::Date::New(context.local(), 1224744689038.0).ToLocalChecked();
|
|
CHECK(date->IsDate());
|
|
CHECK_EQ(1224744689038.0, date.As<v8::Date>()->ValueOf());
|
|
}
|
|
|
|
void CheckIsSymbolAt(v8::Isolate* isolate, v8::Local<v8::Array> properties,
|
|
unsigned index, const char* name) {
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
v8::Local<v8::Value> value =
|
|
properties->Get(context, v8::Integer::New(isolate, index))
|
|
.ToLocalChecked();
|
|
CHECK(value->IsSymbol());
|
|
v8::String::Utf8Value symbol_name(
|
|
isolate, Local<Symbol>::Cast(value)->Description(isolate));
|
|
if (strcmp(name, *symbol_name) != 0) {
|
|
FATAL("properties[%u] was Symbol('%s') instead of Symbol('%s').", index,
|
|
name, *symbol_name);
|
|
}
|
|
}
|
|
|
|
void CheckStringArray(v8::Isolate* isolate, v8::Local<v8::Array> properties,
|
|
unsigned length, const char* names[]) {
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
CHECK_EQ(length, properties->Length());
|
|
for (unsigned i = 0; i < length; i++) {
|
|
v8::Local<v8::Value> value =
|
|
properties->Get(context, v8::Integer::New(isolate, i)).ToLocalChecked();
|
|
if (names[i] == nullptr) {
|
|
DCHECK(value->IsSymbol());
|
|
} else {
|
|
v8::String::Utf8Value elm(isolate, value);
|
|
if (strcmp(names[i], *elm) != 0) {
|
|
FATAL("properties[%u] was '%s' instead of '%s'.", i, *elm, names[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckProperties(v8::Isolate* isolate, v8::Local<v8::Value> val,
|
|
unsigned length, const char* names[]) {
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
v8::Local<v8::Object> obj = val.As<v8::Object>();
|
|
v8::Local<v8::Array> props = obj->GetPropertyNames(context).ToLocalChecked();
|
|
CheckStringArray(isolate, props, length, names);
|
|
}
|
|
|
|
|
|
void CheckOwnProperties(v8::Isolate* isolate, v8::Local<v8::Value> val,
|
|
unsigned elmc, const char* elmv[]) {
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
v8::Local<v8::Object> obj = val.As<v8::Object>();
|
|
v8::Local<v8::Array> props =
|
|
obj->GetOwnPropertyNames(context).ToLocalChecked();
|
|
CHECK_EQ(elmc, props->Length());
|
|
for (unsigned i = 0; i < elmc; i++) {
|
|
v8::String::Utf8Value elm(
|
|
isolate,
|
|
props->Get(context, v8::Integer::New(isolate, i)).ToLocalChecked());
|
|
CHECK_EQ(0, strcmp(elmv[i], *elm));
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(PropertyEnumeration) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Value> obj = CompileRun(
|
|
"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[4] = {21350:1};"
|
|
"x = Object.create(null);"
|
|
"x.a = 1; x[12345678] = 1;"
|
|
"result[5] = x;"
|
|
"result;");
|
|
v8::Local<v8::Array> elms = obj.As<v8::Array>();
|
|
CHECK_EQ(6u, elms->Length());
|
|
int elmc0 = 0;
|
|
const char** elmv0 = nullptr;
|
|
CheckProperties(
|
|
isolate,
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 0)).ToLocalChecked(),
|
|
elmc0, elmv0);
|
|
CheckOwnProperties(
|
|
isolate,
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 0)).ToLocalChecked(),
|
|
elmc0, elmv0);
|
|
int elmc1 = 2;
|
|
const char* elmv1[] = {"a", "b"};
|
|
CheckProperties(
|
|
isolate,
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 1)).ToLocalChecked(),
|
|
elmc1, elmv1);
|
|
CheckOwnProperties(
|
|
isolate,
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 1)).ToLocalChecked(),
|
|
elmc1, elmv1);
|
|
int elmc2 = 3;
|
|
const char* elmv2[] = {"0", "1", "2"};
|
|
CheckProperties(
|
|
isolate,
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 2)).ToLocalChecked(),
|
|
elmc2, elmv2);
|
|
CheckOwnProperties(
|
|
isolate,
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 2)).ToLocalChecked(),
|
|
elmc2, elmv2);
|
|
int elmc3 = 4;
|
|
const char* elmv3[] = {"w", "z", "x", "y"};
|
|
CheckProperties(
|
|
isolate,
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 3)).ToLocalChecked(),
|
|
elmc3, elmv3);
|
|
int elmc4 = 2;
|
|
const char* elmv4[] = {"w", "z"};
|
|
CheckOwnProperties(
|
|
isolate,
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 3)).ToLocalChecked(),
|
|
elmc4, elmv4);
|
|
// Dictionary elements.
|
|
int elmc5 = 1;
|
|
const char* elmv5[] = {"21350"};
|
|
CheckProperties(
|
|
isolate,
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 4)).ToLocalChecked(),
|
|
elmc5, elmv5);
|
|
// Dictionary properties.
|
|
int elmc6 = 2;
|
|
const char* elmv6[] = {"12345678", "a"};
|
|
CheckProperties(
|
|
isolate,
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 5)).ToLocalChecked(),
|
|
elmc6, elmv6);
|
|
}
|
|
|
|
|
|
THREADED_TEST(PropertyEnumeration2) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Value> obj = CompileRun(
|
|
"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;");
|
|
v8::Local<v8::Array> elms = obj.As<v8::Array>();
|
|
CHECK_EQ(4u, elms->Length());
|
|
int elmc0 = 0;
|
|
const char** elmv0 = nullptr;
|
|
CheckProperties(
|
|
isolate,
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 0)).ToLocalChecked(),
|
|
elmc0, elmv0);
|
|
|
|
v8::Local<v8::Value> val =
|
|
elms->Get(context.local(), v8::Integer::New(isolate, 0)).ToLocalChecked();
|
|
v8::Local<v8::Array> props =
|
|
val.As<v8::Object>()->GetPropertyNames(context.local()).ToLocalChecked();
|
|
CHECK_EQ(0u, props->Length());
|
|
for (uint32_t i = 0; i < props->Length(); i++) {
|
|
printf("p[%u]\n", i);
|
|
}
|
|
}
|
|
|
|
THREADED_TEST(GetPropertyNames) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Value> result = CompileRun(
|
|
"var result = {0: 0, 1: 1, a: 2, b: 3};"
|
|
"result[2**32] = '4294967296';"
|
|
"result[2**32-1] = '4294967295';"
|
|
"result[2**32-2] = '4294967294';"
|
|
"result[Symbol('symbol')] = true;"
|
|
"result.__proto__ = {__proto__:null, 2: 4, 3: 5, c: 6, d: 7};"
|
|
"result;");
|
|
v8::Local<v8::Object> object = result.As<v8::Object>();
|
|
v8::PropertyFilter default_filter =
|
|
static_cast<v8::PropertyFilter>(v8::ONLY_ENUMERABLE | v8::SKIP_SYMBOLS);
|
|
v8::PropertyFilter include_symbols_filter = v8::ONLY_ENUMERABLE;
|
|
|
|
v8::Local<v8::Array> properties =
|
|
object->GetPropertyNames(context.local()).ToLocalChecked();
|
|
const char* expected_properties1[] = {"0", "1", "4294967294", "a",
|
|
"b", "4294967296", "4294967295", "2",
|
|
"3", "c", "d"};
|
|
CheckStringArray(isolate, properties, 11, expected_properties1);
|
|
|
|
properties =
|
|
object
|
|
->GetPropertyNames(context.local(),
|
|
v8::KeyCollectionMode::kIncludePrototypes,
|
|
default_filter, v8::IndexFilter::kIncludeIndices)
|
|
.ToLocalChecked();
|
|
CheckStringArray(isolate, properties, 11, expected_properties1);
|
|
|
|
properties = object
|
|
->GetPropertyNames(context.local(),
|
|
v8::KeyCollectionMode::kIncludePrototypes,
|
|
include_symbols_filter,
|
|
v8::IndexFilter::kIncludeIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties1_1[] = {
|
|
"0", "1", "4294967294", "a", "b", "4294967296",
|
|
"4294967295", nullptr, "2", "3", "c", "d"};
|
|
CheckStringArray(isolate, properties, 12, expected_properties1_1);
|
|
CheckIsSymbolAt(isolate, properties, 7, "symbol");
|
|
|
|
properties =
|
|
object
|
|
->GetPropertyNames(context.local(),
|
|
v8::KeyCollectionMode::kIncludePrototypes,
|
|
default_filter, v8::IndexFilter::kSkipIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties2[] = {"a", "b", "4294967296",
|
|
"4294967295", "c", "d"};
|
|
CheckStringArray(isolate, properties, 6, expected_properties2);
|
|
|
|
properties = object
|
|
->GetPropertyNames(context.local(),
|
|
v8::KeyCollectionMode::kIncludePrototypes,
|
|
include_symbols_filter,
|
|
v8::IndexFilter::kSkipIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties2_1[] = {
|
|
"a", "b", "4294967296", "4294967295", nullptr, "c", "d"};
|
|
CheckStringArray(isolate, properties, 7, expected_properties2_1);
|
|
CheckIsSymbolAt(isolate, properties, 4, "symbol");
|
|
|
|
properties =
|
|
object
|
|
->GetPropertyNames(context.local(), v8::KeyCollectionMode::kOwnOnly,
|
|
default_filter, v8::IndexFilter::kIncludeIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties3[] = {
|
|
"0", "1", "4294967294", "a", "b", "4294967296", "4294967295",
|
|
};
|
|
CheckStringArray(isolate, properties, 7, expected_properties3);
|
|
|
|
properties = object
|
|
->GetPropertyNames(
|
|
context.local(), v8::KeyCollectionMode::kOwnOnly,
|
|
include_symbols_filter, v8::IndexFilter::kIncludeIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties3_1[] = {
|
|
"0", "1", "4294967294", "a", "b", "4294967296", "4294967295", nullptr};
|
|
CheckStringArray(isolate, properties, 8, expected_properties3_1);
|
|
CheckIsSymbolAt(isolate, properties, 7, "symbol");
|
|
|
|
properties =
|
|
object
|
|
->GetPropertyNames(context.local(), v8::KeyCollectionMode::kOwnOnly,
|
|
default_filter, v8::IndexFilter::kSkipIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties4[] = {"a", "b", "4294967296", "4294967295"};
|
|
CheckStringArray(isolate, properties, 4, expected_properties4);
|
|
|
|
properties = object
|
|
->GetPropertyNames(
|
|
context.local(), v8::KeyCollectionMode::kOwnOnly,
|
|
include_symbols_filter, v8::IndexFilter::kSkipIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties4_1[] = {"a", "b", "4294967296", "4294967295",
|
|
nullptr};
|
|
CheckStringArray(isolate, properties, 5, expected_properties4_1);
|
|
CheckIsSymbolAt(isolate, properties, 4, "symbol");
|
|
}
|
|
|
|
THREADED_TEST(ProxyGetPropertyNames) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Value> result = CompileRun(
|
|
"var target = {0: 0, 1: 1, a: 2, b: 3};"
|
|
"target[2**32] = '4294967296';"
|
|
"target[2**32-1] = '4294967295';"
|
|
"target[2**32-2] = '4294967294';"
|
|
"target[Symbol('symbol')] = true;"
|
|
"target.__proto__ = {__proto__:null, 2: 4, 3: 5, c: 6, d: 7};"
|
|
"var result = new Proxy(target, {});"
|
|
"result;");
|
|
v8::Local<v8::Object> object = result.As<v8::Object>();
|
|
v8::PropertyFilter default_filter =
|
|
static_cast<v8::PropertyFilter>(v8::ONLY_ENUMERABLE | v8::SKIP_SYMBOLS);
|
|
v8::PropertyFilter include_symbols_filter = v8::ONLY_ENUMERABLE;
|
|
|
|
v8::Local<v8::Array> properties =
|
|
object->GetPropertyNames(context.local()).ToLocalChecked();
|
|
const char* expected_properties1[] = {"0", "1", "4294967294", "a",
|
|
"b", "4294967296", "4294967295", "2",
|
|
"3", "c", "d"};
|
|
CheckStringArray(isolate, properties, 11, expected_properties1);
|
|
|
|
properties =
|
|
object
|
|
->GetPropertyNames(context.local(),
|
|
v8::KeyCollectionMode::kIncludePrototypes,
|
|
default_filter, v8::IndexFilter::kIncludeIndices)
|
|
.ToLocalChecked();
|
|
CheckStringArray(isolate, properties, 11, expected_properties1);
|
|
|
|
properties = object
|
|
->GetPropertyNames(context.local(),
|
|
v8::KeyCollectionMode::kIncludePrototypes,
|
|
include_symbols_filter,
|
|
v8::IndexFilter::kIncludeIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties1_1[] = {
|
|
"0", "1", "4294967294", "a", "b", "4294967296",
|
|
"4294967295", nullptr, "2", "3", "c", "d"};
|
|
CheckStringArray(isolate, properties, 12, expected_properties1_1);
|
|
CheckIsSymbolAt(isolate, properties, 7, "symbol");
|
|
|
|
properties =
|
|
object
|
|
->GetPropertyNames(context.local(),
|
|
v8::KeyCollectionMode::kIncludePrototypes,
|
|
default_filter, v8::IndexFilter::kSkipIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties2[] = {"a", "b", "4294967296",
|
|
"4294967295", "c", "d"};
|
|
CheckStringArray(isolate, properties, 6, expected_properties2);
|
|
|
|
properties = object
|
|
->GetPropertyNames(context.local(),
|
|
v8::KeyCollectionMode::kIncludePrototypes,
|
|
include_symbols_filter,
|
|
v8::IndexFilter::kSkipIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties2_1[] = {
|
|
"a", "b", "4294967296", "4294967295", nullptr, "c", "d"};
|
|
CheckStringArray(isolate, properties, 7, expected_properties2_1);
|
|
CheckIsSymbolAt(isolate, properties, 4, "symbol");
|
|
|
|
properties =
|
|
object
|
|
->GetPropertyNames(context.local(), v8::KeyCollectionMode::kOwnOnly,
|
|
default_filter, v8::IndexFilter::kIncludeIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties3[] = {"0", "1", "4294967294", "a",
|
|
"b", "4294967296", "4294967295"};
|
|
CheckStringArray(isolate, properties, 7, expected_properties3);
|
|
|
|
properties = object
|
|
->GetPropertyNames(
|
|
context.local(), v8::KeyCollectionMode::kOwnOnly,
|
|
include_symbols_filter, v8::IndexFilter::kIncludeIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties3_1[] = {
|
|
"0", "1", "4294967294", "a", "b", "4294967296", "4294967295", nullptr};
|
|
CheckStringArray(isolate, properties, 8, expected_properties3_1);
|
|
CheckIsSymbolAt(isolate, properties, 7, "symbol");
|
|
|
|
properties =
|
|
object
|
|
->GetPropertyNames(context.local(), v8::KeyCollectionMode::kOwnOnly,
|
|
default_filter, v8::IndexFilter::kSkipIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties4[] = {"a", "b", "4294967296", "4294967295"};
|
|
CheckStringArray(isolate, properties, 4, expected_properties4);
|
|
|
|
properties = object
|
|
->GetPropertyNames(
|
|
context.local(), v8::KeyCollectionMode::kOwnOnly,
|
|
include_symbols_filter, v8::IndexFilter::kSkipIndices)
|
|
.ToLocalChecked();
|
|
const char* expected_properties4_1[] = {"a", "b", "4294967296", "4294967295",
|
|
nullptr};
|
|
CheckStringArray(isolate, properties, 5, expected_properties4_1);
|
|
CheckIsSymbolAt(isolate, properties, 4, "symbol");
|
|
}
|
|
|
|
THREADED_TEST(AccessChecksReenabledCorrectly) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessCheckCallback(AccessAlwaysBlocked);
|
|
templ->Set(isolate, "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(isolate, k));
|
|
}
|
|
}
|
|
}
|
|
|
|
Local<v8::Object> instance_1 =
|
|
templ->NewInstance(context.local()).ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj_1"), instance_1)
|
|
.FromJust());
|
|
|
|
Local<Value> value_1 = CompileRun("obj_1.a");
|
|
CHECK(value_1.IsEmpty());
|
|
|
|
Local<v8::Object> instance_2 =
|
|
templ->NewInstance(context.local()).ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj_2"), instance_2)
|
|
.FromJust());
|
|
|
|
Local<Value> value_2 = CompileRun("obj_2.a");
|
|
CHECK(value_2.IsEmpty());
|
|
}
|
|
|
|
|
|
// 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(CcTest::isolate());
|
|
// Test LoadIC.
|
|
for (int i = 0; i < 2; i++) {
|
|
LocalContext context;
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("tmp"), v8::True(CcTest::isolate()))
|
|
.FromJust());
|
|
context->Global()->Delete(context.local(), v8_str("tmp")).FromJust();
|
|
CompileRun("for (var j = 0; j < 10; j++) new RegExp('');");
|
|
}
|
|
// Test CallIC.
|
|
for (int i = 0; i < 2; i++) {
|
|
LocalContext context;
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("tmp"), v8::True(CcTest::isolate()))
|
|
.FromJust());
|
|
context->Global()->Delete(context.local(), v8_str("tmp")).FromJust();
|
|
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::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<Context> context0 = Context::New(isolate);
|
|
v8::Local<Context> context1 = Context::New(isolate);
|
|
|
|
// 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();
|
|
CHECK(context1->Global()
|
|
->Set(context1, v8_str("other"), context0->Global())
|
|
.FromJust());
|
|
Local<Value> value = CompileRun("var instance = new other.C(); instance.x");
|
|
CHECK(value->IsInt32());
|
|
CHECK_EQ(42, value->Int32Value(context1).FromJust());
|
|
context1->Exit();
|
|
}
|
|
|
|
|
|
// Verify that we can clone an object
|
|
TEST(ObjectClone) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
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(env.local(), v8_str("gamma"), v8_str("cloneme")).FromJust();
|
|
|
|
CHECK(v8_str("hello")
|
|
->Equals(env.local(),
|
|
obj->Get(env.local(), v8_str("alpha")).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8::Integer::New(isolate, 123)
|
|
->Equals(env.local(),
|
|
obj->Get(env.local(), v8_str("beta")).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("cloneme")
|
|
->Equals(env.local(),
|
|
obj->Get(env.local(), v8_str("gamma")).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// Clone it.
|
|
Local<v8::Object> clone = obj->Clone();
|
|
CHECK(v8_str("hello")
|
|
->Equals(env.local(),
|
|
clone->Get(env.local(), v8_str("alpha")).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8::Integer::New(isolate, 123)
|
|
->Equals(env.local(),
|
|
clone->Get(env.local(), v8_str("beta")).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str("cloneme")
|
|
->Equals(env.local(),
|
|
clone->Get(env.local(), v8_str("gamma")).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// Set a property on the clone, verify each object.
|
|
CHECK(clone->Set(env.local(), v8_str("beta"), v8::Integer::New(isolate, 456))
|
|
.FromJust());
|
|
CHECK(v8::Integer::New(isolate, 123)
|
|
->Equals(env.local(),
|
|
obj->Get(env.local(), v8_str("beta")).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8::Integer::New(isolate, 456)
|
|
->Equals(env.local(),
|
|
clone->Get(env.local(), v8_str("beta")).ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
class OneByteVectorResource : public v8::String::ExternalOneByteStringResource {
|
|
public:
|
|
explicit OneByteVectorResource(v8::base::Vector<const char> vector)
|
|
: data_(vector) {}
|
|
~OneByteVectorResource() override = default;
|
|
size_t length() const override { return data_.length(); }
|
|
const char* data() const override { return data_.begin(); }
|
|
void Dispose() override {}
|
|
|
|
private:
|
|
v8::base::Vector<const char> data_;
|
|
};
|
|
|
|
|
|
class UC16VectorResource : public v8::String::ExternalStringResource {
|
|
public:
|
|
explicit UC16VectorResource(v8::base::Vector<const v8::base::uc16> vector)
|
|
: data_(vector) {}
|
|
~UC16VectorResource() override = default;
|
|
size_t length() const override { return data_.length(); }
|
|
const v8::base::uc16* data() const override { return data_.begin(); }
|
|
void Dispose() override {}
|
|
|
|
private:
|
|
v8::base::Vector<const v8::base::uc16> data_;
|
|
};
|
|
|
|
static void MorphAString(i::String string,
|
|
OneByteVectorResource* one_byte_resource,
|
|
UC16VectorResource* uc16_resource) {
|
|
i::Isolate* isolate = CcTest::i_isolate();
|
|
CHECK(i::StringShape(string).IsExternal());
|
|
i::ReadOnlyRoots roots(CcTest::heap());
|
|
if (string.IsOneByteRepresentation()) {
|
|
// Check old map is not internalized or long.
|
|
CHECK(string.map() == roots.external_one_byte_string_map());
|
|
// Morph external string to be TwoByte string.
|
|
string.set_map(roots.external_string_map());
|
|
i::ExternalTwoByteString morphed = i::ExternalTwoByteString::cast(string);
|
|
CcTest::heap()->UpdateExternalString(morphed, string.length(), 0);
|
|
morphed.SetResource(isolate, uc16_resource);
|
|
} else {
|
|
// Check old map is not internalized or long.
|
|
CHECK(string.map() == roots.external_string_map());
|
|
// Morph external string to be one-byte string.
|
|
string.set_map(roots.external_one_byte_string_map());
|
|
i::ExternalOneByteString morphed = i::ExternalOneByteString::cast(string);
|
|
CcTest::heap()->UpdateExternalString(morphed, string.length(), 0);
|
|
morphed.SetResource(isolate, one_byte_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);
|
|
{
|
|
LocalContext env;
|
|
i::Factory* factory = CcTest::i_isolate()->factory();
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
i::Isolate* i_isolate = CcTest::i_isolate();
|
|
v8::HandleScope scope(isolate);
|
|
OneByteVectorResource one_byte_resource(
|
|
v8::base::Vector<const char>(c_string, strlen(c_string)));
|
|
UC16VectorResource uc16_resource(
|
|
v8::base::Vector<const uint16_t>(two_byte_string, strlen(c_string)));
|
|
|
|
Local<String> lhs(v8::Utils::ToLocal(
|
|
factory->NewExternalStringFromOneByte(&one_byte_resource)
|
|
.ToHandleChecked()));
|
|
Local<String> rhs(v8::Utils::ToLocal(
|
|
factory->NewExternalStringFromOneByte(&one_byte_resource)
|
|
.ToHandleChecked()));
|
|
|
|
CHECK(env->Global()->Set(env.local(), v8_str("lhs"), lhs).FromJust());
|
|
CHECK(env->Global()->Set(env.local(), v8_str("rhs"), rhs).FromJust());
|
|
|
|
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);");
|
|
|
|
CHECK(lhs->IsOneByte());
|
|
CHECK(rhs->IsOneByte());
|
|
|
|
i::Handle<i::String> ilhs = v8::Utils::OpenHandle(*lhs);
|
|
i::Handle<i::String> irhs = v8::Utils::OpenHandle(*rhs);
|
|
MorphAString(*ilhs, &one_byte_resource, &uc16_resource);
|
|
MorphAString(*irhs, &one_byte_resource, &uc16_resource);
|
|
|
|
// This should UTF-8 without flattening, since everything is ASCII.
|
|
Local<String> cons =
|
|
v8_compile("cons")->Run(env.local()).ToLocalChecked().As<String>();
|
|
CHECK_EQ(128, cons->Utf8Length(isolate));
|
|
int nchars = -1;
|
|
CHECK_EQ(129, cons->WriteUtf8(isolate, 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(v8_str(expected_cons)
|
|
->Equals(env.local(), env->Global()
|
|
->Get(env.local(), v8_str("cons"))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str(expected_slice)
|
|
->Equals(env.local(), env->Global()
|
|
->Get(env.local(), v8_str("slice"))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(v8_str(expected_slice_on_cons)
|
|
->Equals(env.local(),
|
|
env->Global()
|
|
->Get(env.local(), v8_str("slice_on_cons"))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// This avoids the GC from trying to free a stack allocated resource.
|
|
if (ilhs->IsExternalOneByteString())
|
|
i::ExternalOneByteString::cast(*ilhs).SetResource(i_isolate, nullptr);
|
|
else
|
|
i::ExternalTwoByteString::cast(*ilhs).SetResource(i_isolate, nullptr);
|
|
if (irhs->IsExternalOneByteString())
|
|
i::ExternalOneByteString::cast(*irhs).SetResource(i_isolate, nullptr);
|
|
else
|
|
i::ExternalTwoByteString::cast(*irhs).SetResource(i_isolate, nullptr);
|
|
}
|
|
i::DeleteArray(two_byte_string);
|
|
}
|
|
|
|
|
|
TEST(CompileExternalTwoByteSource) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
// This is a very short list of sources, which currently is to check for a
|
|
// regression caused by r2703.
|
|
const char* one_byte_sources[] = {
|
|
"0.5",
|
|
"-0.5", // This mainly testes PushBack in the Scanner.
|
|
"--0.5", // This mainly testes PushBack in the Scanner.
|
|
nullptr};
|
|
|
|
// Compile the sources as external two byte strings.
|
|
for (int i = 0; one_byte_sources[i] != nullptr; i++) {
|
|
uint16_t* two_byte_string = AsciiToTwoByteString(one_byte_sources[i]);
|
|
TestResource* uc16_resource = new TestResource(two_byte_string);
|
|
v8::Local<v8::String> source =
|
|
v8::String::NewExternalTwoByte(context->GetIsolate(), uc16_resource)
|
|
.ToLocalChecked();
|
|
v8::Script::Compile(context.local(), source).FromMaybe(Local<Script>());
|
|
}
|
|
}
|
|
|
|
// Test that we cannot set a property on the global object if there
|
|
// is a read-only property in the prototype chain.
|
|
TEST(ReadOnlyPropertyInGlobalProto) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate);
|
|
LocalContext context(nullptr, templ);
|
|
v8::Local<v8::Object> global = context->Global();
|
|
v8::Local<v8::Object> global_proto = v8::Local<v8::Object>::Cast(
|
|
global->Get(context.local(), v8_str("__proto__")).ToLocalChecked());
|
|
global_proto->DefineOwnProperty(context.local(), v8_str("x"),
|
|
v8::Integer::New(isolate, 0), v8::ReadOnly)
|
|
.FromJust();
|
|
global_proto->DefineOwnProperty(context.local(), v8_str("y"),
|
|
v8::Integer::New(isolate, 0), v8::ReadOnly)
|
|
.FromJust();
|
|
// Check without 'eval' or 'with'.
|
|
v8::Local<v8::Value> res =
|
|
CompileRun("function f() { x = 42; return x; }; f()");
|
|
CHECK(v8::Integer::New(isolate, 0)->Equals(context.local(), res).FromJust());
|
|
// Check with 'eval'.
|
|
res = CompileRun("function f() { eval('1'); y = 43; return y; }; f()");
|
|
CHECK(v8::Integer::New(isolate, 0)->Equals(context.local(), res).FromJust());
|
|
// Check with 'with'.
|
|
res = CompileRun("function f() { with (this) { y = 44 }; return y; }; f()");
|
|
CHECK(v8::Integer::New(isolate, 0)->Equals(context.local(), res).FromJust());
|
|
}
|
|
|
|
|
|
TEST(CreateDataProperty) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
CompileRun(
|
|
"var a = {};"
|
|
"var b = [];"
|
|
"Object.defineProperty(a, 'foo', {value: 23});"
|
|
"Object.defineProperty(a, 'bar', {value: 23, configurable: true});");
|
|
|
|
v8::Local<v8::Object> obj = v8::Local<v8::Object>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("a")).ToLocalChecked());
|
|
v8::Local<v8::Array> arr = v8::Local<v8::Array>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("b")).ToLocalChecked());
|
|
{
|
|
// Can't change a non-configurable properties.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(!obj->CreateDataProperty(env.local(), v8_str("foo"),
|
|
v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK(obj->CreateDataProperty(env.local(), v8_str("bar"),
|
|
v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val =
|
|
obj->Get(env.local(), v8_str("bar")).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// Set a regular property.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->CreateDataProperty(env.local(), v8_str("blub"),
|
|
v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val =
|
|
obj->Get(env.local(), v8_str("blub")).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// Set an indexed property.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->CreateDataProperty(env.local(), v8_str("1"),
|
|
v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val = obj->Get(env.local(), 1).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// Special cases for arrays.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(!arr->CreateDataProperty(env.local(), v8_str("length"),
|
|
v8::Integer::New(isolate, 1)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
{
|
|
// Special cases for arrays: index exceeds the array's length
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(arr->CreateDataProperty(env.local(), 1, v8::Integer::New(isolate, 23))
|
|
.FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(2U, arr->Length());
|
|
v8::Local<v8::Value> val = arr->Get(env.local(), 1).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(23.0, val->NumberValue(env.local()).FromJust());
|
|
|
|
// Set an existing entry.
|
|
CHECK(arr->CreateDataProperty(env.local(), 0, v8::Integer::New(isolate, 42))
|
|
.FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
val = arr->Get(env.local(), 0).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
CompileRun("Object.freeze(a);");
|
|
{
|
|
// Can't change non-extensible objects.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(!obj->CreateDataProperty(env.local(), v8_str("baz"),
|
|
v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate);
|
|
templ->SetAccessCheckCallback(AccessAlwaysBlocked);
|
|
v8::Local<v8::Object> access_checked =
|
|
templ->NewInstance(env.local()).ToLocalChecked();
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(access_checked->CreateDataProperty(env.local(), v8_str("foo"),
|
|
v8::Integer::New(isolate, 42))
|
|
.IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
}
|
|
|
|
|
|
TEST(DefineOwnProperty) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
CompileRun(
|
|
"var a = {};"
|
|
"var b = [];"
|
|
"Object.defineProperty(a, 'foo', {value: 23});"
|
|
"Object.defineProperty(a, 'bar', {value: 23, configurable: true});");
|
|
|
|
v8::Local<v8::Object> obj = v8::Local<v8::Object>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("a")).ToLocalChecked());
|
|
v8::Local<v8::Array> arr = v8::Local<v8::Array>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("b")).ToLocalChecked());
|
|
{
|
|
// Can't change a non-configurable properties.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(!obj->DefineOwnProperty(env.local(), v8_str("foo"),
|
|
v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK(obj->DefineOwnProperty(env.local(), v8_str("bar"),
|
|
v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val =
|
|
obj->Get(env.local(), v8_str("bar")).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// Set a regular property.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->DefineOwnProperty(env.local(), v8_str("blub"),
|
|
v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val =
|
|
obj->Get(env.local(), v8_str("blub")).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// Set an indexed property.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->DefineOwnProperty(env.local(), v8_str("1"),
|
|
v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val = obj->Get(env.local(), 1).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// Special cases for arrays.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(!arr->DefineOwnProperty(env.local(), v8_str("length"),
|
|
v8::Integer::New(isolate, 1)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
{
|
|
// Special cases for arrays: index exceeds the array's length
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(arr->DefineOwnProperty(env.local(), v8_str("1"),
|
|
v8::Integer::New(isolate, 23)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(2U, arr->Length());
|
|
v8::Local<v8::Value> val = arr->Get(env.local(), 1).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(23.0, val->NumberValue(env.local()).FromJust());
|
|
|
|
// Set an existing entry.
|
|
CHECK(arr->DefineOwnProperty(env.local(), v8_str("0"),
|
|
v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
val = arr->Get(env.local(), 0).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// Set a non-writable property.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->DefineOwnProperty(env.local(), v8_str("lala"),
|
|
v8::Integer::New(isolate, 42),
|
|
v8::ReadOnly).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val =
|
|
obj->Get(env.local(), v8_str("lala")).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
CHECK_EQ(v8::ReadOnly, obj->GetPropertyAttributes(
|
|
env.local(), v8_str("lala")).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
CompileRun("Object.freeze(a);");
|
|
{
|
|
// Can't change non-extensible objects.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(!obj->DefineOwnProperty(env.local(), v8_str("baz"),
|
|
v8::Integer::New(isolate, 42)).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate);
|
|
templ->SetAccessCheckCallback(AccessAlwaysBlocked);
|
|
v8::Local<v8::Object> access_checked =
|
|
templ->NewInstance(env.local()).ToLocalChecked();
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(access_checked->DefineOwnProperty(env.local(), v8_str("foo"),
|
|
v8::Integer::New(isolate, 42))
|
|
.IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
}
|
|
|
|
TEST(DefineProperty) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
v8::Local<v8::Name> p;
|
|
|
|
CompileRun(
|
|
"var a = {};"
|
|
"var b = [];"
|
|
"Object.defineProperty(a, 'v1', {value: 23});"
|
|
"Object.defineProperty(a, 'v2', {value: 23, configurable: true});");
|
|
|
|
v8::Local<v8::Object> obj = v8::Local<v8::Object>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("a")).ToLocalChecked());
|
|
v8::Local<v8::Array> arr = v8::Local<v8::Array>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("b")).ToLocalChecked());
|
|
|
|
v8::PropertyDescriptor desc(v8_num(42));
|
|
{
|
|
// Use a data descriptor.
|
|
|
|
// Cannot change a non-configurable property.
|
|
p = v8_str("v1");
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(!obj->DefineProperty(env.local(), p, desc).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(23.0, val->NumberValue(env.local()).FromJust());
|
|
|
|
// Change a configurable property.
|
|
p = v8_str("v2");
|
|
obj->DefineProperty(env.local(), p, desc).FromJust();
|
|
CHECK(obj->DefineProperty(env.local(), p, desc).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
|
|
// Check that missing writable has default value false.
|
|
p = v8_str("v12");
|
|
CHECK(obj->DefineProperty(env.local(), p, desc).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
v8::PropertyDescriptor desc2(v8_num(43));
|
|
CHECK(!obj->DefineProperty(env.local(), p, desc2).FromJust());
|
|
val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
// Set a regular property.
|
|
p = v8_str("v3");
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->DefineProperty(env.local(), p, desc).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// Set an indexed property.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->DefineProperty(env.local(), v8_str("1"), desc).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val = obj->Get(env.local(), 1).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// No special case when changing array length.
|
|
v8::TryCatch try_catch(isolate);
|
|
// Use a writable descriptor, otherwise the next test, that changes
|
|
// the array length will fail.
|
|
v8::PropertyDescriptor desc_writable(v8_num(42), true);
|
|
CHECK(arr->DefineProperty(env.local(), v8_str("length"), desc_writable)
|
|
.FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
// Special cases for arrays: index exceeds the array's length.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(arr->DefineProperty(env.local(), v8_str("100"), desc).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(101U, arr->Length());
|
|
v8::Local<v8::Value> val = arr->Get(env.local(), 100).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
|
|
// Set an existing entry.
|
|
CHECK(arr->DefineProperty(env.local(), v8_str("0"), desc).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
val = arr->Get(env.local(), 0).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// Use a generic descriptor.
|
|
v8::PropertyDescriptor desc_generic;
|
|
|
|
p = v8_str("v4");
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->DefineProperty(env.local(), p, desc_generic).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK(val->IsUndefined());
|
|
|
|
obj->Set(env.local(), p, v8_num(1)).FromJust();
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK(val->IsUndefined());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
// Use a data descriptor with undefined value.
|
|
v8::PropertyDescriptor desc_empty(v8::Undefined(isolate));
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->DefineProperty(env.local(), p, desc_empty).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK(val->IsUndefined());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
// Use a descriptor with attribute == v8::ReadOnly.
|
|
v8::PropertyDescriptor desc_read_only(v8_num(42), false);
|
|
desc_read_only.set_enumerable(true);
|
|
desc_read_only.set_configurable(true);
|
|
|
|
p = v8_str("v5");
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->DefineProperty(env.local(), p, desc_read_only).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(42.0, val->NumberValue(env.local()).FromJust());
|
|
CHECK_EQ(v8::ReadOnly,
|
|
obj->GetPropertyAttributes(env.local(), p).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
// Use an accessor descriptor with empty handles.
|
|
v8::PropertyDescriptor desc_empty(v8::Undefined(isolate),
|
|
v8::Undefined(isolate));
|
|
|
|
p = v8_str("v6");
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->DefineProperty(env.local(), p, desc_empty).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
v8::Local<v8::Value> val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK(val->IsUndefined());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
// Use an accessor descriptor.
|
|
CompileRun(
|
|
"var set = function(x) {this.val = 2*x;};"
|
|
"var get = function() {return this.val || 0;};");
|
|
|
|
v8::Local<v8::Function> get = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("get")).ToLocalChecked());
|
|
v8::Local<v8::Function> set = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("set")).ToLocalChecked());
|
|
v8::PropertyDescriptor desc_getter_setter(get, set);
|
|
|
|
p = v8_str("v7");
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->DefineProperty(env.local(), p, desc_getter_setter).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
v8::Local<v8::Value> val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(0.0, val->NumberValue(env.local()).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
obj->Set(env.local(), p, v8_num(7)).FromJust();
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
val = obj->Get(env.local(), p).ToLocalChecked();
|
|
CHECK(val->IsNumber());
|
|
CHECK_EQ(14.0, val->NumberValue(env.local()).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
// Redefine an existing property.
|
|
|
|
// desc = {value: 42, enumerable: true}
|
|
v8::PropertyDescriptor desc42(v8_num(42));
|
|
desc42.set_enumerable(true);
|
|
|
|
p = v8_str("v8");
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(obj->DefineProperty(env.local(), p, desc42).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
// desc = {enumerable: true}
|
|
v8::PropertyDescriptor desc_true((v8::Local<v8::Value>()));
|
|
desc_true.set_enumerable(true);
|
|
|
|
// Successful redefinition because all present attributes have the same
|
|
// value as the current descriptor.
|
|
CHECK(obj->DefineProperty(env.local(), p, desc_true).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
// desc = {}
|
|
v8::PropertyDescriptor desc_empty;
|
|
// Successful redefinition because no attributes are overwritten in the
|
|
// current descriptor.
|
|
CHECK(obj->DefineProperty(env.local(), p, desc_empty).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
// desc = {enumerable: false}
|
|
v8::PropertyDescriptor desc_false((v8::Local<v8::Value>()));
|
|
desc_false.set_enumerable(false);
|
|
// Not successful because we cannot define a different value for enumerable.
|
|
CHECK(!obj->DefineProperty(env.local(), p, desc_false).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
// Redefine a property that has a getter.
|
|
CompileRun("var get = function() {};");
|
|
v8::Local<v8::Function> get = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("get")).ToLocalChecked());
|
|
|
|
// desc = {get: function() {}}
|
|
v8::PropertyDescriptor desc_getter(get, v8::Local<v8::Function>());
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
p = v8_str("v9");
|
|
CHECK(obj->DefineProperty(env.local(), p, desc_getter).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
// desc_empty = {}
|
|
// Successful because we are not redefining the current getter.
|
|
v8::PropertyDescriptor desc_empty;
|
|
CHECK(obj->DefineProperty(env.local(), p, desc_empty).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
// desc = {get: function() {}}
|
|
// Successful because we redefine the getter with its current value.
|
|
CHECK(obj->DefineProperty(env.local(), p, desc_getter).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
// desc = {get: undefined}
|
|
v8::PropertyDescriptor desc_undefined(v8::Undefined(isolate),
|
|
v8::Local<v8::Function>());
|
|
// Not successful because we cannot redefine with the current value of get
|
|
// with undefined.
|
|
CHECK(!obj->DefineProperty(env.local(), p, desc_undefined).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
CompileRun("Object.freeze(a);");
|
|
{
|
|
// We cannot change non-extensible objects.
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(!obj->DefineProperty(env.local(), v8_str("v10"), desc).FromJust());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate);
|
|
templ->SetAccessCheckCallback(AccessAlwaysBlocked);
|
|
v8::Local<v8::Object> access_checked =
|
|
templ->NewInstance(env.local()).ToLocalChecked();
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(access_checked->DefineProperty(env.local(), v8_str("v11"), desc)
|
|
.IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
}
|
|
|
|
THREADED_TEST(GetCurrentContextWhenNotInContext) {
|
|
i::Isolate* isolate = CcTest::i_isolate();
|
|
CHECK_NOT_NULL(isolate);
|
|
CHECK(isolate->context().is_null());
|
|
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
|
|
v8::HandleScope scope(v8_isolate);
|
|
// The following should not crash, but return an empty handle.
|
|
v8::Local<v8::Context> current = v8_isolate->GetCurrentContext();
|
|
CHECK(current.IsEmpty());
|
|
}
|
|
|
|
|
|
// Check that a variable declaration with no explicit initialization
|
|
// value does shadow an existing property in the prototype chain.
|
|
THREADED_TEST(InitGlobalVarInProtoChain) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
// Introduce a variable in the prototype chain.
|
|
CompileRun("__proto__.x = 42");
|
|
v8::Local<v8::Value> result = CompileRun("var x = 43; x");
|
|
CHECK(!result->IsUndefined());
|
|
CHECK_EQ(43, result->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
|
|
// 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) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
|
v8::Local<v8::FunctionTemplate> func_templ =
|
|
v8::FunctionTemplate::New(isolate);
|
|
v8::Local<v8::String> foo_string = v8_str("foo");
|
|
obj->Set(context.local(), foo_string,
|
|
func_templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
v8::Local<v8::Object> obj_clone = obj->Clone();
|
|
obj_clone->Set(context.local(), foo_string, v8_str("Hello")).FromJust();
|
|
CHECK(!obj->Get(context.local(), foo_string).ToLocalChecked()->IsUndefined());
|
|
}
|
|
|
|
THREADED_TEST(ScriptContextDependence) {
|
|
LocalContext c1;
|
|
v8::HandleScope scope(c1->GetIsolate());
|
|
const char source[] = "foo";
|
|
v8::Local<v8::Script> dep = v8_compile(source);
|
|
v8::ScriptCompiler::Source script_source(
|
|
v8::String::NewFromUtf8Literal(c1->GetIsolate(), source));
|
|
v8::Local<v8::UnboundScript> indep =
|
|
v8::ScriptCompiler::CompileUnboundScript(c1->GetIsolate(), &script_source)
|
|
.ToLocalChecked();
|
|
c1->Global()
|
|
->Set(c1.local(), v8::String::NewFromUtf8Literal(c1->GetIsolate(), "foo"),
|
|
v8::Integer::New(c1->GetIsolate(), 100))
|
|
.FromJust();
|
|
CHECK_EQ(
|
|
dep->Run(c1.local()).ToLocalChecked()->Int32Value(c1.local()).FromJust(),
|
|
100);
|
|
CHECK_EQ(indep->BindToCurrentContext()
|
|
->Run(c1.local())
|
|
.ToLocalChecked()
|
|
->Int32Value(c1.local())
|
|
.FromJust(),
|
|
100);
|
|
LocalContext c2;
|
|
c2->Global()
|
|
->Set(c2.local(), v8::String::NewFromUtf8Literal(c2->GetIsolate(), "foo"),
|
|
v8::Integer::New(c2->GetIsolate(), 101))
|
|
.FromJust();
|
|
CHECK_EQ(
|
|
dep->Run(c2.local()).ToLocalChecked()->Int32Value(c2.local()).FromJust(),
|
|
100);
|
|
CHECK_EQ(indep->BindToCurrentContext()
|
|
->Run(c2.local())
|
|
.ToLocalChecked()
|
|
->Int32Value(c2.local())
|
|
.FromJust(),
|
|
101);
|
|
}
|
|
|
|
#if V8_ENABLE_WEBASSEMBLY
|
|
static int asm_warning_triggered = 0;
|
|
|
|
static void AsmJsWarningListener(v8::Local<v8::Message> message,
|
|
v8::Local<Value>) {
|
|
CHECK_EQ(v8::Isolate::kMessageWarning, message->ErrorLevel());
|
|
asm_warning_triggered = 1;
|
|
}
|
|
|
|
TEST(AsmJsWarning) {
|
|
i::FLAG_validate_asm = true;
|
|
if (i::FLAG_suppress_asm_messages) return;
|
|
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
asm_warning_triggered = 0;
|
|
isolate->AddMessageListenerWithErrorLevel(AsmJsWarningListener,
|
|
v8::Isolate::kMessageAll);
|
|
CompileRun(
|
|
"function module() {\n"
|
|
" 'use asm';\n"
|
|
" var x = 'hi';\n"
|
|
" return {};\n"
|
|
"}\n"
|
|
"module();");
|
|
int kExpectedWarnings = 1;
|
|
CHECK_EQ(kExpectedWarnings, asm_warning_triggered);
|
|
isolate->RemoveMessageListeners(AsmJsWarningListener);
|
|
}
|
|
#endif // V8_ENABLE_WEBASSEMBLY
|
|
|
|
static int error_level_message_count = 0;
|
|
static int expected_error_level = 0;
|
|
|
|
static void ErrorLevelListener(v8::Local<v8::Message> message,
|
|
v8::Local<Value>) {
|
|
DCHECK_EQ(expected_error_level, message->ErrorLevel());
|
|
++error_level_message_count;
|
|
}
|
|
|
|
TEST(ErrorLevelWarning) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
|
|
const char* source = "fake = 1;";
|
|
v8::Local<v8::Script> lscript = CompileWithOrigin(source, "test", false);
|
|
i::Handle<i::SharedFunctionInfo> obj = i::Handle<i::SharedFunctionInfo>::cast(
|
|
v8::Utils::OpenHandle(*lscript->GetUnboundScript()));
|
|
CHECK(obj->script().IsScript());
|
|
i::Handle<i::Script> script(i::Script::cast(obj->script()), i_isolate);
|
|
|
|
int levels[] = {
|
|
v8::Isolate::kMessageLog, v8::Isolate::kMessageInfo,
|
|
v8::Isolate::kMessageDebug, v8::Isolate::kMessageWarning,
|
|
};
|
|
error_level_message_count = 0;
|
|
isolate->AddMessageListenerWithErrorLevel(ErrorLevelListener,
|
|
v8::Isolate::kMessageAll);
|
|
for (size_t i = 0; i < arraysize(levels); i++) {
|
|
i::MessageLocation location(script, 0, 0);
|
|
i::Handle<i::String> msg(i_isolate->factory()->InternalizeString(
|
|
v8::base::StaticCharVector("test")));
|
|
i::Handle<i::JSMessageObject> message =
|
|
i::MessageHandler::MakeMessageObject(
|
|
i_isolate, i::MessageTemplate::kAsmJsInvalid, &location, msg,
|
|
i::Handle<i::FixedArray>::null());
|
|
message->set_error_level(levels[i]);
|
|
expected_error_level = levels[i];
|
|
i::MessageHandler::ReportMessage(i_isolate, &location, message);
|
|
}
|
|
isolate->RemoveMessageListeners(ErrorLevelListener);
|
|
DCHECK_EQ(arraysize(levels), error_level_message_count);
|
|
}
|
|
|
|
v8::PromiseRejectEvent reject_event = v8::kPromiseRejectWithNoHandler;
|
|
int promise_reject_counter = 0;
|
|
int promise_revoke_counter = 0;
|
|
int promise_reject_after_resolved_counter = 0;
|
|
int promise_resolve_after_resolved_counter = 0;
|
|
int promise_reject_msg_line_number = -1;
|
|
int promise_reject_msg_column_number = -1;
|
|
int promise_reject_line_number = -1;
|
|
int promise_reject_column_number = -1;
|
|
int promise_reject_frame_count = -1;
|
|
bool promise_reject_is_shared_cross_origin = false;
|
|
|
|
void PromiseRejectCallback(v8::PromiseRejectMessage reject_message) {
|
|
v8::Local<v8::Object> global = CcTest::global();
|
|
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
|
|
CHECK_NE(v8::Promise::PromiseState::kPending,
|
|
reject_message.GetPromise()->State());
|
|
switch (reject_message.GetEvent()) {
|
|
case v8::kPromiseRejectWithNoHandler: {
|
|
promise_reject_counter++;
|
|
global->Set(context, v8_str("rejected"), reject_message.GetPromise())
|
|
.FromJust();
|
|
global->Set(context, v8_str("value"), reject_message.GetValue())
|
|
.FromJust();
|
|
v8::Local<v8::Message> message = v8::Exception::CreateMessage(
|
|
CcTest::isolate(), reject_message.GetValue());
|
|
v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace();
|
|
|
|
promise_reject_msg_line_number =
|
|
message->GetLineNumber(context).FromJust();
|
|
promise_reject_msg_column_number =
|
|
message->GetStartColumn(context).FromJust() + 1;
|
|
promise_reject_is_shared_cross_origin =
|
|
message->IsSharedCrossOrigin();
|
|
|
|
if (!stack_trace.IsEmpty()) {
|
|
promise_reject_frame_count = stack_trace->GetFrameCount();
|
|
if (promise_reject_frame_count > 0) {
|
|
CHECK(stack_trace->GetFrame(CcTest::isolate(), 0)
|
|
->GetScriptName()
|
|
->Equals(context, v8_str("pro"))
|
|
.FromJust());
|
|
promise_reject_line_number =
|
|
stack_trace->GetFrame(CcTest::isolate(), 0)->GetLineNumber();
|
|
promise_reject_column_number =
|
|
stack_trace->GetFrame(CcTest::isolate(), 0)->GetColumn();
|
|
} else {
|
|
promise_reject_line_number = -1;
|
|
promise_reject_column_number = -1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case v8::kPromiseHandlerAddedAfterReject: {
|
|
promise_revoke_counter++;
|
|
global->Set(context, v8_str("revoked"), reject_message.GetPromise())
|
|
.FromJust();
|
|
CHECK(reject_message.GetValue().IsEmpty());
|
|
break;
|
|
}
|
|
case v8::kPromiseRejectAfterResolved: {
|
|
promise_reject_after_resolved_counter++;
|
|
break;
|
|
}
|
|
case v8::kPromiseResolveAfterResolved: {
|
|
promise_resolve_after_resolved_counter++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
v8::Local<v8::Promise> GetPromise(const char* name) {
|
|
return v8::Local<v8::Promise>::Cast(
|
|
CcTest::global()
|
|
->Get(CcTest::isolate()->GetCurrentContext(), v8_str(name))
|
|
.ToLocalChecked());
|
|
}
|
|
|
|
|
|
v8::Local<v8::Value> RejectValue() {
|
|
return CcTest::global()
|
|
->Get(CcTest::isolate()->GetCurrentContext(), v8_str("value"))
|
|
.ToLocalChecked();
|
|
}
|
|
|
|
|
|
void ResetPromiseStates() {
|
|
promise_reject_counter = 0;
|
|
promise_revoke_counter = 0;
|
|
promise_reject_after_resolved_counter = 0;
|
|
promise_resolve_after_resolved_counter = 0;
|
|
promise_reject_msg_line_number = -1;
|
|
promise_reject_msg_column_number = -1;
|
|
promise_reject_line_number = -1;
|
|
promise_reject_column_number = -1;
|
|
promise_reject_frame_count = -1;
|
|
|
|
v8::Local<v8::Object> global = CcTest::global();
|
|
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
|
|
global->Set(context, v8_str("rejected"), v8_str("")).FromJust();
|
|
global->Set(context, v8_str("value"), v8_str("")).FromJust();
|
|
global->Set(context, v8_str("revoked"), v8_str("")).FromJust();
|
|
}
|
|
|
|
|
|
TEST(PromiseRejectCallback) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
isolate->SetPromiseRejectCallback(PromiseRejectCallback);
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Create promise p0.
|
|
CompileRun(
|
|
"var reject; \n"
|
|
"var p0 = new Promise( \n"
|
|
" function(res, rej) { \n"
|
|
" reject = rej; \n"
|
|
" } \n"
|
|
"); \n");
|
|
CHECK(!GetPromise("p0")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
|
|
// Add resolve handler (and default reject handler) to p0.
|
|
CompileRun("var p1 = p0.then(function(){});");
|
|
CHECK(GetPromise("p0")->HasHandler());
|
|
CHECK(!GetPromise("p1")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
|
|
// Reject p0.
|
|
CompileRun("reject('ppp');");
|
|
CHECK(GetPromise("p0")->HasHandler());
|
|
CHECK(!GetPromise("p1")->HasHandler());
|
|
CHECK_EQ(1, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
CHECK_EQ(v8::kPromiseRejectWithNoHandler, reject_event);
|
|
CHECK(
|
|
GetPromise("rejected")->Equals(env.local(), GetPromise("p1")).FromJust());
|
|
CHECK(RejectValue()->Equals(env.local(), v8_str("ppp")).FromJust());
|
|
|
|
// Reject p0 again. Callback is not triggered again.
|
|
CompileRun("reject();");
|
|
CHECK(GetPromise("p0")->HasHandler());
|
|
CHECK(!GetPromise("p1")->HasHandler());
|
|
CHECK_EQ(1, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
|
|
// Add resolve handler to p1.
|
|
CompileRun("var p2 = p1.then(function(){});");
|
|
CHECK(GetPromise("p0")->HasHandler());
|
|
CHECK(GetPromise("p1")->HasHandler());
|
|
CHECK(!GetPromise("p2")->HasHandler());
|
|
CHECK_EQ(2, promise_reject_counter);
|
|
CHECK_EQ(1, promise_revoke_counter);
|
|
CHECK(
|
|
GetPromise("rejected")->Equals(env.local(), GetPromise("p2")).FromJust());
|
|
CHECK(RejectValue()->Equals(env.local(), v8_str("ppp")).FromJust());
|
|
CHECK(
|
|
GetPromise("revoked")->Equals(env.local(), GetPromise("p1")).FromJust());
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Create promise q0.
|
|
CompileRun(
|
|
"var q0 = new Promise( \n"
|
|
" function(res, rej) { \n"
|
|
" reject = rej; \n"
|
|
" } \n"
|
|
"); \n");
|
|
CHECK(!GetPromise("q0")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
|
|
// Add reject handler to q0.
|
|
CompileRun("var q1 = q0.catch(function() {});");
|
|
CHECK(GetPromise("q0")->HasHandler());
|
|
CHECK(!GetPromise("q1")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
|
|
// Reject q0.
|
|
CompileRun("reject('qq')");
|
|
CHECK(GetPromise("q0")->HasHandler());
|
|
CHECK(!GetPromise("q1")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
|
|
// Add a new reject handler, which rejects by returning Promise.reject().
|
|
// The returned promise q_ triggers a reject callback at first, only to
|
|
// revoke it when returning it causes q2 to be rejected.
|
|
CompileRun(
|
|
"var q_;"
|
|
"var q2 = q0.catch( \n"
|
|
" function() { \n"
|
|
" q_ = Promise.reject('qqq'); \n"
|
|
" return q_; \n"
|
|
" } \n"
|
|
"); \n");
|
|
CHECK(GetPromise("q0")->HasHandler());
|
|
CHECK(!GetPromise("q1")->HasHandler());
|
|
CHECK(!GetPromise("q2")->HasHandler());
|
|
CHECK(GetPromise("q_")->HasHandler());
|
|
CHECK_EQ(2, promise_reject_counter);
|
|
CHECK_EQ(1, promise_revoke_counter);
|
|
CHECK(
|
|
GetPromise("rejected")->Equals(env.local(), GetPromise("q2")).FromJust());
|
|
CHECK(
|
|
GetPromise("revoked")->Equals(env.local(), GetPromise("q_")).FromJust());
|
|
CHECK(RejectValue()->Equals(env.local(), v8_str("qqq")).FromJust());
|
|
|
|
// Add a reject handler to the resolved q1, which rejects by throwing.
|
|
CompileRun(
|
|
"var q3 = q1.then( \n"
|
|
" function() { \n"
|
|
" throw 'qqqq'; \n"
|
|
" } \n"
|
|
"); \n");
|
|
CHECK(GetPromise("q0")->HasHandler());
|
|
CHECK(GetPromise("q1")->HasHandler());
|
|
CHECK(!GetPromise("q2")->HasHandler());
|
|
CHECK(!GetPromise("q3")->HasHandler());
|
|
CHECK_EQ(3, promise_reject_counter);
|
|
CHECK_EQ(1, promise_revoke_counter);
|
|
CHECK(
|
|
GetPromise("rejected")->Equals(env.local(), GetPromise("q3")).FromJust());
|
|
CHECK(RejectValue()->Equals(env.local(), v8_str("qqqq")).FromJust());
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Create promise r0, which has three handlers, two of which handle rejects.
|
|
CompileRun(
|
|
"var r0 = new Promise( \n"
|
|
" function(res, rej) { \n"
|
|
" reject = rej; \n"
|
|
" } \n"
|
|
"); \n"
|
|
"var r1 = r0.catch(function() {}); \n"
|
|
"var r2 = r0.then(function() {}); \n"
|
|
"var r3 = r0.then(function() {}, \n"
|
|
" function() {}); \n");
|
|
CHECK(GetPromise("r0")->HasHandler());
|
|
CHECK(!GetPromise("r1")->HasHandler());
|
|
CHECK(!GetPromise("r2")->HasHandler());
|
|
CHECK(!GetPromise("r3")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
|
|
// Reject r0.
|
|
CompileRun("reject('rrr')");
|
|
CHECK(GetPromise("r0")->HasHandler());
|
|
CHECK(!GetPromise("r1")->HasHandler());
|
|
CHECK(!GetPromise("r2")->HasHandler());
|
|
CHECK(!GetPromise("r3")->HasHandler());
|
|
CHECK_EQ(1, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
CHECK(
|
|
GetPromise("rejected")->Equals(env.local(), GetPromise("r2")).FromJust());
|
|
CHECK(RejectValue()->Equals(env.local(), v8_str("rrr")).FromJust());
|
|
|
|
// Add reject handler to r2.
|
|
CompileRun("var r4 = r2.catch(function() {});");
|
|
CHECK(GetPromise("r0")->HasHandler());
|
|
CHECK(!GetPromise("r1")->HasHandler());
|
|
CHECK(GetPromise("r2")->HasHandler());
|
|
CHECK(!GetPromise("r3")->HasHandler());
|
|
CHECK(!GetPromise("r4")->HasHandler());
|
|
CHECK_EQ(1, promise_reject_counter);
|
|
CHECK_EQ(1, promise_revoke_counter);
|
|
CHECK(
|
|
GetPromise("revoked")->Equals(env.local(), GetPromise("r2")).FromJust());
|
|
CHECK(RejectValue()->Equals(env.local(), v8_str("rrr")).FromJust());
|
|
|
|
// Add reject handlers to r4.
|
|
CompileRun("var r5 = r4.then(function() {}, function() {});");
|
|
CHECK(GetPromise("r0")->HasHandler());
|
|
CHECK(!GetPromise("r1")->HasHandler());
|
|
CHECK(GetPromise("r2")->HasHandler());
|
|
CHECK(!GetPromise("r3")->HasHandler());
|
|
CHECK(GetPromise("r4")->HasHandler());
|
|
CHECK(!GetPromise("r5")->HasHandler());
|
|
CHECK_EQ(1, promise_reject_counter);
|
|
CHECK_EQ(1, promise_revoke_counter);
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Create promise s0, which has three handlers, none of which handle rejects.
|
|
CompileRun(
|
|
"var s0 = new Promise( \n"
|
|
" function(res, rej) { \n"
|
|
" reject = rej; \n"
|
|
" } \n"
|
|
"); \n"
|
|
"var s1 = s0.then(function() {}); \n"
|
|
"var s2 = s0.then(function() {}); \n"
|
|
"var s3 = s0.then(function() {}); \n");
|
|
CHECK(GetPromise("s0")->HasHandler());
|
|
CHECK(!GetPromise("s1")->HasHandler());
|
|
CHECK(!GetPromise("s2")->HasHandler());
|
|
CHECK(!GetPromise("s3")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
|
|
// Reject s0.
|
|
CompileRun("reject('sss')");
|
|
CHECK(GetPromise("s0")->HasHandler());
|
|
CHECK(!GetPromise("s1")->HasHandler());
|
|
CHECK(!GetPromise("s2")->HasHandler());
|
|
CHECK(!GetPromise("s3")->HasHandler());
|
|
CHECK_EQ(3, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
CHECK(RejectValue()->Equals(env.local(), v8_str("sss")).FromJust());
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Swallowed exceptions in the Promise constructor.
|
|
CompileRun(
|
|
"var v0 = new Promise(\n"
|
|
" function(res, rej) {\n"
|
|
" res(1);\n"
|
|
" throw new Error();\n"
|
|
" }\n"
|
|
");\n");
|
|
CHECK(!GetPromise("v0")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
CHECK_EQ(1, promise_reject_after_resolved_counter);
|
|
CHECK_EQ(0, promise_resolve_after_resolved_counter);
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Duplication resolve.
|
|
CompileRun(
|
|
"var r;\n"
|
|
"var y0 = new Promise(\n"
|
|
" function(res, rej) {\n"
|
|
" r = res;\n"
|
|
" throw new Error();\n"
|
|
" }\n"
|
|
");\n"
|
|
"r(1);\n");
|
|
CHECK(!GetPromise("y0")->HasHandler());
|
|
CHECK_EQ(1, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
CHECK_EQ(0, promise_reject_after_resolved_counter);
|
|
CHECK_EQ(1, promise_resolve_after_resolved_counter);
|
|
|
|
// Test stack frames.
|
|
env->GetIsolate()->SetCaptureStackTraceForUncaughtExceptions(true);
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Create promise t0, which is rejected in the constructor with an error.
|
|
CompileRunWithOrigin(
|
|
"var t0 = new Promise( \n"
|
|
" function(res, rej) { \n"
|
|
" reference_error; \n"
|
|
" } \n"
|
|
"); \n",
|
|
"pro", 0, 0);
|
|
CHECK(!GetPromise("t0")->HasHandler());
|
|
CHECK_EQ(1, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
CHECK_EQ(2, promise_reject_frame_count);
|
|
CHECK_EQ(3, promise_reject_line_number);
|
|
CHECK_EQ(5, promise_reject_column_number);
|
|
CHECK_EQ(3, promise_reject_msg_line_number);
|
|
CHECK_EQ(5, promise_reject_msg_column_number);
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Create promise u0 and chain u1 to it, which is rejected via throw.
|
|
CompileRunWithOrigin(
|
|
"var u0 = Promise.resolve(); \n"
|
|
"var u1 = u0.then( \n"
|
|
" function() { \n"
|
|
" (function() { \n"
|
|
" throw new Error(); \n"
|
|
" })(); \n"
|
|
" } \n"
|
|
" ); \n",
|
|
"pro", 0, 0);
|
|
CHECK(GetPromise("u0")->HasHandler());
|
|
CHECK(!GetPromise("u1")->HasHandler());
|
|
CHECK_EQ(1, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
CHECK_EQ(2, promise_reject_frame_count);
|
|
CHECK_EQ(5, promise_reject_line_number);
|
|
CHECK_EQ(23, promise_reject_column_number);
|
|
CHECK_EQ(5, promise_reject_msg_line_number);
|
|
CHECK_EQ(23, promise_reject_msg_column_number);
|
|
|
|
// Throw in u3, which handles u1's rejection.
|
|
CompileRunWithOrigin(
|
|
"function f() { \n"
|
|
" return (function() { \n"
|
|
" return new Error(); \n"
|
|
" })(); \n"
|
|
"} \n"
|
|
"var u2 = Promise.reject(f()); \n"
|
|
"var u3 = u1.catch( \n"
|
|
" function() { \n"
|
|
" return u2; \n"
|
|
" } \n"
|
|
" ); \n",
|
|
"pro", 0, 0);
|
|
CHECK(GetPromise("u0")->HasHandler());
|
|
CHECK(GetPromise("u1")->HasHandler());
|
|
CHECK(GetPromise("u2")->HasHandler());
|
|
CHECK(!GetPromise("u3")->HasHandler());
|
|
CHECK_EQ(3, promise_reject_counter);
|
|
CHECK_EQ(2, promise_revoke_counter);
|
|
CHECK_EQ(3, promise_reject_frame_count);
|
|
CHECK_EQ(3, promise_reject_line_number);
|
|
CHECK_EQ(12, promise_reject_column_number);
|
|
CHECK_EQ(3, promise_reject_msg_line_number);
|
|
CHECK_EQ(12, promise_reject_msg_column_number);
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Create promise rejected promise v0, which is incorrectly handled by v1
|
|
// via chaining cycle.
|
|
CompileRunWithOrigin(
|
|
"var v0 = Promise.reject(); \n"
|
|
"var v1 = v0.catch( \n"
|
|
" function() { \n"
|
|
" return v1; \n"
|
|
" } \n"
|
|
" ); \n",
|
|
"pro", 0, 0);
|
|
CHECK(GetPromise("v0")->HasHandler());
|
|
CHECK(!GetPromise("v1")->HasHandler());
|
|
CHECK_EQ(2, promise_reject_counter);
|
|
CHECK_EQ(1, promise_revoke_counter);
|
|
CHECK_EQ(0, promise_reject_frame_count);
|
|
CHECK_EQ(-1, promise_reject_line_number);
|
|
CHECK_EQ(-1, promise_reject_column_number);
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Create promise t1, which rejects by throwing syntax error from eval.
|
|
CompileRunWithOrigin(
|
|
"var t1 = new Promise( \n"
|
|
" function(res, rej) { \n"
|
|
" var content = '\\n\\\n"
|
|
" }'; \n"
|
|
" eval(content); \n"
|
|
" } \n"
|
|
"); \n",
|
|
"pro", 0, 0);
|
|
CHECK(!GetPromise("t1")->HasHandler());
|
|
CHECK_EQ(1, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
CHECK_EQ(2, promise_reject_frame_count);
|
|
CHECK_EQ(5, promise_reject_line_number);
|
|
CHECK_EQ(10, promise_reject_column_number);
|
|
CHECK_EQ(2, promise_reject_msg_line_number);
|
|
CHECK_EQ(7, promise_reject_msg_column_number);
|
|
}
|
|
|
|
TEST(PromiseRejectIsSharedCrossOrigin) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
isolate->SetPromiseRejectCallback(PromiseRejectCallback);
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Create promise p0.
|
|
CompileRun(
|
|
"var reject; \n"
|
|
"var p0 = new Promise( \n"
|
|
" function(res, rej) { \n"
|
|
" reject = rej; \n"
|
|
" } \n"
|
|
"); \n");
|
|
CHECK(!GetPromise("p0")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
// Not set because it's not yet rejected.
|
|
CHECK(!promise_reject_is_shared_cross_origin);
|
|
|
|
// Reject p0.
|
|
CompileRun("reject('ppp');");
|
|
CHECK_EQ(1, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
// Not set because the ScriptOriginOptions is from the script.
|
|
CHECK(!promise_reject_is_shared_cross_origin);
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Create promise p1
|
|
CompileRun(
|
|
"var reject; \n"
|
|
"var p1 = new Promise( \n"
|
|
" function(res, rej) { \n"
|
|
" reject = rej; \n"
|
|
" } \n"
|
|
"); \n");
|
|
CHECK(!GetPromise("p1")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
// Not set because it's not yet rejected.
|
|
CHECK(!promise_reject_is_shared_cross_origin);
|
|
|
|
// Add resolve handler (and default reject handler) to p1.
|
|
CompileRun("var p2 = p1.then(function(){});");
|
|
CHECK(GetPromise("p1")->HasHandler());
|
|
CHECK(!GetPromise("p2")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
|
|
// Reject p1.
|
|
CompileRun("reject('ppp');");
|
|
CHECK_EQ(1, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
// Set because the event is from an empty script.
|
|
CHECK(promise_reject_is_shared_cross_origin);
|
|
}
|
|
|
|
TEST(PromiseRejectMarkAsHandled) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
isolate->SetPromiseRejectCallback(PromiseRejectCallback);
|
|
|
|
ResetPromiseStates();
|
|
|
|
// Create promise p0.
|
|
CompileRun(
|
|
"var reject; \n"
|
|
"var p0 = new Promise( \n"
|
|
" function(res, rej) { \n"
|
|
" reject = rej; \n"
|
|
" } \n"
|
|
"); \n");
|
|
CHECK(!GetPromise("p0")->HasHandler());
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
GetPromise("p0")->MarkAsHandled();
|
|
|
|
// Reject p0. promise_reject_counter shouldn't be incremented because
|
|
// it's marked as handled.
|
|
CompileRun("reject('ppp');");
|
|
CHECK_EQ(0, promise_reject_counter);
|
|
CHECK_EQ(0, promise_revoke_counter);
|
|
}
|
|
void PromiseRejectCallbackConstructError(
|
|
v8::PromiseRejectMessage reject_message) {
|
|
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
|
|
CHECK_EQ(v8::Promise::PromiseState::kRejected,
|
|
reject_message.GetPromise()->State());
|
|
USE(v8::Script::Compile(context, v8_str("new Error('test')"))
|
|
.ToLocalChecked()
|
|
->Run(context));
|
|
}
|
|
|
|
TEST(PromiseRejectCallbackConstructError) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
isolate->SetPromiseRejectCallback(PromiseRejectCallbackConstructError);
|
|
|
|
ResetPromiseStates();
|
|
CompileRun(
|
|
"function f(p) {"
|
|
" p.catch(() => {});"
|
|
"};"
|
|
"%PrepareFunctionForOptimization(f);"
|
|
"f(Promise.reject());"
|
|
"f(Promise.reject());"
|
|
"%OptimizeFunctionOnNextCall(f);"
|
|
"let p = Promise.reject();"
|
|
"f(p);");
|
|
}
|
|
|
|
void SetPromise(const char* name, v8::Local<v8::Promise> promise) {
|
|
CcTest::global()
|
|
->Set(CcTest::isolate()->GetCurrentContext(), v8_str(name), promise)
|
|
.FromJust();
|
|
}
|
|
|
|
class PromiseHookData {
|
|
public:
|
|
int before_hook_count = 0;
|
|
int after_hook_count = 0;
|
|
int promise_hook_count = 0;
|
|
int parent_promise_count = 0;
|
|
bool check_value = true;
|
|
std::string promise_hook_value;
|
|
|
|
void Reset() {
|
|
before_hook_count = 0;
|
|
after_hook_count = 0;
|
|
promise_hook_count = 0;
|
|
parent_promise_count = 0;
|
|
check_value = true;
|
|
promise_hook_value = "";
|
|
}
|
|
};
|
|
|
|
PromiseHookData* promise_hook_data;
|
|
|
|
void CustomPromiseHook(v8::PromiseHookType type, v8::Local<v8::Promise> promise,
|
|
v8::Local<v8::Value> parentPromise) {
|
|
promise_hook_data->promise_hook_count++;
|
|
switch (type) {
|
|
case v8::PromiseHookType::kInit:
|
|
SetPromise("init", promise);
|
|
|
|
if (!parentPromise->IsUndefined()) {
|
|
promise_hook_data->parent_promise_count++;
|
|
SetPromise("parent", v8::Local<v8::Promise>::Cast(parentPromise));
|
|
}
|
|
|
|
break;
|
|
case v8::PromiseHookType::kResolve:
|
|
SetPromise("resolve", promise);
|
|
break;
|
|
case v8::PromiseHookType::kBefore:
|
|
promise_hook_data->before_hook_count++;
|
|
CHECK(promise_hook_data->before_hook_count >
|
|
promise_hook_data->after_hook_count);
|
|
CHECK(CcTest::global()
|
|
->Get(CcTest::isolate()->GetCurrentContext(), v8_str("value"))
|
|
.ToLocalChecked()
|
|
->Equals(CcTest::isolate()->GetCurrentContext(), v8_str(""))
|
|
.FromJust());
|
|
SetPromise("before", promise);
|
|
break;
|
|
case v8::PromiseHookType::kAfter:
|
|
promise_hook_data->after_hook_count++;
|
|
CHECK(promise_hook_data->after_hook_count <=
|
|
promise_hook_data->before_hook_count);
|
|
if (promise_hook_data->check_value) {
|
|
CHECK(
|
|
CcTest::global()
|
|
->Get(CcTest::isolate()->GetCurrentContext(), v8_str("value"))
|
|
.ToLocalChecked()
|
|
->Equals(CcTest::isolate()->GetCurrentContext(),
|
|
v8_str(promise_hook_data->promise_hook_value.c_str()))
|
|
.FromJust());
|
|
}
|
|
SetPromise("after", promise);
|
|
break;
|
|
}
|
|
}
|
|
|
|
TEST(PromiseHook) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::Object> global = CcTest::global();
|
|
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
|
|
|
|
promise_hook_data = new PromiseHookData();
|
|
isolate->SetPromiseHook(CustomPromiseHook);
|
|
|
|
// Test that an initialized promise is passed to init. Other hooks
|
|
// can not have un initialized promise.
|
|
promise_hook_data->check_value = false;
|
|
CompileRun("var p = new Promise(() => {});");
|
|
|
|
auto init_promise = global->Get(context, v8_str("init")).ToLocalChecked();
|
|
CHECK(GetPromise("p")->Equals(env.local(), init_promise).FromJust());
|
|
auto init_promise_obj = v8::Local<v8::Promise>::Cast(init_promise);
|
|
CHECK_EQ(init_promise_obj->State(), v8::Promise::PromiseState::kPending);
|
|
CHECK(!init_promise_obj->HasHandler());
|
|
|
|
promise_hook_data->Reset();
|
|
promise_hook_data->promise_hook_value = "fulfilled";
|
|
const char* source =
|
|
"var resolve, value = ''; \n"
|
|
"var p = new Promise(r => resolve = r); \n";
|
|
|
|
CompileRun(source);
|
|
init_promise = global->Get(context, v8_str("init")).ToLocalChecked();
|
|
CHECK(GetPromise("p")->Equals(env.local(), init_promise).FromJust());
|
|
CHECK_EQ(1, promise_hook_data->promise_hook_count);
|
|
CHECK_EQ(0, promise_hook_data->parent_promise_count);
|
|
|
|
CompileRun("var p1 = p.then(() => { value = 'fulfilled'; }); \n");
|
|
init_promise = global->Get(context, v8_str("init")).ToLocalChecked();
|
|
auto parent_promise = global->Get(context, v8_str("parent")).ToLocalChecked();
|
|
CHECK(GetPromise("p1")->Equals(env.local(), init_promise).FromJust());
|
|
CHECK(GetPromise("p")->Equals(env.local(), parent_promise).FromJust());
|
|
CHECK_EQ(2, promise_hook_data->promise_hook_count);
|
|
CHECK_EQ(1, promise_hook_data->parent_promise_count);
|
|
|
|
CompileRun("resolve(); \n");
|
|
auto resolve_promise =
|
|
global->Get(context, v8_str("resolve")).ToLocalChecked();
|
|
auto before_promise = global->Get(context, v8_str("before")).ToLocalChecked();
|
|
auto after_promise = global->Get(context, v8_str("after")).ToLocalChecked();
|
|
CHECK(GetPromise("p1")->Equals(env.local(), before_promise).FromJust());
|
|
CHECK(GetPromise("p1")->Equals(env.local(), after_promise).FromJust());
|
|
CHECK(GetPromise("p1")->Equals(env.local(), resolve_promise).FromJust());
|
|
CHECK_EQ(6, promise_hook_data->promise_hook_count);
|
|
|
|
CompileRun("value = ''; var p2 = p1.then(() => { value = 'fulfilled' }); \n");
|
|
init_promise = global->Get(context, v8_str("init")).ToLocalChecked();
|
|
parent_promise = global->Get(context, v8_str("parent")).ToLocalChecked();
|
|
resolve_promise = global->Get(context, v8_str("resolve")).ToLocalChecked();
|
|
before_promise = global->Get(context, v8_str("before")).ToLocalChecked();
|
|
after_promise = global->Get(context, v8_str("after")).ToLocalChecked();
|
|
CHECK(GetPromise("p2")->Equals(env.local(), init_promise).FromJust());
|
|
CHECK(GetPromise("p1")->Equals(env.local(), parent_promise).FromJust());
|
|
CHECK(GetPromise("p2")->Equals(env.local(), before_promise).FromJust());
|
|
CHECK(GetPromise("p2")->Equals(env.local(), after_promise).FromJust());
|
|
CHECK(GetPromise("p2")->Equals(env.local(), resolve_promise).FromJust());
|
|
CHECK_EQ(10, promise_hook_data->promise_hook_count);
|
|
|
|
promise_hook_data->Reset();
|
|
promise_hook_data->promise_hook_value = "rejected";
|
|
source =
|
|
"var reject, value = ''; \n"
|
|
"var p = new Promise((_, r) => reject = r); \n";
|
|
|
|
CompileRun(source);
|
|
init_promise = global->Get(context, v8_str("init")).ToLocalChecked();
|
|
CHECK(GetPromise("p")->Equals(env.local(), init_promise).FromJust());
|
|
CHECK_EQ(1, promise_hook_data->promise_hook_count);
|
|
CHECK_EQ(0, promise_hook_data->parent_promise_count);
|
|
|
|
CompileRun("var p1 = p.catch(() => { value = 'rejected'; }); \n");
|
|
init_promise = global->Get(context, v8_str("init")).ToLocalChecked();
|
|
parent_promise = global->Get(context, v8_str("parent")).ToLocalChecked();
|
|
CHECK(GetPromise("p1")->Equals(env.local(), init_promise).FromJust());
|
|
CHECK(GetPromise("p")->Equals(env.local(), parent_promise).FromJust());
|
|
CHECK_EQ(2, promise_hook_data->promise_hook_count);
|
|
CHECK_EQ(1, promise_hook_data->parent_promise_count);
|
|
|
|
CompileRun("reject(); \n");
|
|
resolve_promise = global->Get(context, v8_str("resolve")).ToLocalChecked();
|
|
before_promise = global->Get(context, v8_str("before")).ToLocalChecked();
|
|
after_promise = global->Get(context, v8_str("after")).ToLocalChecked();
|
|
CHECK(GetPromise("p1")->Equals(env.local(), before_promise).FromJust());
|
|
CHECK(GetPromise("p1")->Equals(env.local(), after_promise).FromJust());
|
|
CHECK(GetPromise("p1")->Equals(env.local(), resolve_promise).FromJust());
|
|
CHECK_EQ(6, promise_hook_data->promise_hook_count);
|
|
|
|
promise_hook_data->Reset();
|
|
promise_hook_data->promise_hook_value = "Promise.resolve";
|
|
source =
|
|
"var value = ''; \n"
|
|
"var p = Promise.resolve('Promise.resolve'); \n";
|
|
|
|
CompileRun(source);
|
|
init_promise = global->Get(context, v8_str("init")).ToLocalChecked();
|
|
CHECK(GetPromise("p")->Equals(env.local(), init_promise).FromJust());
|
|
// init hook and resolve hook
|
|
CHECK_EQ(2, promise_hook_data->promise_hook_count);
|
|
CHECK_EQ(0, promise_hook_data->parent_promise_count);
|
|
resolve_promise = global->Get(context, v8_str("resolve")).ToLocalChecked();
|
|
CHECK(GetPromise("p")->Equals(env.local(), resolve_promise).FromJust());
|
|
|
|
CompileRun("var p1 = p.then((v) => { value = v; }); \n");
|
|
init_promise = global->Get(context, v8_str("init")).ToLocalChecked();
|
|
resolve_promise = global->Get(context, v8_str("resolve")).ToLocalChecked();
|
|
parent_promise = global->Get(context, v8_str("parent")).ToLocalChecked();
|
|
before_promise = global->Get(context, v8_str("before")).ToLocalChecked();
|
|
after_promise = global->Get(context, v8_str("after")).ToLocalChecked();
|
|
CHECK(GetPromise("p1")->Equals(env.local(), init_promise).FromJust());
|
|
CHECK(GetPromise("p1")->Equals(env.local(), resolve_promise).FromJust());
|
|
CHECK(GetPromise("p")->Equals(env.local(), parent_promise).FromJust());
|
|
CHECK(GetPromise("p1")->Equals(env.local(), before_promise).FromJust());
|
|
CHECK(GetPromise("p1")->Equals(env.local(), after_promise).FromJust());
|
|
CHECK_EQ(6, promise_hook_data->promise_hook_count);
|
|
CHECK_EQ(1, promise_hook_data->parent_promise_count);
|
|
|
|
promise_hook_data->Reset();
|
|
source =
|
|
"var resolve, value = ''; \n"
|
|
"var p = new Promise((_, r) => resolve = r); \n";
|
|
|
|
CompileRun(source);
|
|
init_promise = global->Get(context, v8_str("init")).ToLocalChecked();
|
|
CHECK(GetPromise("p")->Equals(env.local(), init_promise).FromJust());
|
|
CHECK_EQ(1, promise_hook_data->promise_hook_count);
|
|
CHECK_EQ(0, promise_hook_data->parent_promise_count);
|
|
|
|
CompileRun("resolve(); \n");
|
|
resolve_promise = global->Get(context, v8_str("resolve")).ToLocalChecked();
|
|
CHECK(GetPromise("p")->Equals(env.local(), resolve_promise).FromJust());
|
|
CHECK_EQ(2, promise_hook_data->promise_hook_count);
|
|
|
|
promise_hook_data->Reset();
|
|
source =
|
|
"var reject, value = ''; \n"
|
|
"var p = new Promise((_, r) => reject = r); \n";
|
|
|
|
CompileRun(source);
|
|
init_promise = global->Get(context, v8_str("init")).ToLocalChecked();
|
|
CHECK(GetPromise("p")->Equals(env.local(), init_promise).FromJust());
|
|
CHECK_EQ(1, promise_hook_data->promise_hook_count);
|
|
CHECK_EQ(0, promise_hook_data->parent_promise_count);
|
|
|
|
CompileRun("reject(); \n");
|
|
resolve_promise = global->Get(context, v8_str("resolve")).ToLocalChecked();
|
|
CHECK(GetPromise("p")->Equals(env.local(), resolve_promise).FromJust());
|
|
CHECK_EQ(2, promise_hook_data->promise_hook_count);
|
|
|
|
promise_hook_data->Reset();
|
|
// This test triggers after callbacks right after each other, so
|
|
// lets just check the value at the end.
|
|
promise_hook_data->check_value = false;
|
|
promise_hook_data->promise_hook_value = "Promise.all";
|
|
source =
|
|
"var resolve, value = ''; \n"
|
|
"var tempPromise = new Promise(r => resolve = r); \n"
|
|
"var p = Promise.all([tempPromise]);\n "
|
|
"var p1 = p.then(v => value = v[0]); \n";
|
|
|
|
CompileRun(source);
|
|
// 1) init hook (tempPromise)
|
|
// 2) init hook (p)
|
|
// 3) init hook (throwaway Promise in Promise.all, p)
|
|
// 4) init hook (p1, p)
|
|
CHECK_EQ(4, promise_hook_data->promise_hook_count);
|
|
CHECK_EQ(2, promise_hook_data->parent_promise_count);
|
|
|
|
promise_hook_data->promise_hook_value = "Promise.all";
|
|
CompileRun("resolve('Promise.all'); \n");
|
|
resolve_promise = global->Get(context, v8_str("resolve")).ToLocalChecked();
|
|
CHECK(GetPromise("p1")->Equals(env.local(), resolve_promise).FromJust());
|
|
// 5) resolve hook (tempPromise)
|
|
// 6) resolve hook (throwaway Promise in Promise.all)
|
|
// 6) before hook (throwaway Promise in Promise.all)
|
|
// 7) after hook (throwaway Promise in Promise.all)
|
|
// 8) before hook (p)
|
|
// 9) after hook (p)
|
|
// 10) resolve hook (p1)
|
|
// 11) before hook (p1)
|
|
// 12) after hook (p1)
|
|
CHECK_EQ(12, promise_hook_data->promise_hook_count);
|
|
CHECK(CcTest::global()
|
|
->Get(CcTest::isolate()->GetCurrentContext(), v8_str("value"))
|
|
.ToLocalChecked()
|
|
->Equals(CcTest::isolate()->GetCurrentContext(),
|
|
v8_str(promise_hook_data->promise_hook_value.c_str()))
|
|
.FromJust());
|
|
|
|
promise_hook_data->Reset();
|
|
// This test triggers after callbacks right after each other, so
|
|
// lets just check the value at the end.
|
|
promise_hook_data->check_value = false;
|
|
promise_hook_data->promise_hook_value = "Promise.race";
|
|
source =
|
|
"var resolve, value = ''; \n"
|
|
"var tempPromise = new Promise(r => resolve = r); \n"
|
|
"var p = Promise.race([tempPromise]);\n "
|
|
"var p1 = p.then(v => value = v); \n";
|
|
|
|
CompileRun(source);
|
|
// 1) init hook (tempPromise)
|
|
// 2) init hook (p)
|
|
// 3) init hook (throwaway Promise in Promise.race, p)
|
|
// 4) init hook (p1, p)
|
|
CHECK_EQ(4, promise_hook_data->promise_hook_count);
|
|
CHECK_EQ(2, promise_hook_data->parent_promise_count);
|
|
|
|
promise_hook_data->promise_hook_value = "Promise.race";
|
|
CompileRun("resolve('Promise.race'); \n");
|
|
resolve_promise = global->Get(context, v8_str("resolve")).ToLocalChecked();
|
|
CHECK(GetPromise("p1")->Equals(env.local(), resolve_promise).FromJust());
|
|
// 5) resolve hook (tempPromise)
|
|
// 6) resolve hook (throwaway Promise in Promise.race)
|
|
// 6) before hook (throwaway Promise in Promise.race)
|
|
// 7) after hook (throwaway Promise in Promise.race)
|
|
// 8) before hook (p)
|
|
// 9) after hook (p)
|
|
// 10) resolve hook (p1)
|
|
// 11) before hook (p1)
|
|
// 12) after hook (p1)
|
|
CHECK_EQ(12, promise_hook_data->promise_hook_count);
|
|
CHECK(CcTest::global()
|
|
->Get(CcTest::isolate()->GetCurrentContext(), v8_str("value"))
|
|
.ToLocalChecked()
|
|
->Equals(CcTest::isolate()->GetCurrentContext(),
|
|
v8_str(promise_hook_data->promise_hook_value.c_str()))
|
|
.FromJust());
|
|
|
|
promise_hook_data->Reset();
|
|
promise_hook_data->promise_hook_value = "subclass";
|
|
source =
|
|
"var resolve, value = '';\n"
|
|
"class MyPromise extends Promise { \n"
|
|
" then(onFulfilled, onRejected) { \n"
|
|
" return super.then(onFulfilled, onRejected); \n"
|
|
" };\n"
|
|
"};\n"
|
|
"var p = new MyPromise(r => resolve = r);\n";
|
|
|
|
CompileRun(source);
|
|
// 1) init hook (p)
|
|
CHECK_EQ(1, promise_hook_data->promise_hook_count);
|
|
|
|
CompileRun("var p1 = p.then(() => value = 'subclass');\n");
|
|
// 2) init hook (p1)
|
|
CHECK_EQ(2, promise_hook_data->promise_hook_count);
|
|
|
|
CompileRun("resolve();\n");
|
|
resolve_promise = global->Get(context, v8_str("resolve")).ToLocalChecked();
|
|
before_promise = global->Get(context, v8_str("before")).ToLocalChecked();
|
|
after_promise = global->Get(context, v8_str("after")).ToLocalChecked();
|
|
CHECK(GetPromise("p1")->Equals(env.local(), before_promise).FromJust());
|
|
CHECK(GetPromise("p1")->Equals(env.local(), after_promise).FromJust());
|
|
CHECK(GetPromise("p1")->Equals(env.local(), resolve_promise).FromJust());
|
|
// 3) resolve hook (p)
|
|
// 4) before hook (p)
|
|
// 5) after hook (p)
|
|
// 6) resolve hook (p1)
|
|
CHECK_EQ(6, promise_hook_data->promise_hook_count);
|
|
|
|
promise_hook_data->Reset();
|
|
source =
|
|
"class X extends Promise {\n"
|
|
" static get [Symbol.species]() {\n"
|
|
" return Y;\n"
|
|
" }\n"
|
|
"}\n"
|
|
"class Y {\n"
|
|
" constructor(executor) {\n"
|
|
" return new Proxy(new Promise(executor), {});\n"
|
|
" }\n"
|
|
"}\n"
|
|
"var x = X.resolve().then(() => {});\n";
|
|
|
|
CompileRun(source);
|
|
|
|
promise_hook_data->Reset();
|
|
source =
|
|
"var resolve, value = '';\n"
|
|
"var p = new Promise(r => resolve = r);\n";
|
|
|
|
CompileRun(source);
|
|
CHECK_EQ(v8::Promise::kPending, GetPromise("p")->State());
|
|
CompileRun("resolve(Promise.resolve(value));\n");
|
|
CHECK_EQ(v8::Promise::kFulfilled, GetPromise("p")->State());
|
|
CHECK_EQ(11, promise_hook_data->promise_hook_count);
|
|
|
|
promise_hook_data->Reset();
|
|
source =
|
|
"var p = Promise.resolve({\n"
|
|
" then(r) {\n"
|
|
" r();\n"
|
|
" }\n"
|
|
"});";
|
|
CompileRun(source);
|
|
CHECK_EQ(GetPromise("p")->State(), v8::Promise::kFulfilled);
|
|
CHECK_EQ(promise_hook_data->promise_hook_count, 5);
|
|
|
|
delete promise_hook_data;
|
|
isolate->SetPromiseHook(nullptr);
|
|
}
|
|
|
|
|
|
TEST(EvalWithSourceURLInMessageScriptResourceNameOrSourceURL) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
const char *source =
|
|
"function outer() {\n"
|
|
" var scriptContents = \"function foo() { FAIL.FAIL; }\\\n"
|
|
" //# sourceURL=source_url\";\n"
|
|
" eval(scriptContents);\n"
|
|
" foo(); }\n"
|
|
"outer();\n"
|
|
"//# sourceURL=outer_url";
|
|
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
CompileRun(source);
|
|
CHECK(try_catch.HasCaught());
|
|
|
|
Local<v8::Message> message = try_catch.Message();
|
|
Local<Value> sourceURL = message->GetScriptOrigin().ResourceName();
|
|
CHECK_EQ(0, strcmp(*v8::String::Utf8Value(context->GetIsolate(), sourceURL),
|
|
"source_url"));
|
|
}
|
|
|
|
|
|
TEST(RecursionWithSourceURLInMessageScriptResourceNameOrSourceURL) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
const char *source =
|
|
"function outer() {\n"
|
|
" var scriptContents = \"function boo(){ boo(); }\\\n"
|
|
" //# sourceURL=source_url\";\n"
|
|
" eval(scriptContents);\n"
|
|
" boo(); }\n"
|
|
"outer();\n"
|
|
"//# sourceURL=outer_url";
|
|
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
CompileRun(source);
|
|
CHECK(try_catch.HasCaught());
|
|
|
|
Local<v8::Message> message = try_catch.Message();
|
|
Local<Value> sourceURL = message->GetScriptOrigin().ResourceName();
|
|
CHECK_EQ(0, strcmp(*v8::String::Utf8Value(context->GetIsolate(), sourceURL),
|
|
"source_url"));
|
|
}
|
|
|
|
|
|
static void CreateGarbageInOldSpace() {
|
|
i::Factory* factory = CcTest::i_isolate()->factory();
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
i::AlwaysAllocateScopeForTesting always_allocate(CcTest::i_isolate()->heap());
|
|
for (int i = 0; i < 1000; i++) {
|
|
factory->NewFixedArray(1000, i::AllocationType::kOld);
|
|
}
|
|
}
|
|
|
|
|
|
// Test that idle notification can be handled and eventually collects garbage.
|
|
TEST(TestIdleNotification) {
|
|
if (!i::FLAG_incremental_marking) return;
|
|
ManualGCScope manual_gc_scope;
|
|
const intptr_t MB = 1024 * 1024;
|
|
const double IdlePauseInSeconds = 1.0;
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
intptr_t initial_size = CcTest::heap()->SizeOfObjects();
|
|
CreateGarbageInOldSpace();
|
|
intptr_t size_with_garbage = CcTest::heap()->SizeOfObjects();
|
|
CHECK_GT(size_with_garbage, initial_size + MB);
|
|
bool finished = false;
|
|
for (int i = 0; i < 200 && !finished; i++) {
|
|
if (i < 10 && CcTest::heap()->incremental_marking()->IsStopped()) {
|
|
CcTest::heap()->StartIdleIncrementalMarking(
|
|
i::GarbageCollectionReason::kTesting);
|
|
}
|
|
finished = env->GetIsolate()->IdleNotificationDeadline(
|
|
(v8::base::TimeTicks::HighResolutionNow().ToInternalValue() /
|
|
static_cast<double>(v8::base::Time::kMicrosecondsPerSecond)) +
|
|
IdlePauseInSeconds);
|
|
if (CcTest::heap()->mark_compact_collector()->sweeping_in_progress()) {
|
|
CcTest::heap()->mark_compact_collector()->EnsureSweepingCompleted();
|
|
}
|
|
}
|
|
intptr_t final_size = CcTest::heap()->SizeOfObjects();
|
|
CHECK(finished);
|
|
CHECK_LT(final_size, initial_size + 1);
|
|
}
|
|
|
|
TEST(TestMemorySavingsMode) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::internal::Isolate* i_isolate =
|
|
reinterpret_cast<v8::internal::Isolate*>(isolate);
|
|
CHECK(!i_isolate->IsMemorySavingsModeActive());
|
|
isolate->EnableMemorySavingsMode();
|
|
CHECK(i_isolate->IsMemorySavingsModeActive());
|
|
isolate->DisableMemorySavingsMode();
|
|
CHECK(!i_isolate->IsMemorySavingsModeActive());
|
|
}
|
|
|
|
TEST(Regress2333) {
|
|
LocalContext env;
|
|
for (int i = 0; i < 3; i++) {
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
}
|
|
}
|
|
|
|
static uint32_t* stack_limit;
|
|
|
|
static void GetStackLimitCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
stack_limit = reinterpret_cast<uint32_t*>(
|
|
CcTest::i_isolate()->stack_guard()->real_climit());
|
|
}
|
|
|
|
|
|
// 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;
|
|
}
|
|
|
|
|
|
// We need at least 165kB for an x64 debug build with clang and ASAN.
|
|
static const int stack_breathing_room = 256 * i::KB;
|
|
|
|
|
|
TEST(SetStackLimit) {
|
|
uint32_t* set_limit = ComputeStackLimit(stack_breathing_room);
|
|
|
|
// Set stack limit.
|
|
CcTest::isolate()->SetStackLimit(reinterpret_cast<uintptr_t>(set_limit));
|
|
|
|
// Execute a script.
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(env->GetIsolate(), GetStackLimitCallback);
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("get_stack_limit"), fun)
|
|
.FromJust());
|
|
CompileRun("get_stack_limit();");
|
|
|
|
CHECK(stack_limit == set_limit);
|
|
}
|
|
|
|
|
|
TEST(SetStackLimitInThread) {
|
|
uint32_t* set_limit;
|
|
{
|
|
v8::Locker locker(CcTest::isolate());
|
|
set_limit = ComputeStackLimit(stack_breathing_room);
|
|
|
|
// Set stack limit.
|
|
CcTest::isolate()->SetStackLimit(reinterpret_cast<uintptr_t>(set_limit));
|
|
|
|
// Execute a script.
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
LocalContext env;
|
|
Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(CcTest::isolate(), GetStackLimitCallback);
|
|
Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("get_stack_limit"), fun)
|
|
.FromJust());
|
|
CompileRun("get_stack_limit();");
|
|
|
|
CHECK(stack_limit == set_limit);
|
|
}
|
|
{
|
|
v8::Locker locker(CcTest::isolate());
|
|
CHECK(stack_limit == set_limit);
|
|
}
|
|
}
|
|
|
|
THREADED_TEST(GetHeapStatistics) {
|
|
LocalContext c1;
|
|
v8::HandleScope scope(c1->GetIsolate());
|
|
v8::HeapStatistics heap_statistics;
|
|
CHECK_EQ(0u, heap_statistics.total_heap_size());
|
|
CHECK_EQ(0u, heap_statistics.used_heap_size());
|
|
c1->GetIsolate()->GetHeapStatistics(&heap_statistics);
|
|
CHECK_NE(static_cast<int>(heap_statistics.total_heap_size()), 0);
|
|
if (!v8::internal::FLAG_enable_third_party_heap) {
|
|
// TODO(wenyuzhao): Get used size from third_party_heap interface
|
|
CHECK_NE(static_cast<int>(heap_statistics.used_heap_size()), 0);
|
|
}
|
|
}
|
|
|
|
TEST(GetHeapSpaceStatistics) {
|
|
LocalContext c1;
|
|
v8::Isolate* isolate = c1->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::HeapStatistics heap_statistics;
|
|
|
|
// Force allocation in LO_SPACE so that every space has non-zero size.
|
|
v8::internal::Isolate* i_isolate =
|
|
reinterpret_cast<v8::internal::Isolate*>(isolate);
|
|
auto unused = i_isolate->factory()->TryNewFixedArray(512 * 1024,
|
|
i::AllocationType::kOld);
|
|
USE(unused);
|
|
|
|
isolate->GetHeapStatistics(&heap_statistics);
|
|
|
|
// Ensure that the sum of all the spaces matches the totals from
|
|
// GetHeapSpaceStatics.
|
|
size_t total_size = 0u;
|
|
size_t total_used_size = 0u;
|
|
size_t total_available_size = 0u;
|
|
size_t total_physical_size = 0u;
|
|
for (size_t i = 0; i < isolate->NumberOfHeapSpaces(); ++i) {
|
|
v8::HeapSpaceStatistics space_statistics;
|
|
isolate->GetHeapSpaceStatistics(&space_statistics, i);
|
|
CHECK_NOT_NULL(space_statistics.space_name());
|
|
total_size += space_statistics.space_size();
|
|
total_used_size += space_statistics.space_used_size();
|
|
total_available_size += space_statistics.space_available_size();
|
|
total_physical_size += space_statistics.physical_space_size();
|
|
}
|
|
total_available_size += CcTest::heap()->memory_allocator()->Available();
|
|
|
|
CHECK_EQ(total_size, heap_statistics.total_heap_size());
|
|
CHECK_EQ(total_used_size, heap_statistics.used_heap_size());
|
|
CHECK_EQ(total_available_size, heap_statistics.total_available_size());
|
|
CHECK_EQ(total_physical_size, heap_statistics.total_physical_size());
|
|
}
|
|
|
|
TEST(NumberOfNativeContexts) {
|
|
static const size_t kNumTestContexts = 10;
|
|
i::Isolate* isolate = CcTest::i_isolate();
|
|
i::HandleScope scope(isolate);
|
|
v8::Global<v8::Context> context[kNumTestContexts];
|
|
v8::HeapStatistics heap_statistics;
|
|
CHECK_EQ(0u, heap_statistics.number_of_native_contexts());
|
|
CcTest::isolate()->GetHeapStatistics(&heap_statistics);
|
|
CHECK_EQ(0u, heap_statistics.number_of_native_contexts());
|
|
for (size_t i = 0; i < kNumTestContexts; i++) {
|
|
i::HandleScope inner(isolate);
|
|
context[i].Reset(CcTest::isolate(), v8::Context::New(CcTest::isolate()));
|
|
CcTest::isolate()->GetHeapStatistics(&heap_statistics);
|
|
CHECK_EQ(i + 1, heap_statistics.number_of_native_contexts());
|
|
}
|
|
for (size_t i = 0; i < kNumTestContexts; i++) {
|
|
context[i].Reset();
|
|
CcTest::PreciseCollectAllGarbage();
|
|
CcTest::isolate()->GetHeapStatistics(&heap_statistics);
|
|
CHECK_EQ(kNumTestContexts - i - 1u,
|
|
heap_statistics.number_of_native_contexts());
|
|
}
|
|
}
|
|
|
|
TEST(NumberOfDetachedContexts) {
|
|
static const size_t kNumTestContexts = 10;
|
|
i::Isolate* isolate = CcTest::i_isolate();
|
|
i::HandleScope scope(isolate);
|
|
v8::Global<v8::Context> context[kNumTestContexts];
|
|
v8::HeapStatistics heap_statistics;
|
|
CHECK_EQ(0u, heap_statistics.number_of_detached_contexts());
|
|
CcTest::isolate()->GetHeapStatistics(&heap_statistics);
|
|
CHECK_EQ(0u, heap_statistics.number_of_detached_contexts());
|
|
for (size_t i = 0; i < kNumTestContexts; i++) {
|
|
i::HandleScope inner(isolate);
|
|
v8::Local<v8::Context> local = v8::Context::New(CcTest::isolate());
|
|
context[i].Reset(CcTest::isolate(), local);
|
|
local->DetachGlobal();
|
|
CcTest::isolate()->GetHeapStatistics(&heap_statistics);
|
|
CHECK_EQ(i + 1, heap_statistics.number_of_detached_contexts());
|
|
}
|
|
for (size_t i = 0; i < kNumTestContexts; i++) {
|
|
context[i].Reset();
|
|
CcTest::PreciseCollectAllGarbage();
|
|
CcTest::isolate()->GetHeapStatistics(&heap_statistics);
|
|
CHECK_EQ(kNumTestContexts - i - 1u,
|
|
heap_statistics.number_of_detached_contexts());
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
~VisitorImpl() override = default;
|
|
void VisitExternalString(v8::Local<v8::String> string) override {
|
|
CHECK(string->IsExternal());
|
|
if (string->IsExternalOneByte()) {
|
|
CHECK(!string->IsExternalTwoByte());
|
|
return;
|
|
}
|
|
CHECK(string->IsExternalTwoByte());
|
|
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(ExternalizeOldSpaceTwoByteCons) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
LocalContext env;
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::String> cons =
|
|
CompileRun("'Romeo Montague ' + 'Juliet Capulet'")
|
|
->ToString(env.local())
|
|
.ToLocalChecked();
|
|
CHECK(v8::Utils::OpenHandle(*cons)->IsConsString());
|
|
CcTest::CollectAllAvailableGarbage();
|
|
CHECK(CcTest::heap()->old_space()->Contains(*v8::Utils::OpenHandle(*cons)));
|
|
|
|
TestResource* resource = new TestResource(
|
|
AsciiToTwoByteString("Romeo Montague Juliet Capulet"));
|
|
cons->MakeExternal(resource);
|
|
|
|
CHECK(cons->IsExternalTwoByte());
|
|
CHECK(cons->IsExternal());
|
|
CHECK_EQ(resource, cons->GetExternalStringResource());
|
|
String::Encoding encoding;
|
|
CHECK_EQ(resource, cons->GetExternalStringResourceBase(&encoding));
|
|
CHECK_EQ(String::TWO_BYTE_ENCODING, encoding);
|
|
}
|
|
|
|
|
|
TEST(ExternalizeOldSpaceOneByteCons) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
LocalContext env;
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::String> cons =
|
|
CompileRun("'Romeo Montague ' + 'Juliet Capulet'")
|
|
->ToString(env.local())
|
|
.ToLocalChecked();
|
|
CHECK(v8::Utils::OpenHandle(*cons)->IsConsString());
|
|
CcTest::CollectAllAvailableGarbage();
|
|
CHECK(CcTest::heap()->old_space()->Contains(*v8::Utils::OpenHandle(*cons)));
|
|
|
|
TestOneByteResource* resource =
|
|
new TestOneByteResource(i::StrDup("Romeo Montague Juliet Capulet"));
|
|
cons->MakeExternal(resource);
|
|
|
|
CHECK(cons->IsExternalOneByte());
|
|
CHECK_EQ(resource, cons->GetExternalOneByteStringResource());
|
|
String::Encoding encoding;
|
|
CHECK_EQ(resource, cons->GetExternalStringResourceBase(&encoding));
|
|
CHECK_EQ(String::ONE_BYTE_ENCODING, encoding);
|
|
}
|
|
|
|
|
|
TEST(VisitExternalStrings) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
LocalContext env;
|
|
v8::HandleScope scope(isolate);
|
|
const char string[] = "Some string";
|
|
uint16_t* two_byte_string = AsciiToTwoByteString(string);
|
|
TestResource* resource[5];
|
|
resource[0] = new TestResource(two_byte_string);
|
|
v8::Local<v8::String> string0 =
|
|
v8::String::NewExternalTwoByte(env->GetIsolate(), resource[0])
|
|
.ToLocalChecked();
|
|
resource[1] = new TestResource(two_byte_string, nullptr, false);
|
|
v8::Local<v8::String> string1 =
|
|
v8::String::NewExternalTwoByte(env->GetIsolate(), resource[1])
|
|
.ToLocalChecked();
|
|
|
|
// Externalized symbol.
|
|
resource[2] = new TestResource(two_byte_string, nullptr, false);
|
|
v8::Local<v8::String> string2 = v8::String::NewFromUtf8Literal(
|
|
env->GetIsolate(), string, v8::NewStringType::kInternalized);
|
|
CHECK(string2->MakeExternal(resource[2]));
|
|
|
|
// Symbolized External.
|
|
resource[3] = new TestResource(AsciiToTwoByteString("Some other string"));
|
|
v8::Local<v8::String> string3 =
|
|
v8::String::NewExternalTwoByte(env->GetIsolate(), resource[3])
|
|
.ToLocalChecked();
|
|
CcTest::CollectAllAvailableGarbage(); // Tenure string.
|
|
// Turn into a symbol.
|
|
i::Handle<i::String> string3_i = v8::Utils::OpenHandle(*string3);
|
|
CHECK(!CcTest::i_isolate()->factory()->InternalizeString(
|
|
string3_i).is_null());
|
|
CHECK(string3_i->IsInternalizedString());
|
|
|
|
// Externalized one-byte string.
|
|
auto one_byte_resource =
|
|
new TestOneByteResource(i::StrDup(string), nullptr, 0);
|
|
v8::Local<v8::String> string4 =
|
|
String::NewExternalOneByte(env->GetIsolate(), one_byte_resource)
|
|
.ToLocalChecked();
|
|
|
|
// We need to add usages for string* to avoid warnings in GCC 4.7
|
|
CHECK(string0->IsExternalTwoByte());
|
|
CHECK(string1->IsExternalTwoByte());
|
|
CHECK(string2->IsExternalTwoByte());
|
|
CHECK(string3->IsExternalTwoByte());
|
|
CHECK(!string4->IsExternalTwoByte());
|
|
CHECK(string0->IsExternal());
|
|
CHECK(string1->IsExternal());
|
|
CHECK(string2->IsExternal());
|
|
CHECK(string3->IsExternal());
|
|
CHECK(string4->IsExternal());
|
|
CHECK(!string0->IsExternalOneByte());
|
|
CHECK(!string1->IsExternalOneByte());
|
|
CHECK(!string2->IsExternalOneByte());
|
|
CHECK(!string3->IsExternalOneByte());
|
|
CHECK(string4->IsExternalOneByte());
|
|
|
|
VisitorImpl visitor(resource);
|
|
isolate->VisitExternalResources(&visitor);
|
|
visitor.CheckVisitedResources();
|
|
}
|
|
|
|
|
|
TEST(ExternalStringCollectedAtTearDown) {
|
|
int destroyed = 0;
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
{ v8::Isolate::Scope isolate_scope(isolate);
|
|
v8::HandleScope handle_scope(isolate);
|
|
const char* s = "One string to test them all, one string to find them.";
|
|
TestOneByteResource* inscription =
|
|
new TestOneByteResource(i::StrDup(s), &destroyed);
|
|
v8::Local<v8::String> ring =
|
|
v8::String::NewExternalOneByte(isolate, inscription).ToLocalChecked();
|
|
// Ring is still alive. Orcs are roaming freely across our lands.
|
|
CHECK_EQ(0, destroyed);
|
|
USE(ring);
|
|
}
|
|
|
|
isolate->Dispose();
|
|
// Ring has been destroyed. Free Peoples of Middle-earth Rejoice.
|
|
CHECK_EQ(1, destroyed);
|
|
}
|
|
|
|
|
|
TEST(ExternalInternalizedStringCollectedAtTearDown) {
|
|
int destroyed = 0;
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
{ v8::Isolate::Scope isolate_scope(isolate);
|
|
LocalContext env(isolate);
|
|
v8::HandleScope handle_scope(isolate);
|
|
CompileRun("var ring = 'One string to test them all';");
|
|
const char* s = "One string to test them all";
|
|
TestOneByteResource* inscription =
|
|
new TestOneByteResource(i::StrDup(s), &destroyed);
|
|
v8::Local<v8::String> ring =
|
|
CompileRun("ring")->ToString(env.local()).ToLocalChecked();
|
|
CHECK(v8::Utils::OpenHandle(*ring)->IsInternalizedString());
|
|
ring->MakeExternal(inscription);
|
|
// Ring is still alive. Orcs are roaming freely across our lands.
|
|
CHECK_EQ(0, destroyed);
|
|
USE(ring);
|
|
}
|
|
|
|
isolate->Dispose();
|
|
// Ring has been destroyed. Free Peoples of Middle-earth Rejoice.
|
|
CHECK_EQ(1, destroyed);
|
|
}
|
|
|
|
|
|
TEST(ExternalInternalizedStringCollectedAtGC) {
|
|
int destroyed = 0;
|
|
{ LocalContext env;
|
|
v8::HandleScope handle_scope(env->GetIsolate());
|
|
CompileRun("var ring = 'One string to test them all';");
|
|
const char* s = "One string to test them all";
|
|
TestOneByteResource* inscription =
|
|
new TestOneByteResource(i::StrDup(s), &destroyed);
|
|
v8::Local<v8::String> ring = CompileRun("ring").As<v8::String>();
|
|
CHECK(v8::Utils::OpenHandle(*ring)->IsInternalizedString());
|
|
ring->MakeExternal(inscription);
|
|
// Ring is still alive. Orcs are roaming freely across our lands.
|
|
CHECK_EQ(0, destroyed);
|
|
USE(ring);
|
|
}
|
|
|
|
// Garbage collector deals swift blows to evil.
|
|
CcTest::i_isolate()->compilation_cache()->Clear();
|
|
CcTest::CollectAllAvailableGarbage();
|
|
|
|
// Ring has been destroyed. Free Peoples of Middle-earth Rejoice.
|
|
CHECK_EQ(1, destroyed);
|
|
}
|
|
|
|
|
|
static double DoubleFromBits(uint64_t value) {
|
|
double target;
|
|
i::MemCopy(&target, &value, sizeof(target));
|
|
return target;
|
|
}
|
|
|
|
|
|
static uint64_t DoubleToBits(double value) {
|
|
uint64_t target;
|
|
i::MemCopy(&target, &value, sizeof(target));
|
|
return target;
|
|
}
|
|
|
|
|
|
static double DoubleToDateTime(double input) {
|
|
double date_limit = 864e13;
|
|
if (std::isnan(input) || input < -date_limit || input > date_limit) {
|
|
return std::numeric_limits<double>::quiet_NaN();
|
|
}
|
|
return (input < 0) ? -(std::floor(-input)) : std::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) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
// 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::Local<v8::Value> number = v8::Number::New(isolate, test_value);
|
|
double stored_number = number->NumberValue(context.local()).FromJust();
|
|
if (!std::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(V8_TARGET_ARCH_MIPS64)) && \
|
|
!defined(_MIPS_ARCH_MIPS64R6) && !defined(_MIPS_ARCH_MIPS32R6) && \
|
|
!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::Local<v8::Value> date =
|
|
v8::Date::New(context.local(), test_value).ToLocalChecked();
|
|
double expected_stored_date = DoubleToDateTime(test_value);
|
|
double stored_date = date->NumberValue(context.local()).FromJust();
|
|
if (!std::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(V8_TARGET_ARCH_MIPS64)) && \
|
|
!defined(_MIPS_ARCH_MIPS64R6) && !defined(_MIPS_ARCH_MIPS32R6) && \
|
|
!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 void SpaghettiIncident(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::HandleScope scope(args.GetIsolate());
|
|
v8::TryCatch tc(args.GetIsolate());
|
|
v8::MaybeLocal<v8::String> str(
|
|
args[0]->ToString(args.GetIsolate()->GetCurrentContext()));
|
|
USE(str);
|
|
if (tc.HasCaught())
|
|
tc.ReThrow();
|
|
}
|
|
|
|
|
|
// Test that an exception can be propagated down through a spaghetti
|
|
// stack using ReThrow.
|
|
THREADED_TEST(SpaghettiStackReThrow) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext context;
|
|
context->Global()
|
|
->Set(context.local(), v8_str("s"),
|
|
v8::FunctionTemplate::New(isolate, SpaghettiIncident)
|
|
->GetFunction(context.local())
|
|
.ToLocalChecked())
|
|
.FromJust();
|
|
v8::TryCatch try_catch(isolate);
|
|
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(isolate, try_catch.Exception());
|
|
CHECK_EQ(0, strcmp(*value, "Hey!"));
|
|
}
|
|
|
|
|
|
TEST(Regress528) {
|
|
ManualGCScope manual_gc_scope;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
i::FLAG_retain_maps_for_n_gc = 0;
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<Context> other_context;
|
|
int gc_count;
|
|
|
|
// Create a context used to keep the code from aging in the compilation
|
|
// cache.
|
|
other_context = Context::New(isolate);
|
|
|
|
// Context-dependent context data creates reference from the compilation
|
|
// cache to the global object.
|
|
const char* source_simple = "1";
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
v8::Local<Context> context = Context::New(isolate);
|
|
|
|
context->Enter();
|
|
Local<v8::String> obj = v8_str("");
|
|
context->SetEmbedderData(0, obj);
|
|
CompileRun(source_simple);
|
|
context->Exit();
|
|
}
|
|
isolate->ContextDisposedNotification();
|
|
for (gc_count = 1; gc_count < 10; gc_count++) {
|
|
other_context->Enter();
|
|
CompileRun(source_simple);
|
|
other_context->Exit();
|
|
CcTest::CollectAllGarbage();
|
|
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()";
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
v8::Local<Context> context = Context::New(isolate);
|
|
|
|
context->Enter();
|
|
CompileRun(source_eval);
|
|
context->Exit();
|
|
}
|
|
isolate->ContextDisposedNotification();
|
|
for (gc_count = 1; gc_count < 10; gc_count++) {
|
|
other_context->Enter();
|
|
CompileRun(source_eval);
|
|
other_context->Exit();
|
|
CcTest::CollectAllGarbage();
|
|
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()";
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
v8::Local<Context> context = Context::New(isolate);
|
|
|
|
context->Enter();
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun(source_exception);
|
|
CHECK(try_catch.HasCaught());
|
|
v8::Local<v8::Message> message = try_catch.Message();
|
|
CHECK(!message.IsEmpty());
|
|
CHECK_EQ(1, message->GetLineNumber(context).FromJust());
|
|
context->Exit();
|
|
}
|
|
isolate->ContextDisposedNotification();
|
|
for (gc_count = 1; gc_count < 10; gc_count++) {
|
|
other_context->Enter();
|
|
CompileRun(source_exception);
|
|
other_context->Exit();
|
|
CcTest::CollectAllGarbage();
|
|
if (GetGlobalObjectsCount() == 1) break;
|
|
}
|
|
CHECK_GE(2, gc_count);
|
|
CHECK_EQ(1, GetGlobalObjectsCount());
|
|
|
|
isolate->ContextDisposedNotification();
|
|
}
|
|
|
|
|
|
THREADED_TEST(ScriptOrigin) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<v8::PrimitiveArray> array(v8::PrimitiveArray::New(isolate, 1));
|
|
Local<v8::Symbol> symbol(v8::Symbol::New(isolate));
|
|
array->Set(isolate, 0, symbol);
|
|
|
|
v8::ScriptOrigin origin = v8::ScriptOrigin(
|
|
isolate, v8_str("test"), 1, 1, true, -1, v8_str("http://sourceMapUrl"),
|
|
true, false, false, array);
|
|
v8::Local<v8::String> script = v8_str("function f() {}\n\nfunction g() {}");
|
|
v8::Script::Compile(env.local(), script, &origin)
|
|
.ToLocalChecked()
|
|
->Run(env.local())
|
|
.ToLocalChecked();
|
|
v8::Local<v8::Function> f = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("f")).ToLocalChecked());
|
|
v8::Local<v8::Function> g = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("g")).ToLocalChecked());
|
|
|
|
v8::ScriptOrigin script_origin_f = f->GetScriptOrigin();
|
|
CHECK_EQ(0, strcmp("test",
|
|
*v8::String::Utf8Value(env->GetIsolate(),
|
|
script_origin_f.ResourceName())));
|
|
CHECK_EQ(1, script_origin_f.LineOffset());
|
|
CHECK(script_origin_f.Options().IsSharedCrossOrigin());
|
|
CHECK(script_origin_f.Options().IsOpaque());
|
|
printf("is name = %d\n", script_origin_f.SourceMapUrl()->IsUndefined());
|
|
CHECK(script_origin_f.GetHostDefinedOptions()
|
|
.As<v8::PrimitiveArray>()
|
|
->Get(isolate, 0)
|
|
->IsSymbol());
|
|
|
|
CHECK_EQ(0, strcmp("http://sourceMapUrl",
|
|
*v8::String::Utf8Value(env->GetIsolate(),
|
|
script_origin_f.SourceMapUrl())));
|
|
|
|
v8::ScriptOrigin script_origin_g = g->GetScriptOrigin();
|
|
CHECK_EQ(0, strcmp("test",
|
|
*v8::String::Utf8Value(env->GetIsolate(),
|
|
script_origin_g.ResourceName())));
|
|
CHECK_EQ(1, script_origin_g.LineOffset());
|
|
CHECK(script_origin_g.Options().IsSharedCrossOrigin());
|
|
CHECK(script_origin_g.Options().IsOpaque());
|
|
CHECK_EQ(0, strcmp("http://sourceMapUrl",
|
|
*v8::String::Utf8Value(env->GetIsolate(),
|
|
script_origin_g.SourceMapUrl())));
|
|
CHECK(script_origin_g.GetHostDefinedOptions()
|
|
.As<v8::PrimitiveArray>()
|
|
->Get(isolate, 0)
|
|
->IsSymbol());
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionGetInferredName) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::ScriptOrigin origin = v8::ScriptOrigin(isolate, v8_str("test"), 0, 0);
|
|
v8::Local<v8::String> script =
|
|
v8_str("var foo = { bar : { baz : function() {}}}; var f = foo.bar.baz;");
|
|
v8::Script::Compile(env.local(), script, &origin)
|
|
.ToLocalChecked()
|
|
->Run(env.local())
|
|
.ToLocalChecked();
|
|
v8::Local<v8::Function> f = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("f")).ToLocalChecked());
|
|
CHECK_EQ(0,
|
|
strcmp("foo.bar.baz", *v8::String::Utf8Value(env->GetIsolate(),
|
|
f->GetInferredName())));
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionGetDebugName) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
const char* code =
|
|
"var error = false;"
|
|
"function a() { this.x = 1; };"
|
|
"Object.defineProperty(a, 'name', {value: 'display_a'});"
|
|
"var b = (function() {"
|
|
" var f = function() { this.x = 2; };"
|
|
" Object.defineProperty(f, 'name', {value: 'display_b'});"
|
|
" return f;"
|
|
"})();"
|
|
"var c = function() {};"
|
|
"c.__defineGetter__('name', function() {"
|
|
" error = true;"
|
|
" throw new Error();"
|
|
"});"
|
|
"function d() {};"
|
|
"d.__defineGetter__('name', function() {"
|
|
" error = true;"
|
|
" return 'wrong_display_name';"
|
|
"});"
|
|
"function e() {};"
|
|
"Object.defineProperty(e, 'name', {value: 'wrong_display_name'});"
|
|
"e.__defineSetter__('name', function() {"
|
|
" error = true;"
|
|
" throw new Error();"
|
|
"});"
|
|
"function f() {};"
|
|
"Object.defineProperty(f, 'name', {value: {foo: 6, toString: function() {"
|
|
" error = true;"
|
|
" return 'wrong_display_name';"
|
|
"}}});"
|
|
"var g = function() {"
|
|
" Object.defineProperty(arguments.callee, 'name', {"
|
|
" value: 'set_in_runtime'"
|
|
" });"
|
|
"}; g();"
|
|
"var h = function() {};"
|
|
"h.displayName = 'displayName';"
|
|
"Object.defineProperty(h, 'name', { value: 'function.name' });"
|
|
"var i = function() {};"
|
|
"i.displayName = 239;"
|
|
"Object.defineProperty(i, 'name', { value: 'function.name' });"
|
|
"var j = function() {};"
|
|
"Object.defineProperty(j, 'name', { value: 'function.name' });"
|
|
"var foo = { bar : { baz : (0, function() {})}}; var k = foo.bar.baz;"
|
|
"var foo = { bar : { baz : function() {} }}; var l = foo.bar.baz;";
|
|
v8::ScriptOrigin origin = v8::ScriptOrigin(isolate, v8_str("test"), 0, 0);
|
|
v8::Script::Compile(env.local(), v8_str(code), &origin)
|
|
.ToLocalChecked()
|
|
->Run(env.local())
|
|
.ToLocalChecked();
|
|
v8::Local<v8::Value> error =
|
|
env->Global()->Get(env.local(), v8_str("error")).ToLocalChecked();
|
|
CHECK(!error->BooleanValue(isolate));
|
|
const char* functions[] = {"a", "display_a",
|
|
"b", "display_b",
|
|
"c", "c",
|
|
"d", "d",
|
|
"e", "e",
|
|
"f", "f",
|
|
"g", "set_in_runtime",
|
|
"h", "function.name",
|
|
"i", "function.name",
|
|
"j", "function.name",
|
|
"k", "foo.bar.baz",
|
|
"l", "baz"};
|
|
for (size_t i = 0; i < sizeof(functions) / sizeof(functions[0]) / 2; ++i) {
|
|
v8::Local<v8::Function> f = v8::Local<v8::Function>::Cast(
|
|
env->Global()
|
|
->Get(env.local(),
|
|
v8::String::NewFromUtf8(isolate, functions[i * 2])
|
|
.ToLocalChecked())
|
|
.ToLocalChecked());
|
|
std::string expected(functions[i * 2 + 1]);
|
|
std::string actual = *v8::String::Utf8Value(isolate, f->GetDebugName());
|
|
CHECK_EQ(expected, actual);
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(ScriptLineNumber) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::ScriptOrigin origin = v8::ScriptOrigin(isolate, v8_str("test"), 0, 0);
|
|
v8::Local<v8::String> script = v8_str("function f() {}\n\nfunction g() {}");
|
|
v8::Script::Compile(env.local(), script, &origin)
|
|
.ToLocalChecked()
|
|
->Run(env.local())
|
|
.ToLocalChecked();
|
|
v8::Local<v8::Function> f = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("f")).ToLocalChecked());
|
|
v8::Local<v8::Function> g = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("g")).ToLocalChecked());
|
|
CHECK_EQ(0, f->GetScriptLineNumber());
|
|
CHECK_EQ(2, g->GetScriptLineNumber());
|
|
}
|
|
|
|
|
|
THREADED_TEST(ScriptColumnNumber) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::ScriptOrigin origin = v8::ScriptOrigin(isolate, v8_str("test"), 3, 2);
|
|
v8::Local<v8::String> script =
|
|
v8_str("function foo() {}\n\n function bar() {}");
|
|
v8::Script::Compile(env.local(), script, &origin)
|
|
.ToLocalChecked()
|
|
->Run(env.local())
|
|
.ToLocalChecked();
|
|
v8::Local<v8::Function> foo = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("foo")).ToLocalChecked());
|
|
v8::Local<v8::Function> bar = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("bar")).ToLocalChecked());
|
|
CHECK_EQ(14, foo->GetScriptColumnNumber());
|
|
CHECK_EQ(17, bar->GetScriptColumnNumber());
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionGetScriptId) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::ScriptOrigin origin = v8::ScriptOrigin(isolate, v8_str("test"), 3, 2);
|
|
v8::Local<v8::String> scriptSource =
|
|
v8_str("function foo() {}\n\n function bar() {}");
|
|
v8::Local<v8::Script> script(
|
|
v8::Script::Compile(env.local(), scriptSource, &origin).ToLocalChecked());
|
|
script->Run(env.local()).ToLocalChecked();
|
|
v8::Local<v8::Function> foo = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("foo")).ToLocalChecked());
|
|
v8::Local<v8::Function> bar = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("bar")).ToLocalChecked());
|
|
CHECK_EQ(script->GetUnboundScript()->GetId(), foo->ScriptId());
|
|
CHECK_EQ(script->GetUnboundScript()->GetId(), bar->ScriptId());
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionGetBoundFunction) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::ScriptOrigin origin = v8::ScriptOrigin(isolate, v8_str("test"));
|
|
v8::Local<v8::String> script = v8_str(
|
|
"var a = new Object();\n"
|
|
"a.x = 1;\n"
|
|
"function f () { return this.x };\n"
|
|
"var g = f.bind(a);\n"
|
|
"var b = g();");
|
|
v8::Script::Compile(env.local(), script, &origin)
|
|
.ToLocalChecked()
|
|
->Run(env.local())
|
|
.ToLocalChecked();
|
|
v8::Local<v8::Function> f = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("f")).ToLocalChecked());
|
|
v8::Local<v8::Function> g = v8::Local<v8::Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("g")).ToLocalChecked());
|
|
CHECK(g->GetBoundFunction()->IsFunction());
|
|
Local<v8::Function> original_function = Local<v8::Function>::Cast(
|
|
g->GetBoundFunction());
|
|
CHECK(f->GetName()
|
|
->Equals(env.local(), original_function->GetName())
|
|
.FromJust());
|
|
CHECK_EQ(f->GetScriptLineNumber(), original_function->GetScriptLineNumber());
|
|
CHECK_EQ(f->GetScriptColumnNumber(),
|
|
original_function->GetScriptColumnNumber());
|
|
}
|
|
|
|
THREADED_TEST(FunctionProtoToString) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Replace Function.prototype.toString.
|
|
CompileRun(R"(
|
|
Function.prototype.toString = function() {
|
|
return 'customized toString';
|
|
})");
|
|
|
|
constexpr char kTestFunction[] = "function testFunction() { return 7; }";
|
|
std::string wrapped_function("(");
|
|
wrapped_function.append(kTestFunction).append(")");
|
|
Local<Function> function =
|
|
CompileRun(wrapped_function.c_str()).As<Function>();
|
|
|
|
Local<String> value = function->ToString(context.local()).ToLocalChecked();
|
|
CHECK(value->IsString());
|
|
CHECK(
|
|
value->Equals(context.local(), v8_str("customized toString")).FromJust());
|
|
|
|
// FunctionProtoToString() should not call the replaced toString function.
|
|
value = function->FunctionProtoToString(context.local()).ToLocalChecked();
|
|
CHECK(value->IsString());
|
|
CHECK(value->Equals(context.local(), v8_str(kTestFunction)).FromJust());
|
|
}
|
|
|
|
static void GetterWhichReturns42(
|
|
Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(v8::Utils::OpenHandle(*info.This())->IsJSObject());
|
|
CHECK(v8::Utils::OpenHandle(*info.Holder())->IsJSObject());
|
|
info.GetReturnValue().Set(v8_num(42));
|
|
}
|
|
|
|
|
|
static void SetterWhichSetsYOnThisTo23(
|
|
Local<String> name,
|
|
Local<Value> value,
|
|
const v8::PropertyCallbackInfo<void>& info) {
|
|
CHECK(v8::Utils::OpenHandle(*info.This())->IsJSObject());
|
|
CHECK(v8::Utils::OpenHandle(*info.Holder())->IsJSObject());
|
|
info.This()
|
|
.As<Object>()
|
|
->Set(info.GetIsolate()->GetCurrentContext(), v8_str("y"), v8_num(23))
|
|
.FromJust();
|
|
}
|
|
|
|
|
|
void FooGetInterceptor(Local<Name> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(v8::Utils::OpenHandle(*info.This())->IsJSObject());
|
|
CHECK(v8::Utils::OpenHandle(*info.Holder())->IsJSObject());
|
|
if (!name->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("foo"))
|
|
.FromJust()) {
|
|
return;
|
|
}
|
|
info.GetReturnValue().Set(v8_num(42));
|
|
}
|
|
|
|
|
|
void FooSetInterceptor(Local<Name> name, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(v8::Utils::OpenHandle(*info.This())->IsJSObject());
|
|
CHECK(v8::Utils::OpenHandle(*info.Holder())->IsJSObject());
|
|
if (!name->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("foo"))
|
|
.FromJust()) {
|
|
return;
|
|
}
|
|
info.This()
|
|
.As<Object>()
|
|
->Set(info.GetIsolate()->GetCurrentContext(), v8_str("y"), v8_num(23))
|
|
.FromJust();
|
|
info.GetReturnValue().Set(v8_num(23));
|
|
}
|
|
|
|
|
|
TEST(SetterOnConstructorPrototype) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("x"), GetterWhichReturns42,
|
|
SetterWhichSetsYOnThisTo23);
|
|
LocalContext context;
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("P"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
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_compile("new C1();");
|
|
for (int i = 0; i < 10; i++) {
|
|
v8::Local<v8::Object> c1 = v8::Local<v8::Object>::Cast(
|
|
script->Run(context.local()).ToLocalChecked());
|
|
CHECK_EQ(42, c1->Get(context.local(), v8_str("x"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(23, c1->Get(context.local(), v8_str("y"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
script = v8_compile("new C2();");
|
|
for (int i = 0; i < 10; i++) {
|
|
v8::Local<v8::Object> c2 = v8::Local<v8::Object>::Cast(
|
|
script->Run(context.local()).ToLocalChecked());
|
|
CHECK_EQ(42, c2->Get(context.local(), v8_str("x"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(23, c2->Get(context.local(), v8_str("y"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
static void NamedPropertySetterWhichSetsYOnThisTo23(
|
|
Local<Name> name, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
if (name->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("x"))
|
|
.FromJust()) {
|
|
info.This()
|
|
.As<Object>()
|
|
->Set(info.GetIsolate()->GetCurrentContext(), v8_str("y"), v8_num(23))
|
|
.FromJust();
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(InterceptorOnConstructorPrototype) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
NamedPropertyGetterWhichReturns42,
|
|
NamedPropertySetterWhichSetsYOnThisTo23));
|
|
LocalContext context;
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("P"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
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_compile("new C1();");
|
|
for (int i = 0; i < 10; i++) {
|
|
v8::Local<v8::Object> c1 = v8::Local<v8::Object>::Cast(
|
|
script->Run(context.local()).ToLocalChecked());
|
|
CHECK_EQ(23, c1->Get(context.local(), v8_str("x"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(42, c1->Get(context.local(), v8_str("y"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
script = v8_compile("new C2();");
|
|
for (int i = 0; i < 10; i++) {
|
|
v8::Local<v8::Object> c2 = v8::Local<v8::Object>::Cast(
|
|
script->Run(context.local()).ToLocalChecked());
|
|
CHECK_EQ(23, c2->Get(context.local(), v8_str("x"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(42, c2->Get(context.local(), v8_str("y"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
TEST(Regress618) {
|
|
const char* source = "function C1() {"
|
|
" this.x = 23;"
|
|
"};"
|
|
"C1.prototype = P;";
|
|
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Script> script;
|
|
|
|
// Use a simple object as prototype.
|
|
v8::Local<v8::Object> prototype = v8::Object::New(isolate);
|
|
prototype->Set(context.local(), v8_str("y"), v8_num(42)).FromJust();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("P"), prototype)
|
|
.FromJust());
|
|
|
|
// This compile will add the code to the compilation cache.
|
|
CompileRun(source);
|
|
|
|
script = v8_compile("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::Local<v8::Object> c1 = v8::Local<v8::Object>::Cast(
|
|
script->Run(context.local()).ToLocalChecked());
|
|
CHECK_EQ(23, c1->Get(context.local(), v8_str("x"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(42, c1->Get(context.local(), v8_str("y"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
// Use an API object with accessors as prototype.
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("x"), GetterWhichReturns42,
|
|
SetterWhichSetsYOnThisTo23);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("P"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// This compile will get the code from the compilation cache.
|
|
CompileRun(source);
|
|
|
|
script = v8_compile("new C1();");
|
|
for (int i = 0; i < 10; i++) {
|
|
v8::Local<v8::Object> c1 = v8::Local<v8::Object>::Cast(
|
|
script->Run(context.local()).ToLocalChecked());
|
|
CHECK_EQ(42, c1->Get(context.local(), v8_str("x"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(23, c1->Get(context.local(), v8_str("y"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
v8::Isolate* gc_callbacks_isolate = nullptr;
|
|
int prologue_call_count = 0;
|
|
int epilogue_call_count = 0;
|
|
int prologue_call_count_second = 0;
|
|
int epilogue_call_count_second = 0;
|
|
int prologue_call_count_alloc = 0;
|
|
int epilogue_call_count_alloc = 0;
|
|
|
|
void PrologueCallback(v8::Isolate* isolate,
|
|
v8::GCType,
|
|
v8::GCCallbackFlags flags) {
|
|
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
|
|
CHECK_EQ(gc_callbacks_isolate, isolate);
|
|
++prologue_call_count;
|
|
}
|
|
|
|
void EpilogueCallback(v8::Isolate* isolate,
|
|
v8::GCType,
|
|
v8::GCCallbackFlags flags) {
|
|
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
|
|
CHECK_EQ(gc_callbacks_isolate, isolate);
|
|
++epilogue_call_count;
|
|
}
|
|
|
|
|
|
void PrologueCallbackSecond(v8::Isolate* isolate,
|
|
v8::GCType,
|
|
v8::GCCallbackFlags flags) {
|
|
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
|
|
CHECK_EQ(gc_callbacks_isolate, isolate);
|
|
++prologue_call_count_second;
|
|
}
|
|
|
|
|
|
void EpilogueCallbackSecond(v8::Isolate* isolate,
|
|
v8::GCType,
|
|
v8::GCCallbackFlags flags) {
|
|
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
|
|
CHECK_EQ(gc_callbacks_isolate, isolate);
|
|
++epilogue_call_count_second;
|
|
}
|
|
|
|
void PrologueCallbackNew(v8::Isolate* isolate, v8::GCType,
|
|
v8::GCCallbackFlags flags, void* data) {
|
|
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
|
|
CHECK_EQ(gc_callbacks_isolate, isolate);
|
|
++*static_cast<int*>(data);
|
|
}
|
|
|
|
void EpilogueCallbackNew(v8::Isolate* isolate, v8::GCType,
|
|
v8::GCCallbackFlags flags, void* data) {
|
|
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
|
|
CHECK_EQ(gc_callbacks_isolate, isolate);
|
|
++*static_cast<int*>(data);
|
|
}
|
|
|
|
void PrologueCallbackAlloc(v8::Isolate* isolate,
|
|
v8::GCType,
|
|
v8::GCCallbackFlags flags) {
|
|
v8::HandleScope scope(isolate);
|
|
|
|
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
|
|
CHECK_EQ(gc_callbacks_isolate, isolate);
|
|
++prologue_call_count_alloc;
|
|
|
|
if (!v8::internal::FLAG_single_generation) {
|
|
// Simulate full heap to see if we will reenter this callback
|
|
i::heap::SimulateFullSpace(CcTest::heap()->new_space());
|
|
}
|
|
|
|
Local<Object> obj = Object::New(isolate);
|
|
CHECK(!obj.IsEmpty());
|
|
|
|
CcTest::PreciseCollectAllGarbage();
|
|
}
|
|
|
|
|
|
void EpilogueCallbackAlloc(v8::Isolate* isolate,
|
|
v8::GCType,
|
|
v8::GCCallbackFlags flags) {
|
|
v8::HandleScope scope(isolate);
|
|
|
|
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
|
|
CHECK_EQ(gc_callbacks_isolate, isolate);
|
|
++epilogue_call_count_alloc;
|
|
|
|
if (!v8::internal::FLAG_single_generation) {
|
|
// Simulate full heap to see if we will reenter this callback
|
|
i::heap::SimulateFullSpace(CcTest::heap()->new_space());
|
|
}
|
|
|
|
Local<Object> obj = Object::New(isolate);
|
|
CHECK(!obj.IsEmpty());
|
|
|
|
CcTest::PreciseCollectAllGarbage();
|
|
}
|
|
|
|
|
|
TEST(GCCallbacksOld) {
|
|
LocalContext context;
|
|
|
|
gc_callbacks_isolate = context->GetIsolate();
|
|
|
|
context->GetIsolate()->AddGCPrologueCallback(PrologueCallback);
|
|
context->GetIsolate()->AddGCEpilogueCallback(EpilogueCallback);
|
|
CHECK_EQ(0, prologue_call_count);
|
|
CHECK_EQ(0, epilogue_call_count);
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(1, prologue_call_count);
|
|
CHECK_EQ(1, epilogue_call_count);
|
|
context->GetIsolate()->AddGCPrologueCallback(PrologueCallbackSecond);
|
|
context->GetIsolate()->AddGCEpilogueCallback(EpilogueCallbackSecond);
|
|
CcTest::CollectAllGarbage();
|
|
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);
|
|
context->GetIsolate()->RemoveGCPrologueCallback(PrologueCallback);
|
|
context->GetIsolate()->RemoveGCEpilogueCallback(EpilogueCallback);
|
|
CcTest::CollectAllGarbage();
|
|
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);
|
|
context->GetIsolate()->RemoveGCPrologueCallback(PrologueCallbackSecond);
|
|
context->GetIsolate()->RemoveGCEpilogueCallback(EpilogueCallbackSecond);
|
|
CcTest::CollectAllGarbage();
|
|
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);
|
|
}
|
|
|
|
TEST(GCCallbacksWithData) {
|
|
LocalContext context;
|
|
|
|
gc_callbacks_isolate = context->GetIsolate();
|
|
int prologue1 = 0;
|
|
int epilogue1 = 0;
|
|
int prologue2 = 0;
|
|
int epilogue2 = 0;
|
|
|
|
context->GetIsolate()->AddGCPrologueCallback(PrologueCallbackNew, &prologue1);
|
|
context->GetIsolate()->AddGCEpilogueCallback(EpilogueCallbackNew, &epilogue1);
|
|
CHECK_EQ(0, prologue1);
|
|
CHECK_EQ(0, epilogue1);
|
|
CHECK_EQ(0, prologue2);
|
|
CHECK_EQ(0, epilogue2);
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(1, prologue1);
|
|
CHECK_EQ(1, epilogue1);
|
|
CHECK_EQ(0, prologue2);
|
|
CHECK_EQ(0, epilogue2);
|
|
context->GetIsolate()->AddGCPrologueCallback(PrologueCallbackNew, &prologue2);
|
|
context->GetIsolate()->AddGCEpilogueCallback(EpilogueCallbackNew, &epilogue2);
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(2, prologue1);
|
|
CHECK_EQ(2, epilogue1);
|
|
CHECK_EQ(1, prologue2);
|
|
CHECK_EQ(1, epilogue2);
|
|
context->GetIsolate()->RemoveGCPrologueCallback(PrologueCallbackNew,
|
|
&prologue1);
|
|
context->GetIsolate()->RemoveGCEpilogueCallback(EpilogueCallbackNew,
|
|
&epilogue1);
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(2, prologue1);
|
|
CHECK_EQ(2, epilogue1);
|
|
CHECK_EQ(2, prologue2);
|
|
CHECK_EQ(2, epilogue2);
|
|
context->GetIsolate()->RemoveGCPrologueCallback(PrologueCallbackNew,
|
|
&prologue2);
|
|
context->GetIsolate()->RemoveGCEpilogueCallback(EpilogueCallbackNew,
|
|
&epilogue2);
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(2, prologue1);
|
|
CHECK_EQ(2, epilogue1);
|
|
CHECK_EQ(2, prologue2);
|
|
CHECK_EQ(2, epilogue2);
|
|
}
|
|
|
|
TEST(GCCallbacks) {
|
|
// For SimulateFullSpace in PrologueCallbackAlloc and EpilogueCallbackAlloc.
|
|
i::FLAG_stress_concurrent_allocation = false;
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
gc_callbacks_isolate = isolate;
|
|
isolate->AddGCPrologueCallback(PrologueCallback);
|
|
isolate->AddGCEpilogueCallback(EpilogueCallback);
|
|
CHECK_EQ(0, prologue_call_count);
|
|
CHECK_EQ(0, epilogue_call_count);
|
|
CcTest::CollectAllGarbage();
|
|
CHECK_EQ(1, prologue_call_count);
|
|
CHECK_EQ(1, epilogue_call_count);
|
|
isolate->AddGCPrologueCallback(PrologueCallbackSecond);
|
|
isolate->AddGCEpilogueCallback(EpilogueCallbackSecond);
|
|
CcTest::CollectAllGarbage();
|
|
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);
|
|
isolate->RemoveGCPrologueCallback(PrologueCallback);
|
|
isolate->RemoveGCEpilogueCallback(EpilogueCallback);
|
|
CcTest::CollectAllGarbage();
|
|
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);
|
|
isolate->RemoveGCPrologueCallback(PrologueCallbackSecond);
|
|
isolate->RemoveGCEpilogueCallback(EpilogueCallbackSecond);
|
|
CcTest::CollectAllGarbage();
|
|
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);
|
|
|
|
CHECK_EQ(0, prologue_call_count_alloc);
|
|
CHECK_EQ(0, epilogue_call_count_alloc);
|
|
isolate->AddGCPrologueCallback(PrologueCallbackAlloc);
|
|
isolate->AddGCEpilogueCallback(EpilogueCallbackAlloc);
|
|
CcTest::PreciseCollectAllGarbage();
|
|
CHECK_EQ(1, prologue_call_count_alloc);
|
|
CHECK_EQ(1, epilogue_call_count_alloc);
|
|
isolate->RemoveGCPrologueCallback(PrologueCallbackAlloc);
|
|
isolate->RemoveGCEpilogueCallback(EpilogueCallbackAlloc);
|
|
}
|
|
|
|
namespace {
|
|
|
|
void AssertOneByteConsContainsTwoByteExternal(i::Handle<i::String> maybe_cons,
|
|
i::Handle<i::String> external) {
|
|
CHECK(maybe_cons->IsOneByteRepresentation());
|
|
CHECK(maybe_cons->IsConsString());
|
|
i::ConsString cons = i::ConsString::cast(*maybe_cons);
|
|
CHECK(cons.IsFlat(GetPtrComprCageBase(cons)));
|
|
CHECK(cons.first() == *external);
|
|
CHECK(cons.first().IsTwoByteRepresentation());
|
|
CHECK(cons.first().IsExternalString());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
THREADED_TEST(TwoByteStringInOneByteCons) {
|
|
// See Chromium issue 47824.
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
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')");
|
|
Local<Value> second_char = CompileRun("str2[1]");
|
|
|
|
CHECK(result->IsString());
|
|
i::Handle<i::String> string = v8::Utils::OpenHandle(String::Cast(*result));
|
|
int length = string->length();
|
|
CHECK(string->IsConsString());
|
|
CHECK(string->IsOneByteRepresentation());
|
|
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate());
|
|
i::Handle<i::String> flat_string = i::String::Flatten(i_isolate, 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 one-byte,
|
|
// with a first half that is a two-byte string (although it only contains
|
|
// one-byte characters). This is a valid sequence of steps, and it can
|
|
// happen in real pages.
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
// Check that some string operations work.
|
|
|
|
// Atom RegExp.
|
|
Local<Value> reresult = CompileRun("str2.match(/abel/g).length;");
|
|
CHECK_EQ(6, reresult->Int32Value(context.local()).FromJust());
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
// Nonatom RegExp.
|
|
reresult = CompileRun("str2.match(/abe./g).length;");
|
|
CHECK_EQ(6, reresult->Int32Value(context.local()).FromJust());
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
reresult = CompileRun("str2.search(/bel/g);");
|
|
CHECK_EQ(1, reresult->Int32Value(context.local()).FromJust());
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
reresult = CompileRun("str2.search(/be./g);");
|
|
CHECK_EQ(1, reresult->Int32Value(context.local()).FromJust());
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
ExpectTrue("/bel/g.test(str2);");
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
ExpectTrue("/be./g.test(str2);");
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
reresult = CompileRun("/bel/g.exec(str2);");
|
|
CHECK(!reresult->IsNull());
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
reresult = CompileRun("/be./g.exec(str2);");
|
|
CHECK(!reresult->IsNull());
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
ExpectString("str2.substring(2, 10);", "elspenda");
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
ExpectString("str2.substring(2, 20);", "elspendabelabelspe");
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
ExpectString("str2.charAt(2);", "e");
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
ExpectObject("str2.indexOf('els');", indexof);
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
ExpectObject("str2.lastIndexOf('dab');", lastindexof);
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
// crbug.com/1088179. Fill the single character string cache, then run split.
|
|
i_isolate->factory()->LookupSingleCharacterStringFromCode(0);
|
|
for (size_t i = 0; i < std::strlen(init_code); i++) {
|
|
i_isolate->factory()->LookupSingleCharacterStringFromCode(init_code[i]);
|
|
}
|
|
ExpectObject("str2.split('')[1]", second_char);
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
reresult = CompileRun("str2.charCodeAt(2);");
|
|
CHECK_EQ(static_cast<int32_t>('e'),
|
|
reresult->Int32Value(context.local()).FromJust());
|
|
AssertOneByteConsContainsTwoByteExternal(string, flat_string);
|
|
|
|
// This avoids the GC from trying to free stack allocated resources.
|
|
i::Handle<i::ExternalTwoByteString>::cast(flat_string)
|
|
->SetResource(i_isolate, nullptr);
|
|
}
|
|
|
|
TEST(ContainsOnlyOneByte) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
// Make a buffer long enough that it won't automatically be converted.
|
|
const int length = 512;
|
|
// Ensure word aligned assignment.
|
|
const int aligned_length = length*sizeof(uintptr_t)/sizeof(uint16_t);
|
|
std::unique_ptr<uintptr_t[]> aligned_contents(new uintptr_t[aligned_length]);
|
|
uint16_t* string_contents =
|
|
reinterpret_cast<uint16_t*>(aligned_contents.get());
|
|
// Set to contain only one byte.
|
|
for (int i = 0; i < length-1; i++) {
|
|
string_contents[i] = 0x41;
|
|
}
|
|
string_contents[length-1] = 0;
|
|
// Simple case.
|
|
Local<String> string =
|
|
String::NewExternalTwoByte(
|
|
isolate, new TestResource(string_contents, nullptr, false))
|
|
.ToLocalChecked();
|
|
CHECK(!string->IsOneByte() && string->ContainsOnlyOneByte());
|
|
// Counter example.
|
|
string = String::NewFromTwoByte(isolate, string_contents).ToLocalChecked();
|
|
CHECK(string->IsOneByte() && string->ContainsOnlyOneByte());
|
|
// Test left right and balanced cons strings.
|
|
Local<String> base = v8_str("a");
|
|
Local<String> left = base;
|
|
Local<String> right = base;
|
|
for (int i = 0; i < 1000; i++) {
|
|
left = String::Concat(isolate, base, left);
|
|
right = String::Concat(isolate, right, base);
|
|
}
|
|
Local<String> balanced = String::Concat(isolate, left, base);
|
|
balanced = String::Concat(isolate, balanced, right);
|
|
Local<String> cons_strings[] = {left, balanced, right};
|
|
Local<String> two_byte =
|
|
String::NewExternalTwoByte(
|
|
isolate, new TestResource(string_contents, nullptr, false))
|
|
.ToLocalChecked();
|
|
USE(two_byte); USE(cons_strings);
|
|
for (size_t i = 0; i < arraysize(cons_strings); i++) {
|
|
// Base assumptions.
|
|
string = cons_strings[i];
|
|
CHECK(string->IsOneByte() && string->ContainsOnlyOneByte());
|
|
// Test left and right concatentation.
|
|
string = String::Concat(isolate, two_byte, cons_strings[i]);
|
|
CHECK(!string->IsOneByte() && string->ContainsOnlyOneByte());
|
|
string = String::Concat(isolate, cons_strings[i], two_byte);
|
|
CHECK(!string->IsOneByte() && string->ContainsOnlyOneByte());
|
|
}
|
|
// Set bits in different positions
|
|
// for strings of different lengths and alignments.
|
|
for (int alignment = 0; alignment < 7; alignment++) {
|
|
for (int size = 2; alignment + size < length; size *= 2) {
|
|
int zero_offset = size + alignment;
|
|
string_contents[zero_offset] = 0;
|
|
for (int i = 0; i < size; i++) {
|
|
int shift = 8 + (i % 7);
|
|
string_contents[alignment + i] = 1 << shift;
|
|
string = String::NewExternalTwoByte(
|
|
isolate, new TestResource(string_contents + alignment,
|
|
nullptr, false))
|
|
.ToLocalChecked();
|
|
CHECK_EQ(size, string->Length());
|
|
CHECK(!string->ContainsOnlyOneByte());
|
|
string_contents[alignment + i] = 0x41;
|
|
}
|
|
string_contents[zero_offset] = 0x41;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Failed access check callback that performs a GC on each invocation.
|
|
void FailedAccessCheckCallbackGC(Local<v8::Object> target,
|
|
v8::AccessType type,
|
|
Local<v8::Value> data) {
|
|
CcTest::CollectAllGarbage();
|
|
CcTest::isolate()->ThrowException(
|
|
v8::Exception::Error(v8_str("cross context")));
|
|
}
|
|
|
|
|
|
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::Isolate* isolate = CcTest::isolate();
|
|
|
|
isolate->SetFailedAccessCheckCallbackFunction(&FailedAccessCheckCallbackGC);
|
|
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Create an ObjectTemplate for global objects and install access
|
|
// check callbacks that will block access.
|
|
v8::Local<v8::ObjectTemplate> global_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
global_template->SetAccessCheckCallback(AccessAlwaysBlocked);
|
|
|
|
// Create a context and set an x property on it's global object.
|
|
LocalContext context0(nullptr, global_template);
|
|
CHECK(context0->Global()
|
|
->Set(context0.local(), v8_str("x"), v8_num(42))
|
|
.FromJust());
|
|
v8::Local<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(nullptr, global_template);
|
|
CHECK(context1->Global()
|
|
->Set(context1.local(), v8_str("other"), global0)
|
|
.FromJust());
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
// Get property with failed access check.
|
|
CHECK(CompileRun("other.x").IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Get element with failed access check.
|
|
CHECK(CompileRun("other[0]").IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Set property with failed access check.
|
|
CHECK(CompileRun("other.x = new Object()").IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Set element with failed access check.
|
|
CHECK(CompileRun("other[0] = new Object()").IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Get property attribute with failed access check.
|
|
CHECK(CompileRun("\'x\' in other").IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Get property attribute for element with failed access check.
|
|
CHECK(CompileRun("0 in other").IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Delete property.
|
|
CHECK(CompileRun("delete other.x").IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Delete element.
|
|
CHECK(global0->Delete(context1.local(), 0).IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// DefineAccessor.
|
|
CHECK(global0
|
|
->SetAccessor(context1.local(), v8_str("x"), GetXValue, nullptr,
|
|
v8_str("x"))
|
|
.IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Define JavaScript accessor.
|
|
CHECK(CompileRun(
|
|
"Object.prototype.__defineGetter__.call("
|
|
" other, \'x\', function() { return 42; })").IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// LookupAccessor.
|
|
CHECK(CompileRun(
|
|
"Object.prototype.__lookupGetter__.call("
|
|
" other, \'x\')").IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// HasOwnElement.
|
|
CHECK(CompileRun(
|
|
"Object.prototype.hasOwnProperty.call("
|
|
"other, \'0\')").IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
CHECK(global0->HasRealIndexedProperty(context1.local(), 0).IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
CHECK(
|
|
global0->HasRealNamedProperty(context1.local(), v8_str("x")).IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
CHECK(global0->HasRealNamedCallbackProperty(context1.local(), v8_str("x"))
|
|
.IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
// Reset the failed access check callback so it does not influence
|
|
// the other tests.
|
|
isolate->SetFailedAccessCheckCallbackFunction(nullptr);
|
|
}
|
|
|
|
|
|
TEST(IsolateNewDispose) {
|
|
v8::Isolate* current_isolate = CcTest::isolate();
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
CHECK_NOT_NULL(isolate);
|
|
CHECK(current_isolate != isolate);
|
|
CHECK(current_isolate == CcTest::isolate());
|
|
CHECK(isolate->GetArrayBufferAllocator() == CcTest::array_buffer_allocator());
|
|
|
|
isolate->SetFatalErrorHandler(StoringErrorCallback);
|
|
last_location = last_message = nullptr;
|
|
isolate->Dispose();
|
|
CHECK(!last_location);
|
|
CHECK(!last_message);
|
|
}
|
|
|
|
|
|
UNINITIALIZED_TEST(DisposeIsolateWhenInUse) {
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
{
|
|
v8::Isolate::Scope i_scope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext context(isolate);
|
|
// Run something in this isolate.
|
|
ExpectTrue("true");
|
|
isolate->SetFatalErrorHandler(StoringErrorCallback);
|
|
last_location = last_message = nullptr;
|
|
// Still entered, should fail.
|
|
isolate->Dispose();
|
|
CHECK(last_location);
|
|
CHECK(last_message);
|
|
}
|
|
isolate->Dispose();
|
|
}
|
|
|
|
|
|
static void BreakArrayGuarantees(const char* script) {
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate1 = v8::Isolate::New(create_params);
|
|
isolate1->Enter();
|
|
v8::Persistent<v8::Context> context1;
|
|
{
|
|
v8::HandleScope scope(isolate1);
|
|
context1.Reset(isolate1, Context::New(isolate1));
|
|
}
|
|
|
|
{
|
|
v8::HandleScope scope(isolate1);
|
|
v8::Local<v8::Context> context =
|
|
v8::Local<v8::Context>::New(isolate1, context1);
|
|
v8::Context::Scope context_scope(context);
|
|
v8::internal::Isolate* i_isolate =
|
|
reinterpret_cast<v8::internal::Isolate*>(isolate1);
|
|
CHECK(v8::internal::Protectors::IsNoElementsIntact(i_isolate));
|
|
// Run something in new isolate.
|
|
CompileRun(script);
|
|
CHECK(!v8::internal::Protectors::IsNoElementsIntact(i_isolate));
|
|
}
|
|
isolate1->Exit();
|
|
isolate1->Dispose();
|
|
}
|
|
|
|
|
|
TEST(VerifyArrayPrototypeGuarantees) {
|
|
// Break fast array hole handling by element changes.
|
|
BreakArrayGuarantees("[].__proto__[1] = 3;");
|
|
BreakArrayGuarantees("Object.prototype[3] = 'three';");
|
|
BreakArrayGuarantees("Array.prototype.push(1);");
|
|
BreakArrayGuarantees("Array.prototype.unshift(1);");
|
|
// Break fast array hole handling by changing length.
|
|
BreakArrayGuarantees("Array.prototype.length = 30;");
|
|
// Break fast array hole handling by prototype structure changes.
|
|
BreakArrayGuarantees("[].__proto__.__proto__ = { funny: true };");
|
|
// By sending elements to dictionary mode.
|
|
BreakArrayGuarantees(
|
|
"Object.defineProperty(Array.prototype, 0, {"
|
|
" get: function() { return 3; }});");
|
|
BreakArrayGuarantees(
|
|
"Object.defineProperty(Object.prototype, 0, {"
|
|
" get: function() { return 3; }});");
|
|
}
|
|
|
|
|
|
TEST(RunTwoIsolatesOnSingleThread) {
|
|
// Run isolate 1.
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate1 = v8::Isolate::New(create_params);
|
|
|
|
CHECK(CcTest::isolate()->IsCurrent());
|
|
CHECK(!isolate1->IsCurrent());
|
|
|
|
isolate1->Enter();
|
|
CHECK(!CcTest::isolate()->IsCurrent());
|
|
CHECK(isolate1->IsCurrent());
|
|
|
|
CHECK_EQ(isolate1, v8::Isolate::GetCurrent());
|
|
CHECK_EQ(isolate1, v8::Isolate::TryGetCurrent());
|
|
|
|
v8::Persistent<v8::Context> context1;
|
|
{
|
|
v8::HandleScope scope(isolate1);
|
|
context1.Reset(isolate1, Context::New(isolate1));
|
|
}
|
|
|
|
{
|
|
v8::HandleScope scope(isolate1);
|
|
v8::Local<v8::Context> context =
|
|
v8::Local<v8::Context>::New(isolate1, context1);
|
|
v8::Context::Scope context_scope(context);
|
|
// 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(create_params);
|
|
v8::Persistent<v8::Context> context2;
|
|
|
|
CHECK(!CcTest::isolate()->IsCurrent());
|
|
CHECK(isolate1->IsCurrent());
|
|
CHECK(!isolate2->IsCurrent());
|
|
{
|
|
v8::Isolate::Scope iscope(isolate2);
|
|
CHECK(!isolate1->IsCurrent());
|
|
CHECK(isolate2->IsCurrent());
|
|
CHECK_EQ(isolate2, v8::Isolate::GetCurrent());
|
|
CHECK_EQ(isolate2, v8::Isolate::TryGetCurrent());
|
|
|
|
v8::HandleScope scope(isolate2);
|
|
context2.Reset(isolate2, Context::New(isolate2));
|
|
v8::Local<v8::Context> context =
|
|
v8::Local<v8::Context>::New(isolate2, context2);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
// Run something in new isolate.
|
|
CompileRun("var foo = 'isolate 2';");
|
|
ExpectString("function f() { return foo; }; f()", "isolate 2");
|
|
}
|
|
|
|
CHECK(!CcTest::isolate()->IsCurrent());
|
|
CHECK(isolate1->IsCurrent());
|
|
CHECK(!isolate2->IsCurrent());
|
|
|
|
{
|
|
v8::HandleScope scope(isolate1);
|
|
v8::Local<v8::Context> context =
|
|
v8::Local<v8::Context>::New(isolate1, context1);
|
|
v8::Context::Scope context_scope(context);
|
|
// Now again in isolate 1
|
|
ExpectString("function f() { return foo; }; f()", "isolate 1");
|
|
}
|
|
|
|
isolate1->Exit();
|
|
CHECK(CcTest::isolate()->IsCurrent());
|
|
CHECK(!isolate1->IsCurrent());
|
|
CHECK(!isolate2->IsCurrent());
|
|
|
|
// Run some stuff in default isolate.
|
|
v8::Persistent<v8::Context> context_default;
|
|
{
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
CHECK_EQ(isolate, v8::Isolate::GetCurrent());
|
|
CHECK_EQ(isolate, v8::Isolate::TryGetCurrent());
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
context_default.Reset(isolate, Context::New(isolate));
|
|
}
|
|
|
|
{
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
v8::Local<v8::Context> context =
|
|
v8::Local<v8::Context>::New(CcTest::isolate(), context_default);
|
|
v8::Context::Scope context_scope(context);
|
|
// 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::HandleScope scope(isolate2);
|
|
v8::Local<v8::Context> context =
|
|
v8::Local<v8::Context>::New(isolate2, context2);
|
|
v8::Context::Scope context_scope(context);
|
|
ExpectString("function f() { return foo; }; f()", "isolate 2");
|
|
}
|
|
|
|
{
|
|
v8::HandleScope scope(isolate1);
|
|
v8::Local<v8::Context> context =
|
|
v8::Local<v8::Context>::New(isolate1, context1);
|
|
v8::Context::Scope context_scope(context);
|
|
ExpectString("function f() { return foo; }; f()", "isolate 1");
|
|
}
|
|
|
|
{
|
|
v8::Isolate::Scope iscope(isolate2);
|
|
context2.Reset();
|
|
}
|
|
|
|
context1.Reset();
|
|
isolate1->Exit();
|
|
|
|
isolate2->SetFatalErrorHandler(StoringErrorCallback);
|
|
last_location = last_message = nullptr;
|
|
|
|
isolate1->Dispose();
|
|
CHECK(!last_location);
|
|
CHECK(!last_message);
|
|
|
|
isolate2->Dispose();
|
|
CHECK(!last_location);
|
|
CHECK(!last_message);
|
|
|
|
// Check that default isolate still runs.
|
|
{
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
v8::Local<v8::Context> context =
|
|
v8::Local<v8::Context>::New(CcTest::isolate(), context_default);
|
|
v8::Context::Scope context_scope(context);
|
|
ExpectTrue("function f() { return isDefaultIsolate; }; f()");
|
|
}
|
|
}
|
|
|
|
|
|
static int CalcFibonacci(v8::Isolate* isolate, int limit) {
|
|
v8::Isolate::Scope isolate_scope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext context(isolate);
|
|
v8::base::ScopedVector<char> code(1024);
|
|
v8::base::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.begin());
|
|
CHECK(value->IsNumber());
|
|
return static_cast<int>(value->NumberValue(context.local()).FromJust());
|
|
}
|
|
|
|
class IsolateThread : public v8::base::Thread {
|
|
public:
|
|
explicit IsolateThread(int fib_limit)
|
|
: Thread(Options("IsolateThread")), fib_limit_(fib_limit), result_(0) {}
|
|
|
|
void Run() override {
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
result_ = CalcFibonacci(isolate, fib_limit_);
|
|
isolate->Dispose();
|
|
}
|
|
|
|
int result() { return result_; }
|
|
|
|
private:
|
|
int fib_limit_;
|
|
int result_;
|
|
};
|
|
|
|
|
|
TEST(MultipleIsolatesOnIndividualThreads) {
|
|
IsolateThread thread1(21);
|
|
IsolateThread thread2(12);
|
|
|
|
// Compute some fibonacci numbers on 3 threads in 3 isolates.
|
|
CHECK(thread1.Start());
|
|
CHECK(thread2.Start());
|
|
|
|
int result1 = CalcFibonacci(CcTest::isolate(), 21);
|
|
int result2 = CalcFibonacci(CcTest::isolate(), 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());
|
|
}
|
|
|
|
|
|
TEST(IsolateDifferentContexts) {
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
Local<v8::Context> context;
|
|
{
|
|
v8::Isolate::Scope isolate_scope(isolate);
|
|
v8::HandleScope handle_scope(isolate);
|
|
context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
Local<Value> v = CompileRun("2");
|
|
CHECK(v->IsNumber());
|
|
CHECK_EQ(2, static_cast<int>(v->NumberValue(context).FromJust()));
|
|
}
|
|
{
|
|
v8::Isolate::Scope isolate_scope(isolate);
|
|
v8::HandleScope handle_scope(isolate);
|
|
context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
Local<Value> v = CompileRun("22");
|
|
CHECK(v->IsNumber());
|
|
CHECK_EQ(22, static_cast<int>(v->NumberValue(context).FromJust()));
|
|
}
|
|
isolate->Dispose();
|
|
}
|
|
|
|
class InitDefaultIsolateThread : public v8::base::Thread {
|
|
public:
|
|
enum TestCase {
|
|
SetFatalHandler,
|
|
SetCounterFunction,
|
|
SetCreateHistogramFunction,
|
|
SetAddHistogramSampleFunction
|
|
};
|
|
|
|
explicit InitDefaultIsolateThread(TestCase testCase)
|
|
: Thread(Options("InitDefaultIsolateThread")),
|
|
testCase_(testCase),
|
|
result_(false) {}
|
|
|
|
void Run() override {
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
isolate->Enter();
|
|
switch (testCase_) {
|
|
case SetFatalHandler:
|
|
isolate->SetFatalErrorHandler(nullptr);
|
|
break;
|
|
|
|
case SetCounterFunction:
|
|
CcTest::isolate()->SetCounterFunction(nullptr);
|
|
break;
|
|
|
|
case SetCreateHistogramFunction:
|
|
CcTest::isolate()->SetCreateHistogramFunction(nullptr);
|
|
break;
|
|
|
|
case SetAddHistogramSampleFunction:
|
|
CcTest::isolate()->SetAddHistogramSampleFunction(nullptr);
|
|
break;
|
|
}
|
|
isolate->Exit();
|
|
isolate->Dispose();
|
|
result_ = true;
|
|
}
|
|
|
|
bool result() { return result_; }
|
|
|
|
private:
|
|
TestCase testCase_;
|
|
bool result_;
|
|
};
|
|
|
|
|
|
static void InitializeTestHelper(InitDefaultIsolateThread::TestCase testCase) {
|
|
InitDefaultIsolateThread thread(testCase);
|
|
CHECK(thread.Start());
|
|
thread.Join();
|
|
CHECK(thread.result());
|
|
}
|
|
|
|
TEST(InitializeDefaultIsolateOnSecondaryThread_FatalHandler) {
|
|
InitializeTestHelper(InitDefaultIsolateThread::SetFatalHandler);
|
|
}
|
|
|
|
TEST(InitializeDefaultIsolateOnSecondaryThread_CounterFunction) {
|
|
InitializeTestHelper(InitDefaultIsolateThread::SetCounterFunction);
|
|
}
|
|
|
|
TEST(InitializeDefaultIsolateOnSecondaryThread_CreateHistogramFunction) {
|
|
InitializeTestHelper(InitDefaultIsolateThread::SetCreateHistogramFunction);
|
|
}
|
|
|
|
TEST(InitializeDefaultIsolateOnSecondaryThread_AddHistogramSampleFunction) {
|
|
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.
|
|
LocalContext context1;
|
|
v8::HandleScope scope(context1->GetIsolate());
|
|
ExpectString(code, "a");
|
|
ExpectString(code, "a");
|
|
}
|
|
|
|
{
|
|
// Change the String.prototype in the second context and check
|
|
// that the right function gets called.
|
|
LocalContext context2;
|
|
v8::HandleScope scope(context2->GetIsolate());
|
|
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.
|
|
LocalContext context1;
|
|
v8::HandleScope scope(context1->GetIsolate());
|
|
ExpectString(code, "42");
|
|
ExpectString(code, "42");
|
|
}
|
|
|
|
{
|
|
// Change the Number.prototype in the second context and check
|
|
// that the right function gets called.
|
|
LocalContext context2;
|
|
v8::HandleScope scope(context2->GetIsolate());
|
|
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.
|
|
LocalContext context1;
|
|
v8::HandleScope scope(context1->GetIsolate());
|
|
ExpectString(code, "true");
|
|
ExpectString(code, "true");
|
|
}
|
|
|
|
{
|
|
// Change the Boolean.prototype in the second context and check
|
|
// that the right function gets called.
|
|
LocalContext context2;
|
|
v8::HandleScope scope(context2->GetIsolate());
|
|
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.
|
|
LocalContext context1;
|
|
v8::HandleScope scope(context1->GetIsolate());
|
|
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.
|
|
LocalContext context2;
|
|
v8::HandleScope scope(context2->GetIsolate());
|
|
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\";");
|
|
CcTest::CollectAllGarbage();
|
|
ExpectString("readCell()", "new_second");
|
|
ExpectString("readCell()", "new_second");
|
|
}
|
|
}
|
|
|
|
|
|
class Visitor42 : public v8::PersistentHandleVisitor {
|
|
public:
|
|
explicit Visitor42(v8::Persistent<v8::Object>* object)
|
|
: counter_(0), object_(object) { }
|
|
|
|
void VisitPersistentHandle(Persistent<Value>* value,
|
|
uint16_t class_id) override {
|
|
if (class_id != 42) return;
|
|
CHECK_EQ(42, value->WrapperClassId());
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::Value> handle = v8::Local<v8::Value>::New(isolate, *value);
|
|
v8::Local<v8::Value> object = v8::Local<v8::Object>::New(isolate, *object_);
|
|
CHECK(handle->IsObject());
|
|
CHECK(Local<Object>::Cast(handle)
|
|
->Equals(isolate->GetCurrentContext(), object)
|
|
.FromJust());
|
|
++counter_;
|
|
}
|
|
|
|
int counter_;
|
|
v8::Persistent<v8::Object>* object_;
|
|
};
|
|
|
|
|
|
TEST(PersistentHandleVisitor) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Persistent<v8::Object> object(isolate, v8::Object::New(isolate));
|
|
CHECK_EQ(0, object.WrapperClassId());
|
|
object.SetWrapperClassId(42);
|
|
CHECK_EQ(42, object.WrapperClassId());
|
|
|
|
Visitor42 visitor(&object);
|
|
isolate->VisitHandlesWithClassIds(&visitor);
|
|
CHECK_EQ(1, visitor.counter_);
|
|
|
|
object.Reset();
|
|
}
|
|
|
|
|
|
TEST(WrapperClassId) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Persistent<v8::Object> object(isolate, v8::Object::New(isolate));
|
|
CHECK_EQ(0, object.WrapperClassId());
|
|
object.SetWrapperClassId(65535);
|
|
CHECK_EQ(65535, object.WrapperClassId());
|
|
object.Reset();
|
|
}
|
|
|
|
TEST(RegExp) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
v8::Local<v8::RegExp> re =
|
|
v8::RegExp::New(context.local(), v8_str("foo"), v8::RegExp::kNone)
|
|
.ToLocalChecked();
|
|
CHECK(re->IsRegExp());
|
|
CHECK(re->GetSource()->Equals(context.local(), v8_str("foo")).FromJust());
|
|
CHECK_EQ(v8::RegExp::kNone, re->GetFlags());
|
|
|
|
re = v8::RegExp::New(context.local(), v8_str("foo/bar"), v8::RegExp::kNone)
|
|
.ToLocalChecked();
|
|
CHECK(re->IsRegExp());
|
|
CHECK(
|
|
re->GetSource()->Equals(context.local(), v8_str("foo\\/bar")).FromJust());
|
|
CHECK_EQ(v8::RegExp::kNone, re->GetFlags());
|
|
|
|
re = v8::RegExp::New(context.local(), v8_str("bar"),
|
|
static_cast<v8::RegExp::Flags>(v8::RegExp::kIgnoreCase |
|
|
v8::RegExp::kGlobal))
|
|
.ToLocalChecked();
|
|
CHECK(re->IsRegExp());
|
|
CHECK(re->GetSource()->Equals(context.local(), v8_str("bar")).FromJust());
|
|
CHECK_EQ(v8::RegExp::kIgnoreCase | v8::RegExp::kGlobal,
|
|
static_cast<int>(re->GetFlags()));
|
|
|
|
re = v8::RegExp::New(context.local(), v8_str("baz"),
|
|
static_cast<v8::RegExp::Flags>(v8::RegExp::kIgnoreCase |
|
|
v8::RegExp::kMultiline))
|
|
.ToLocalChecked();
|
|
CHECK(re->IsRegExp());
|
|
CHECK(re->GetSource()->Equals(context.local(), v8_str("baz")).FromJust());
|
|
CHECK_EQ(v8::RegExp::kIgnoreCase | v8::RegExp::kMultiline,
|
|
static_cast<int>(re->GetFlags()));
|
|
|
|
re = v8::RegExp::New(context.local(), v8_str("baz"),
|
|
static_cast<v8::RegExp::Flags>(v8::RegExp::kUnicode |
|
|
v8::RegExp::kSticky))
|
|
.ToLocalChecked();
|
|
CHECK(re->IsRegExp());
|
|
CHECK(re->GetSource()->Equals(context.local(), v8_str("baz")).FromJust());
|
|
CHECK_EQ(v8::RegExp::kUnicode | v8::RegExp::kSticky,
|
|
static_cast<int>(re->GetFlags()));
|
|
|
|
re = CompileRun("/quux/").As<v8::RegExp>();
|
|
CHECK(re->IsRegExp());
|
|
CHECK(re->GetSource()->Equals(context.local(), v8_str("quux")).FromJust());
|
|
CHECK_EQ(v8::RegExp::kNone, re->GetFlags());
|
|
|
|
re = CompileRun("RegExp('qu/ux')").As<v8::RegExp>();
|
|
CHECK(re->IsRegExp());
|
|
CHECK(re->GetSource()->Equals(context.local(), v8_str("qu\\/ux")).FromJust());
|
|
CHECK_EQ(v8::RegExp::kNone, re->GetFlags());
|
|
|
|
re = CompileRun("/quux/gm").As<v8::RegExp>();
|
|
CHECK(re->IsRegExp());
|
|
CHECK(re->GetSource()->Equals(context.local(), v8_str("quux")).FromJust());
|
|
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(context.local(), v8_str("foobar"), v8::RegExp::kNone)
|
|
.ToLocalChecked();
|
|
CHECK(re->IsRegExp());
|
|
CHECK(re->GetSource()->Equals(context.local(), v8_str("foobar")).FromJust());
|
|
CHECK_EQ(v8::RegExp::kNone, re->GetFlags());
|
|
|
|
re = v8::RegExp::New(context.local(), v8_str("foobarbaz"),
|
|
static_cast<v8::RegExp::Flags>(v8::RegExp::kIgnoreCase |
|
|
v8::RegExp::kMultiline))
|
|
.ToLocalChecked();
|
|
CHECK(re->IsRegExp());
|
|
CHECK(
|
|
re->GetSource()->Equals(context.local(), v8_str("foobarbaz")).FromJust());
|
|
CHECK_EQ(v8::RegExp::kIgnoreCase | v8::RegExp::kMultiline,
|
|
static_cast<int>(re->GetFlags()));
|
|
|
|
CHECK(context->Global()->Set(context.local(), v8_str("re"), re).FromJust());
|
|
ExpectTrue("re.test('FoobarbaZ')");
|
|
|
|
// RegExps are objects on which you can set properties.
|
|
re->Set(context.local(), v8_str("property"),
|
|
v8::Integer::New(context->GetIsolate(), 32))
|
|
.FromJust();
|
|
v8::Local<v8::Value> value(CompileRun("re.property"));
|
|
CHECK_EQ(32, value->Int32Value(context.local()).FromJust());
|
|
|
|
{
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
CHECK(v8::RegExp::New(context.local(), v8_str("foo["), v8::RegExp::kNone)
|
|
.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("ex"), try_catch.Exception())
|
|
.FromJust());
|
|
ExpectTrue("ex instanceof SyntaxError");
|
|
}
|
|
|
|
// RegExp::Exec.
|
|
{
|
|
v8::Local<v8::RegExp> regexp =
|
|
v8::RegExp::New(context.local(), v8_str("a.c"), {}).ToLocalChecked();
|
|
v8::Local<v8::Object> result0 =
|
|
regexp->Exec(context.local(), v8_str("abc")).ToLocalChecked();
|
|
CHECK(result0->IsArray());
|
|
v8::Local<v8::Object> result1 =
|
|
regexp->Exec(context.local(), v8_str("abd")).ToLocalChecked();
|
|
CHECK(result1->IsNull());
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(Equals) {
|
|
LocalContext localContext;
|
|
v8::HandleScope handleScope(localContext->GetIsolate());
|
|
|
|
v8::Local<v8::Object> globalProxy = localContext->Global();
|
|
v8::Local<Value> global = globalProxy->GetPrototype();
|
|
|
|
CHECK(global->StrictEquals(global));
|
|
CHECK(!global->StrictEquals(globalProxy));
|
|
CHECK(!globalProxy->StrictEquals(global));
|
|
CHECK(globalProxy->StrictEquals(globalProxy));
|
|
|
|
CHECK(global->Equals(localContext.local(), global).FromJust());
|
|
CHECK(!global->Equals(localContext.local(), globalProxy).FromJust());
|
|
CHECK(!globalProxy->Equals(localContext.local(), global).FromJust());
|
|
CHECK(globalProxy->Equals(localContext.local(), globalProxy).FromJust());
|
|
}
|
|
|
|
|
|
static void Getter(v8::Local<v8::Name> property,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(v8_str("42!"));
|
|
}
|
|
|
|
|
|
static void Enumerator(const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate());
|
|
result->Set(info.GetIsolate()->GetCurrentContext(), 0,
|
|
v8_str("universalAnswer"))
|
|
.FromJust();
|
|
info.GetReturnValue().Set(result);
|
|
}
|
|
|
|
|
|
TEST(NamedEnumeratorAndForIn) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Context::Scope context_scope(context.local());
|
|
|
|
v8::Local<v8::ObjectTemplate> tmpl = v8::ObjectTemplate::New(isolate);
|
|
tmpl->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
Getter, nullptr, nullptr, nullptr, Enumerator));
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("o"),
|
|
tmpl->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
v8::Local<v8::Array> result = v8::Local<v8::Array>::Cast(
|
|
CompileRun("var result = []; for (var k in o) result.push(k); result"));
|
|
CHECK_EQ(1u, result->Length());
|
|
CHECK(v8_str("universalAnswer")
|
|
->Equals(context.local(),
|
|
result->Get(context.local(), 0).ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
TEST(DefinePropertyPostDetach) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
v8::Local<v8::Object> proxy = context->Global();
|
|
v8::Local<v8::Function> define_property =
|
|
CompileRun(
|
|
"(function() {"
|
|
" Object.defineProperty("
|
|
" this,"
|
|
" 1,"
|
|
" { configurable: true, enumerable: true, value: 3 });"
|
|
"})")
|
|
.As<Function>();
|
|
context->DetachGlobal();
|
|
CHECK(define_property->Call(context.local(), proxy, 0, nullptr).IsEmpty());
|
|
}
|
|
|
|
|
|
static void InstallContextId(v8::Local<Context> context, int id) {
|
|
Context::Scope scope(context);
|
|
CHECK(CompileRun("Object.prototype")
|
|
.As<Object>()
|
|
->Set(context, v8_str("context_id"),
|
|
v8::Integer::New(context->GetIsolate(), id))
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
static void CheckContextId(v8::Local<Object> object, int expected) {
|
|
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
|
|
CHECK_EQ(expected, object->Get(context, v8_str("context_id"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(CreationContext) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
HandleScope handle_scope(isolate);
|
|
Local<Context> context1 = Context::New(isolate);
|
|
InstallContextId(context1, 1);
|
|
Local<Context> context2 = Context::New(isolate);
|
|
InstallContextId(context2, 2);
|
|
Local<Context> context3 = Context::New(isolate);
|
|
InstallContextId(context3, 3);
|
|
|
|
Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New(isolate);
|
|
|
|
Local<Object> object1;
|
|
Local<Function> func1;
|
|
{
|
|
Context::Scope scope(context1);
|
|
object1 = Object::New(isolate);
|
|
func1 = tmpl->GetFunction(context1).ToLocalChecked();
|
|
}
|
|
|
|
Local<Object> object2;
|
|
Local<Function> func2;
|
|
{
|
|
Context::Scope scope(context2);
|
|
object2 = Object::New(isolate);
|
|
func2 = tmpl->GetFunction(context2).ToLocalChecked();
|
|
}
|
|
|
|
Local<Object> instance1;
|
|
Local<Object> instance2;
|
|
|
|
{
|
|
Context::Scope scope(context3);
|
|
instance1 = func1->NewInstance(context3).ToLocalChecked();
|
|
instance2 = func2->NewInstance(context3).ToLocalChecked();
|
|
}
|
|
|
|
{
|
|
Local<Context> other_context = Context::New(isolate);
|
|
Context::Scope scope(other_context);
|
|
CHECK(object1->GetCreationContext().ToLocalChecked() == context1);
|
|
CHECK(object1->GetCreationContextChecked() == context1);
|
|
CheckContextId(object1, 1);
|
|
CHECK(func1->GetCreationContext().ToLocalChecked() == context1);
|
|
CHECK(func1->GetCreationContextChecked() == context1);
|
|
CheckContextId(func1, 1);
|
|
CHECK(instance1->GetCreationContext().ToLocalChecked() == context1);
|
|
CHECK(instance1->GetCreationContextChecked() == context1);
|
|
CheckContextId(instance1, 1);
|
|
CHECK(object2->GetCreationContext().ToLocalChecked() == context2);
|
|
CHECK(object2->GetCreationContextChecked() == context2);
|
|
CheckContextId(object2, 2);
|
|
CHECK(func2->GetCreationContext().ToLocalChecked() == context2);
|
|
CHECK(func2->GetCreationContextChecked() == context2);
|
|
CheckContextId(func2, 2);
|
|
CHECK(instance2->GetCreationContext().ToLocalChecked() == context2);
|
|
CHECK(instance2->GetCreationContextChecked() == context2);
|
|
CheckContextId(instance2, 2);
|
|
}
|
|
|
|
{
|
|
Context::Scope scope(context1);
|
|
CHECK(object1->GetCreationContext().ToLocalChecked() == context1);
|
|
CheckContextId(object1, 1);
|
|
CHECK(func1->GetCreationContext().ToLocalChecked() == context1);
|
|
CheckContextId(func1, 1);
|
|
CHECK(instance1->GetCreationContext().ToLocalChecked() == context1);
|
|
CheckContextId(instance1, 1);
|
|
CHECK(object2->GetCreationContext().ToLocalChecked() == context2);
|
|
CheckContextId(object2, 2);
|
|
CHECK(func2->GetCreationContext().ToLocalChecked() == context2);
|
|
CheckContextId(func2, 2);
|
|
CHECK(instance2->GetCreationContext().ToLocalChecked() == context2);
|
|
CheckContextId(instance2, 2);
|
|
}
|
|
|
|
{
|
|
Context::Scope scope(context2);
|
|
CHECK(object1->GetCreationContext().ToLocalChecked() == context1);
|
|
CheckContextId(object1, 1);
|
|
CHECK(func1->GetCreationContext().ToLocalChecked() == context1);
|
|
CheckContextId(func1, 1);
|
|
CHECK(instance1->GetCreationContext().ToLocalChecked() == context1);
|
|
CheckContextId(instance1, 1);
|
|
CHECK(object2->GetCreationContext().ToLocalChecked() == context2);
|
|
CheckContextId(object2, 2);
|
|
CHECK(func2->GetCreationContext().ToLocalChecked() == context2);
|
|
CheckContextId(func2, 2);
|
|
CHECK(instance2->GetCreationContext().ToLocalChecked() == context2);
|
|
CheckContextId(instance2, 2);
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(CreationContextOfJsFunction) {
|
|
HandleScope handle_scope(CcTest::isolate());
|
|
Local<Context> context = Context::New(CcTest::isolate());
|
|
InstallContextId(context, 1);
|
|
|
|
Local<Object> function;
|
|
{
|
|
Context::Scope scope(context);
|
|
function = CompileRun("function foo() {}; foo").As<Object>();
|
|
}
|
|
|
|
Local<Context> other_context = Context::New(CcTest::isolate());
|
|
Context::Scope scope(other_context);
|
|
CHECK(function->GetCreationContext().ToLocalChecked() == context);
|
|
CheckContextId(function, 1);
|
|
}
|
|
|
|
|
|
THREADED_TEST(CreationContextOfJsBoundFunction) {
|
|
HandleScope handle_scope(CcTest::isolate());
|
|
Local<Context> context1 = Context::New(CcTest::isolate());
|
|
InstallContextId(context1, 1);
|
|
Local<Context> context2 = Context::New(CcTest::isolate());
|
|
InstallContextId(context2, 2);
|
|
|
|
Local<Function> target_function;
|
|
{
|
|
Context::Scope scope(context1);
|
|
target_function = CompileRun("function foo() {}; foo").As<Function>();
|
|
}
|
|
|
|
Local<Function> bound_function1, bound_function2;
|
|
{
|
|
Context::Scope scope(context2);
|
|
CHECK(context2->Global()
|
|
->Set(context2, v8_str("foo"), target_function)
|
|
.FromJust());
|
|
bound_function1 = CompileRun("foo.bind(1)").As<Function>();
|
|
bound_function2 =
|
|
CompileRun("Function.prototype.bind.call(foo, 2)").As<Function>();
|
|
}
|
|
|
|
Local<Context> other_context = Context::New(CcTest::isolate());
|
|
Context::Scope scope(other_context);
|
|
CHECK(bound_function1->GetCreationContext().ToLocalChecked() == context1);
|
|
CheckContextId(bound_function1, 1);
|
|
CHECK(bound_function2->GetCreationContext().ToLocalChecked() == context1);
|
|
CheckContextId(bound_function2, 1);
|
|
}
|
|
|
|
void HasOwnPropertyIndexedPropertyGetter(
|
|
uint32_t index,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
if (index == 42) info.GetReturnValue().Set(v8_str("yes"));
|
|
}
|
|
|
|
|
|
void HasOwnPropertyNamedPropertyGetter(
|
|
Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
if (property->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("foo"))
|
|
.FromJust()) {
|
|
info.GetReturnValue().Set(v8_str("yes"));
|
|
}
|
|
}
|
|
|
|
|
|
void HasOwnPropertyIndexedPropertyQuery(
|
|
uint32_t index, const v8::PropertyCallbackInfo<v8::Integer>& info) {
|
|
if (index == 42) info.GetReturnValue().Set(1);
|
|
}
|
|
|
|
|
|
void HasOwnPropertyNamedPropertyQuery(
|
|
Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& info) {
|
|
if (property->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("foo"))
|
|
.FromJust()) {
|
|
info.GetReturnValue().Set(1);
|
|
}
|
|
}
|
|
|
|
|
|
void HasOwnPropertyNamedPropertyQuery2(
|
|
Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& info) {
|
|
if (property->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("bar"))
|
|
.FromJust()) {
|
|
info.GetReturnValue().Set(1);
|
|
}
|
|
}
|
|
|
|
void HasOwnPropertyAccessorGetter(
|
|
Local<String> property,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(v8_str("yes"));
|
|
}
|
|
|
|
void HasOwnPropertyAccessorNameGetter(
|
|
Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(v8_str("yes"));
|
|
}
|
|
|
|
TEST(HasOwnProperty) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
{ // Check normal properties and defined getters.
|
|
Local<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());
|
|
Local<Object> object = value->ToObject(env.local()).ToLocalChecked();
|
|
CHECK(object->Has(env.local(), v8_str("foo")).FromJust());
|
|
CHECK(!object->HasOwnProperty(env.local(), v8_str("foo")).FromJust());
|
|
CHECK(object->HasOwnProperty(env.local(), v8_str("bar")).FromJust());
|
|
CHECK(object->Has(env.local(), v8_str("baz")).FromJust());
|
|
CHECK(!object->HasOwnProperty(env.local(), v8_str("baz")).FromJust());
|
|
CHECK(object->HasOwnProperty(env.local(), v8_str("bla")).FromJust());
|
|
}
|
|
{ // Check named getter interceptors.
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
HasOwnPropertyNamedPropertyGetter));
|
|
Local<Object> instance = templ->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(!instance->HasOwnProperty(env.local(), v8_str("42")).FromJust());
|
|
CHECK(!instance->HasOwnProperty(env.local(), 42).FromJust());
|
|
CHECK(instance->HasOwnProperty(env.local(), v8_str("foo")).FromJust());
|
|
CHECK(!instance->HasOwnProperty(env.local(), v8_str("bar")).FromJust());
|
|
}
|
|
{ // Check indexed getter interceptors.
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
HasOwnPropertyIndexedPropertyGetter));
|
|
Local<Object> instance = templ->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(instance->HasOwnProperty(env.local(), v8_str("42")).FromJust());
|
|
CHECK(instance->HasOwnProperty(env.local(), 42).FromJust());
|
|
CHECK(!instance->HasOwnProperty(env.local(), v8_str("43")).FromJust());
|
|
CHECK(!instance->HasOwnProperty(env.local(), 43).FromJust());
|
|
CHECK(!instance->HasOwnProperty(env.local(), v8_str("foo")).FromJust());
|
|
}
|
|
{ // Check named query interceptors.
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
nullptr, nullptr, HasOwnPropertyNamedPropertyQuery));
|
|
Local<Object> instance = templ->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(instance->HasOwnProperty(env.local(), v8_str("foo")).FromJust());
|
|
CHECK(!instance->HasOwnProperty(env.local(), v8_str("bar")).FromJust());
|
|
}
|
|
{ // Check indexed query interceptors.
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
nullptr, nullptr, HasOwnPropertyIndexedPropertyQuery));
|
|
Local<Object> instance = templ->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(instance->HasOwnProperty(env.local(), v8_str("42")).FromJust());
|
|
CHECK(instance->HasOwnProperty(env.local(), 42).FromJust());
|
|
CHECK(!instance->HasOwnProperty(env.local(), v8_str("41")).FromJust());
|
|
CHECK(!instance->HasOwnProperty(env.local(), 41).FromJust());
|
|
}
|
|
{ // Check callbacks.
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("foo"), HasOwnPropertyAccessorGetter);
|
|
Local<Object> instance = templ->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(instance->HasOwnProperty(env.local(), v8_str("foo")).FromJust());
|
|
CHECK(!instance->HasOwnProperty(env.local(), v8_str("bar")).FromJust());
|
|
}
|
|
{ // Check that query wins on disagreement.
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
HasOwnPropertyNamedPropertyGetter, nullptr,
|
|
HasOwnPropertyNamedPropertyQuery2));
|
|
Local<Object> instance = templ->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(!instance->HasOwnProperty(env.local(), v8_str("foo")).FromJust());
|
|
CHECK(instance->HasOwnProperty(env.local(), v8_str("bar")).FromJust());
|
|
}
|
|
{ // Check that non-internalized keys are handled correctly.
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
HasOwnPropertyAccessorNameGetter));
|
|
Local<Object> instance = templ->NewInstance(env.local()).ToLocalChecked();
|
|
env->Global()->Set(env.local(), v8_str("obj"), instance).FromJust();
|
|
const char* src =
|
|
"var dyn_string = 'this string ';"
|
|
"dyn_string += 'does not exist elsewhere';"
|
|
"({}).hasOwnProperty.call(obj, dyn_string)";
|
|
CHECK(CompileRun(src)->BooleanValue(isolate));
|
|
}
|
|
}
|
|
|
|
|
|
TEST(IndexedInterceptorWithStringProto) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
|
|
nullptr, nullptr, HasOwnPropertyIndexedPropertyQuery));
|
|
LocalContext context;
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CompileRun("var s = new String('foobar'); obj.__proto__ = s;");
|
|
// These should be intercepted.
|
|
CHECK(CompileRun("42 in obj")->BooleanValue(isolate));
|
|
CHECK(CompileRun("'42' in obj")->BooleanValue(isolate));
|
|
// These should fall through to the String prototype.
|
|
CHECK(CompileRun("0 in obj")->BooleanValue(isolate));
|
|
CHECK(CompileRun("'0' in obj")->BooleanValue(isolate));
|
|
// And these should both fail.
|
|
CHECK(!CompileRun("32 in obj")->BooleanValue(isolate));
|
|
CHECK(!CompileRun("'32' in obj")->BooleanValue(isolate));
|
|
}
|
|
|
|
|
|
void CheckCodeGenerationAllowed() {
|
|
Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
|
|
Local<Value> result = CompileRun("eval('42')");
|
|
CHECK_EQ(42, result->Int32Value(context).FromJust());
|
|
result = CompileRun("(function(e) { return e('42'); })(eval)");
|
|
CHECK_EQ(42, result->Int32Value(context).FromJust());
|
|
result = CompileRun("var f = new Function('return 42'); f()");
|
|
CHECK_EQ(42, result->Int32Value(context).FromJust());
|
|
}
|
|
|
|
|
|
void CheckCodeGenerationDisallowed() {
|
|
TryCatch try_catch(CcTest::isolate());
|
|
|
|
Local<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());
|
|
}
|
|
|
|
char first_fourty_bytes[41];
|
|
|
|
v8::ModifyCodeGenerationFromStringsResult CodeGenerationAllowed(
|
|
Local<Context> context, Local<Value> source, bool is_code_like) {
|
|
String::Utf8Value str(CcTest::isolate(), source);
|
|
size_t len = std::min(sizeof(first_fourty_bytes) - 1,
|
|
static_cast<size_t>(str.length()));
|
|
strncpy(first_fourty_bytes, *str, len);
|
|
first_fourty_bytes[len] = 0;
|
|
ApiTestFuzzer::Fuzz();
|
|
return {true, {}};
|
|
}
|
|
|
|
v8::ModifyCodeGenerationFromStringsResult CodeGenerationDisallowed(
|
|
Local<Context> context, Local<Value> source, bool is_code_like) {
|
|
ApiTestFuzzer::Fuzz();
|
|
return {false, {}};
|
|
}
|
|
|
|
v8::ModifyCodeGenerationFromStringsResult ModifyCodeGeneration(
|
|
Local<Context> context, Local<Value> source, bool is_code_like) {
|
|
// Allow (passthrough, unmodified) all objects that are not strings.
|
|
if (!source->IsString()) {
|
|
return {/* codegen_allowed= */ true, v8::MaybeLocal<String>()};
|
|
}
|
|
|
|
String::Utf8Value utf8(context->GetIsolate(), source);
|
|
DCHECK_GT(utf8.length(), 0);
|
|
|
|
// Allow (unmodified) all strings that contain "44".
|
|
if (strstr(*utf8, "44") != nullptr) {
|
|
return {/* codegen_allowed= */ true, v8::MaybeLocal<String>()};
|
|
}
|
|
|
|
// Deny all odd-length strings.
|
|
if (utf8.length() == 0 || utf8.length() % 2 != 0) {
|
|
return {/* codegen_allowed= */ false, v8::MaybeLocal<String>()};
|
|
}
|
|
|
|
// Allow even-length strings and modify them by replacing all '2' with '3'.
|
|
for (char* i = *utf8; *i != '\0'; i++) {
|
|
if (*i == '2') *i = '3';
|
|
}
|
|
return {/* codegen_allowed= */ true,
|
|
String::NewFromUtf8(context->GetIsolate(), *utf8).ToLocalChecked()};
|
|
}
|
|
|
|
THREADED_TEST(AllowCodeGenFromStrings) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
// 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);
|
|
context->GetIsolate()->SetModifyCodeGenerationFromStringsCallback(
|
|
&CodeGenerationAllowed);
|
|
CHECK(!context->IsCodeGenerationFromStringsAllowed());
|
|
CheckCodeGenerationAllowed();
|
|
|
|
// Set a callback that disallows the code generation.
|
|
context->GetIsolate()->SetModifyCodeGenerationFromStringsCallback(
|
|
&CodeGenerationDisallowed);
|
|
CHECK(!context->IsCodeGenerationFromStringsAllowed());
|
|
CheckCodeGenerationDisallowed();
|
|
}
|
|
|
|
TEST(ModifyCodeGenFromStrings) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
context->AllowCodeGenerationFromStrings(false);
|
|
context->GetIsolate()->SetModifyCodeGenerationFromStringsCallback(
|
|
&ModifyCodeGeneration);
|
|
|
|
// Test 'allowed' case in different modes (direct eval, indirect eval,
|
|
// Function constructor, Function contructor with arguments).
|
|
Local<Value> result = CompileRun("eval('42')");
|
|
CHECK_EQ(43, result->Int32Value(context.local()).FromJust());
|
|
|
|
result = CompileRun("(function(e) { return e('42'); })(eval)");
|
|
CHECK_EQ(43, result->Int32Value(context.local()).FromJust());
|
|
|
|
result = CompileRun("var f = new Function('return 42;'); f()");
|
|
CHECK_EQ(43, result->Int32Value(context.local()).FromJust());
|
|
|
|
result = CompileRun("eval(43)");
|
|
CHECK_EQ(43, result->Int32Value(context.local()).FromJust());
|
|
|
|
result = CompileRun("var f = new Function('return 44;'); f();");
|
|
CHECK_EQ(44, result->Int32Value(context.local()).FromJust());
|
|
|
|
// Test 'disallowed' cases.
|
|
TryCatch try_catch(CcTest::isolate());
|
|
result = CompileRun("eval('123')");
|
|
CHECK(result.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
result = CompileRun("new Function('a', 'return 42;')(123)");
|
|
CHECK(result.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
}
|
|
|
|
v8::ModifyCodeGenerationFromStringsResult RejectStringsIncrementNumbers(
|
|
Local<Context> context, Local<Value> source, bool is_code_like) {
|
|
if (source->IsString()) {
|
|
return {false, v8::MaybeLocal<String>()};
|
|
}
|
|
|
|
Local<v8::Number> number;
|
|
if (!source->ToNumber(context).ToLocal(&number)) {
|
|
return {true, v8::MaybeLocal<String>()};
|
|
}
|
|
|
|
Local<v8::String> incremented =
|
|
String::NewFromUtf8(context->GetIsolate(),
|
|
std::to_string(number->Value() + 1).c_str(),
|
|
v8::NewStringType::kNormal)
|
|
.ToLocalChecked();
|
|
|
|
return {true, incremented};
|
|
}
|
|
|
|
TEST(AllowFromStringsOrModifyCodegen) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
context->GetIsolate()->SetModifyCodeGenerationFromStringsCallback(
|
|
&RejectStringsIncrementNumbers);
|
|
|
|
context->AllowCodeGenerationFromStrings(false);
|
|
|
|
TryCatch try_catch(CcTest::isolate());
|
|
Local<Value> result = CompileRun("eval('40+2')");
|
|
CHECK(result.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
try_catch.Reset();
|
|
|
|
result = CompileRun("eval(42)");
|
|
CHECK_EQ(43, result->Int32Value(context.local()).FromJust());
|
|
|
|
context->AllowCodeGenerationFromStrings(true);
|
|
|
|
result = CompileRun("eval('40+2')");
|
|
CHECK_EQ(42, result->Int32Value(context.local()).FromJust());
|
|
|
|
result = CompileRun("eval(42)");
|
|
CHECK_EQ(43, result->Int32Value(context.local()).FromJust());
|
|
}
|
|
|
|
TEST(SetErrorMessageForCodeGenFromStrings) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
TryCatch try_catch(context->GetIsolate());
|
|
|
|
Local<String> message = v8_str("Message");
|
|
Local<String> expected_message = v8_str("Uncaught EvalError: Message");
|
|
context->GetIsolate()->SetModifyCodeGenerationFromStringsCallback(
|
|
&CodeGenerationDisallowed);
|
|
context->AllowCodeGenerationFromStrings(false);
|
|
context->SetErrorMessageForCodeGenerationFromStrings(message);
|
|
Local<Value> result = CompileRun("eval('42')");
|
|
CHECK(result.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
Local<String> actual_message = try_catch.Message()->Get();
|
|
CHECK(expected_message->Equals(context.local(), actual_message).FromJust());
|
|
}
|
|
|
|
TEST(CaptureSourceForCodeGenFromStrings) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
TryCatch try_catch(context->GetIsolate());
|
|
|
|
context->GetIsolate()->SetModifyCodeGenerationFromStringsCallback(
|
|
&CodeGenerationAllowed);
|
|
context->AllowCodeGenerationFromStrings(false);
|
|
CompileRun("eval('42')");
|
|
CHECK(!strcmp(first_fourty_bytes, "42"));
|
|
}
|
|
|
|
static void NonObjectThis(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
}
|
|
|
|
|
|
THREADED_TEST(CallAPIFunctionOnNonObject) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<FunctionTemplate> templ =
|
|
v8::FunctionTemplate::New(isolate, NonObjectThis);
|
|
Local<Function> function =
|
|
templ->GetFunction(context.local()).ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f"), function)
|
|
.FromJust());
|
|
TryCatch try_catch(isolate);
|
|
CompileRun("f.call(2)");
|
|
}
|
|
|
|
|
|
// Regression test for issue 1470.
|
|
THREADED_TEST(ReadOnlyIndexedProperties) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
|
|
LocalContext context;
|
|
Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
|
|
CHECK(context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust());
|
|
obj->DefineOwnProperty(context.local(), v8_str("1"), v8_str("DONT_CHANGE"),
|
|
v8::ReadOnly)
|
|
.FromJust();
|
|
obj->Set(context.local(), v8_str("1"), v8_str("foobar")).FromJust();
|
|
CHECK(v8_str("DONT_CHANGE")
|
|
->Equals(context.local(),
|
|
obj->Get(context.local(), v8_str("1")).ToLocalChecked())
|
|
.FromJust());
|
|
obj->DefineOwnProperty(context.local(), v8_str("2"), v8_str("DONT_CHANGE"),
|
|
v8::ReadOnly)
|
|
.FromJust();
|
|
obj->Set(context.local(), v8_num(2), v8_str("foobar")).FromJust();
|
|
CHECK(v8_str("DONT_CHANGE")
|
|
->Equals(context.local(),
|
|
obj->Get(context.local(), v8_num(2)).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// Test non-smi case.
|
|
obj->DefineOwnProperty(context.local(), v8_str("2000000000"),
|
|
v8_str("DONT_CHANGE"), v8::ReadOnly)
|
|
.FromJust();
|
|
obj->Set(context.local(), v8_str("2000000000"), v8_str("foobar")).FromJust();
|
|
CHECK(v8_str("DONT_CHANGE")
|
|
->Equals(context.local(),
|
|
obj->Get(context.local(), v8_str("2000000000"))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
|
|
static int CountLiveMapsInMapCache(i::Context context) {
|
|
i::WeakFixedArray map_cache = i::WeakFixedArray::cast(context.map_cache());
|
|
int length = map_cache.length();
|
|
int count = 0;
|
|
for (int i = 0; i < length; i++) {
|
|
if (map_cache.Get(i)->IsWeak()) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
THREADED_TEST(Regress1516) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
// Object with 20 properties is not a common case, so it should be removed
|
|
// from the cache after GC.
|
|
{ v8::HandleScope temp_scope(context->GetIsolate());
|
|
CompileRun(
|
|
"({"
|
|
"'a00': 0, 'a01': 0, 'a02': 0, 'a03': 0, 'a04': 0, "
|
|
"'a05': 0, 'a06': 0, 'a07': 0, 'a08': 0, 'a09': 0, "
|
|
"'a10': 0, 'a11': 0, 'a12': 0, 'a13': 0, 'a14': 0, "
|
|
"'a15': 0, 'a16': 0, 'a17': 0, 'a18': 0, 'a19': 0, "
|
|
"})");
|
|
}
|
|
|
|
int elements = CountLiveMapsInMapCache(CcTest::i_isolate()->context());
|
|
CHECK_LE(1, elements);
|
|
|
|
// We have to abort incremental marking here to abandon black pages.
|
|
CcTest::PreciseCollectAllGarbage();
|
|
|
|
CHECK_GT(elements, CountLiveMapsInMapCache(CcTest::i_isolate()->context()));
|
|
}
|
|
|
|
|
|
static void TestReceiver(Local<Value> expected_result,
|
|
Local<Value> expected_receiver,
|
|
const char* code) {
|
|
Local<Value> result = CompileRun(code);
|
|
Local<Context> context = CcTest::isolate()->GetCurrentContext();
|
|
CHECK(result->IsObject());
|
|
CHECK(expected_receiver
|
|
->Equals(context,
|
|
result.As<v8::Object>()->Get(context, 1).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(expected_result
|
|
->Equals(context,
|
|
result.As<v8::Object>()->Get(context, 0).ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
THREADED_TEST(ForeignFunctionReceiver) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
HandleScope scope(isolate);
|
|
|
|
// 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).
|
|
|
|
Local<Context> foreign_context = v8::Context::New(isolate);
|
|
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");
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("func"), foreign_function)
|
|
.FromJust());
|
|
|
|
// Sanity check the contexts.
|
|
CHECK(
|
|
i->Equals(
|
|
context.local(),
|
|
foreign_context->Global()->Get(context.local(), id).ToLocalChecked())
|
|
.FromJust());
|
|
CHECK(o->Equals(context.local(),
|
|
context->Global()->Get(context.local(), id).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// 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(context.local(), CompileRun("'abcbd'.replace(/b/,ownfunc)[1]"))
|
|
.FromJust());
|
|
CHECK(
|
|
o->Equals(context.local(), CompileRun("'abcbd'.replace(/b/g,ownfunc)[1]"))
|
|
.FromJust());
|
|
CHECK(
|
|
o->Equals(context.local(), CompileRun("'abcbd'.replace(/b/g,ownfunc)[3]"))
|
|
.FromJust());
|
|
// 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(context.local(), CompileRun("'abcbd'.replace(/b/,func)[1]"))
|
|
.FromJust());
|
|
CHECK(i->Equals(context.local(), CompileRun("'abcbd'.replace(/b/g,func)[1]"))
|
|
.FromJust());
|
|
CHECK(i->Equals(context.local(), CompileRun("'abcbd'.replace(/b/g,func)[3]"))
|
|
.FromJust());
|
|
|
|
// Calling with environment record as base.
|
|
TestReceiver(i, foreign_context->Global(), "func()");
|
|
// Calling with no base.
|
|
TestReceiver(i, foreign_context->Global(), "(1,func)()");
|
|
}
|
|
|
|
|
|
uint8_t callback_fired = 0;
|
|
uint8_t before_call_entered_callback_count1 = 0;
|
|
uint8_t before_call_entered_callback_count2 = 0;
|
|
|
|
|
|
void CallCompletedCallback1(v8::Isolate*) {
|
|
v8::base::OS::Print("Firing callback 1.\n");
|
|
callback_fired ^= 1; // Toggle first bit.
|
|
}
|
|
|
|
|
|
void CallCompletedCallback2(v8::Isolate*) {
|
|
v8::base::OS::Print("Firing callback 2.\n");
|
|
callback_fired ^= 2; // Toggle second bit.
|
|
}
|
|
|
|
|
|
void BeforeCallEnteredCallback1(v8::Isolate*) {
|
|
v8::base::OS::Print("Firing before call entered callback 1.\n");
|
|
before_call_entered_callback_count1++;
|
|
}
|
|
|
|
|
|
void BeforeCallEnteredCallback2(v8::Isolate*) {
|
|
v8::base::OS::Print("Firing before call entered callback 2.\n");
|
|
before_call_entered_callback_count2++;
|
|
}
|
|
|
|
|
|
void RecursiveCall(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
int32_t level =
|
|
args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust();
|
|
if (level < 3) {
|
|
level++;
|
|
v8::base::OS::Print("Entering recursion level %d.\n", level);
|
|
char script[64];
|
|
v8::base::Vector<char> script_vector(script, sizeof(script));
|
|
v8::base::SNPrintF(script_vector, "recursion(%d)", level);
|
|
CompileRun(script_vector.begin());
|
|
v8::base::OS::Print("Leaving recursion level %d.\n", level);
|
|
CHECK_EQ(0, callback_fired);
|
|
} else {
|
|
v8::base::OS::Print("Recursion ends.\n");
|
|
CHECK_EQ(0, callback_fired);
|
|
}
|
|
}
|
|
|
|
|
|
TEST(CallCompletedCallback) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
v8::Local<v8::FunctionTemplate> recursive_runtime =
|
|
v8::FunctionTemplate::New(env->GetIsolate(), RecursiveCall);
|
|
env->Global()
|
|
->Set(env.local(), v8_str("recursion"),
|
|
recursive_runtime->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust();
|
|
// Adding the same callback a second time has no effect.
|
|
env->GetIsolate()->AddCallCompletedCallback(CallCompletedCallback1);
|
|
env->GetIsolate()->AddCallCompletedCallback(CallCompletedCallback1);
|
|
env->GetIsolate()->AddCallCompletedCallback(CallCompletedCallback2);
|
|
env->GetIsolate()->AddBeforeCallEnteredCallback(BeforeCallEnteredCallback1);
|
|
env->GetIsolate()->AddBeforeCallEnteredCallback(BeforeCallEnteredCallback2);
|
|
env->GetIsolate()->AddBeforeCallEnteredCallback(BeforeCallEnteredCallback1);
|
|
v8::base::OS::Print("--- Script (1) ---\n");
|
|
callback_fired = 0;
|
|
before_call_entered_callback_count1 = 0;
|
|
before_call_entered_callback_count2 = 0;
|
|
Local<Script> script =
|
|
v8::Script::Compile(env.local(), v8_str("recursion(0)")).ToLocalChecked();
|
|
script->Run(env.local()).ToLocalChecked();
|
|
CHECK_EQ(3, callback_fired);
|
|
CHECK_EQ(4, before_call_entered_callback_count1);
|
|
CHECK_EQ(4, before_call_entered_callback_count2);
|
|
|
|
v8::base::OS::Print("\n--- Script (2) ---\n");
|
|
callback_fired = 0;
|
|
before_call_entered_callback_count1 = 0;
|
|
before_call_entered_callback_count2 = 0;
|
|
env->GetIsolate()->RemoveCallCompletedCallback(CallCompletedCallback1);
|
|
env->GetIsolate()->RemoveBeforeCallEnteredCallback(
|
|
BeforeCallEnteredCallback1);
|
|
script->Run(env.local()).ToLocalChecked();
|
|
CHECK_EQ(2, callback_fired);
|
|
CHECK_EQ(0, before_call_entered_callback_count1);
|
|
CHECK_EQ(4, before_call_entered_callback_count2);
|
|
|
|
v8::base::OS::Print("\n--- Function ---\n");
|
|
callback_fired = 0;
|
|
before_call_entered_callback_count1 = 0;
|
|
before_call_entered_callback_count2 = 0;
|
|
Local<Function> recursive_function = Local<Function>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("recursion")).ToLocalChecked());
|
|
v8::Local<Value> args[] = {v8_num(0)};
|
|
recursive_function->Call(env.local(), env->Global(), 1, args)
|
|
.ToLocalChecked();
|
|
CHECK_EQ(2, callback_fired);
|
|
CHECK_EQ(0, before_call_entered_callback_count1);
|
|
CHECK_EQ(4, before_call_entered_callback_count2);
|
|
}
|
|
|
|
|
|
void CallCompletedCallbackNoException(v8::Isolate*) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
CompileRun("1+1;");
|
|
}
|
|
|
|
|
|
void CallCompletedCallbackException(v8::Isolate*) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
CompileRun("throw 'second exception';");
|
|
}
|
|
|
|
|
|
TEST(CallCompletedCallbackOneException) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
env->GetIsolate()->AddCallCompletedCallback(CallCompletedCallbackNoException);
|
|
CompileRun("throw 'exception';");
|
|
}
|
|
|
|
|
|
TEST(CallCompletedCallbackTwoExceptions) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
env->GetIsolate()->AddCallCompletedCallback(CallCompletedCallbackException);
|
|
CompileRun("throw 'first exception';");
|
|
}
|
|
|
|
|
|
static void MicrotaskOne(const v8::FunctionCallbackInfo<Value>& info) {
|
|
CHECK(v8::MicrotasksScope::IsRunningMicrotasks(info.GetIsolate()));
|
|
v8::HandleScope scope(info.GetIsolate());
|
|
v8::MicrotasksScope microtasks(info.GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
CompileRun("ext1Calls++;");
|
|
}
|
|
|
|
|
|
static void MicrotaskTwo(const v8::FunctionCallbackInfo<Value>& info) {
|
|
CHECK(v8::MicrotasksScope::IsRunningMicrotasks(info.GetIsolate()));
|
|
v8::HandleScope scope(info.GetIsolate());
|
|
v8::MicrotasksScope microtasks(info.GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
CompileRun("ext2Calls++;");
|
|
}
|
|
|
|
void* g_passed_to_three = nullptr;
|
|
|
|
static void MicrotaskThree(void* data) {
|
|
g_passed_to_three = data;
|
|
}
|
|
|
|
|
|
TEST(EnqueueMicrotask) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
CHECK(!v8::MicrotasksScope::IsRunningMicrotasks(env->GetIsolate()));
|
|
CompileRun(
|
|
"var ext1Calls = 0;"
|
|
"var ext2Calls = 0;");
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(0, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(1, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(2, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(2, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
|
|
g_passed_to_three = nullptr;
|
|
env->GetIsolate()->EnqueueMicrotask(MicrotaskThree);
|
|
CompileRun("1+1;");
|
|
CHECK(!g_passed_to_three);
|
|
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(2, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
|
|
int dummy;
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
env->GetIsolate()->EnqueueMicrotask(MicrotaskThree, &dummy);
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(&dummy, g_passed_to_three);
|
|
CHECK_EQ(3, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(3, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
g_passed_to_three = nullptr;
|
|
}
|
|
|
|
|
|
static void MicrotaskExceptionOne(
|
|
const v8::FunctionCallbackInfo<Value>& info) {
|
|
v8::HandleScope scope(info.GetIsolate());
|
|
CompileRun("exception1Calls++;");
|
|
info.GetIsolate()->ThrowException(
|
|
v8::Exception::Error(v8_str("first")));
|
|
}
|
|
|
|
|
|
static void MicrotaskExceptionTwo(
|
|
const v8::FunctionCallbackInfo<Value>& info) {
|
|
v8::HandleScope scope(info.GetIsolate());
|
|
CompileRun("exception2Calls++;");
|
|
info.GetIsolate()->ThrowException(
|
|
v8::Exception::Error(v8_str("second")));
|
|
}
|
|
|
|
int handler_call_count = 0;
|
|
static void MicrotaskExceptionHandler(Local<Message> message,
|
|
Local<Value> exception) {
|
|
CHECK(exception->IsNativeError());
|
|
Local<Context> context = message->GetIsolate()->GetCurrentContext();
|
|
Local<String> str = exception->ToString(context).ToLocalChecked();
|
|
switch (handler_call_count++) {
|
|
case 0:
|
|
CHECK(str->StrictEquals(v8_str("Error: first")));
|
|
break;
|
|
case 1:
|
|
CHECK(str->StrictEquals(v8_str("Error: second")));
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
TEST(RunMicrotasksIgnoresThrownExceptions) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
isolate->AddMessageListenerWithErrorLevel(MicrotaskExceptionHandler,
|
|
v8::Isolate::kMessageAll);
|
|
CompileRun(
|
|
"var exception1Calls = 0;"
|
|
"var exception2Calls = 0;");
|
|
isolate->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskExceptionOne).ToLocalChecked());
|
|
isolate->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskExceptionTwo).ToLocalChecked());
|
|
TryCatch try_catch(isolate);
|
|
CompileRun("1+1;");
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(handler_call_count, 2);
|
|
CHECK_EQ(1,
|
|
CompileRun("exception1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(1,
|
|
CompileRun("exception2Calls")->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
static void ThrowExceptionMicrotask(void* data) {
|
|
CcTest::isolate()->ThrowException(v8_str("exception"));
|
|
}
|
|
|
|
int microtask_callback_count = 0;
|
|
|
|
static void IncrementCounterMicrotask(void* data) {
|
|
microtask_callback_count++;
|
|
}
|
|
|
|
TEST(RunMicrotasksIgnoresThrownExceptionsFromApi) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
{
|
|
CHECK(!isolate->IsExecutionTerminating());
|
|
isolate->EnqueueMicrotask(ThrowExceptionMicrotask);
|
|
isolate->EnqueueMicrotask(IncrementCounterMicrotask);
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(1, microtask_callback_count);
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
}
|
|
|
|
uint8_t microtasks_completed_callback_count = 0;
|
|
|
|
static void MicrotasksCompletedCallback(v8::Isolate* isolate, void*) {
|
|
++microtasks_completed_callback_count;
|
|
}
|
|
|
|
TEST(SetAutorunMicrotasks) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
env->GetIsolate()->AddMicrotasksCompletedCallback(
|
|
&MicrotasksCompletedCallback);
|
|
|
|
// If the policy is auto, there's a microtask checkpoint at the end of every
|
|
// zero-depth API call.
|
|
CompileRun(
|
|
"var ext1Calls = 0;"
|
|
"var ext2Calls = 0;");
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(0, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(4u, microtasks_completed_callback_count);
|
|
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(7u, microtasks_completed_callback_count);
|
|
|
|
// If the policy is explicit, microtask checkpoints are explicitly invoked.
|
|
env->GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(7u, microtasks_completed_callback_count);
|
|
|
|
env->GetIsolate()->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(1, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(8u, microtasks_completed_callback_count);
|
|
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(1, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(8u, microtasks_completed_callback_count);
|
|
|
|
env->GetIsolate()->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(2, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(9u, microtasks_completed_callback_count);
|
|
|
|
env->GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kAuto);
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(3, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(12u, microtasks_completed_callback_count);
|
|
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
|
|
{
|
|
v8::Isolate::SuppressMicrotaskExecutionScope suppress(env->GetIsolate());
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(3, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(12u, microtasks_completed_callback_count);
|
|
}
|
|
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(4, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(15u, microtasks_completed_callback_count);
|
|
|
|
env->GetIsolate()->RemoveMicrotasksCompletedCallback(
|
|
&MicrotasksCompletedCallback);
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(3, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(4, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(15u, microtasks_completed_callback_count);
|
|
}
|
|
|
|
|
|
TEST(RunMicrotasksWithoutEnteringContext) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
HandleScope handle_scope(isolate);
|
|
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
|
|
Local<Context> context = Context::New(isolate);
|
|
{
|
|
Context::Scope context_scope(context);
|
|
CompileRun("var ext1Calls = 0;");
|
|
isolate->EnqueueMicrotask(
|
|
Function::New(context, MicrotaskOne).ToLocalChecked());
|
|
}
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
{
|
|
Context::Scope context_scope(context);
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(context).FromJust());
|
|
}
|
|
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kAuto);
|
|
}
|
|
|
|
static void Regress808911_MicrotaskCallback(void* data) {
|
|
// So here we expect "current context" to be context1 and
|
|
// "entered or microtask context" to be context2.
|
|
v8::Isolate* isolate = static_cast<v8::Isolate*>(data);
|
|
CHECK(isolate->GetCurrentContext() !=
|
|
isolate->GetEnteredOrMicrotaskContext());
|
|
}
|
|
|
|
static void Regress808911_CurrentContextWrapper(
|
|
const v8::FunctionCallbackInfo<Value>& info) {
|
|
// So here we expect "current context" to be context1 and
|
|
// "entered or microtask context" to be context2.
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
CHECK(isolate->GetCurrentContext() !=
|
|
isolate->GetEnteredOrMicrotaskContext());
|
|
isolate->EnqueueMicrotask(Regress808911_MicrotaskCallback, isolate);
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
}
|
|
|
|
THREADED_TEST(Regress808911) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
HandleScope handle_scope(isolate);
|
|
Local<Context> context1 = Context::New(isolate);
|
|
Local<Function> function;
|
|
{
|
|
Context::Scope context_scope(context1);
|
|
function = Function::New(context1, Regress808911_CurrentContextWrapper)
|
|
.ToLocalChecked();
|
|
}
|
|
Local<Context> context2 = Context::New(isolate);
|
|
Context::Scope context_scope(context2);
|
|
function->CallAsFunction(context2, v8::Undefined(isolate), 0, nullptr)
|
|
.ToLocalChecked();
|
|
}
|
|
|
|
TEST(ScopedMicrotasks) {
|
|
LocalContext env;
|
|
v8::HandleScope handles(env->GetIsolate());
|
|
env->GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kScoped);
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
CompileRun("var ext1Calls = 0;");
|
|
}
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
ExpectInt32("ext1Calls", 1);
|
|
}
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
CompileRun("throw new Error()");
|
|
}
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
ExpectInt32("ext1Calls", 2);
|
|
}
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
v8::TryCatch try_catch(env->GetIsolate());
|
|
CompileRun("throw new Error()");
|
|
}
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
ExpectInt32("ext1Calls", 3);
|
|
}
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
env->GetIsolate()->TerminateExecution();
|
|
{
|
|
v8::MicrotasksScope scope2(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
}
|
|
}
|
|
env->GetIsolate()->CancelTerminateExecution();
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
ExpectInt32("ext1Calls", 3);
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
}
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
|
|
ExpectInt32("ext1Calls", 4);
|
|
}
|
|
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
CompileRun(
|
|
"var ext1Calls = 0;"
|
|
"var ext2Calls = 0;");
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(0, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
{
|
|
v8::MicrotasksScope scope2(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(0, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
{
|
|
v8::MicrotasksScope scope3(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(0,
|
|
CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0,
|
|
CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
}
|
|
CHECK_EQ(0, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
}
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
|
|
}
|
|
|
|
{
|
|
v8::MicrotasksScope scope(env->GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
CompileRun("1+1;");
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
{
|
|
v8::MicrotasksScope scope2(env->GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
}
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
v8::MicrotasksScope scope(env->GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(1, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
|
|
}
|
|
|
|
{
|
|
v8::Isolate::SuppressMicrotaskExecutionScope scope1(env->GetIsolate());
|
|
{
|
|
v8::MicrotasksScope scope2(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
}
|
|
v8::MicrotasksScope scope3(env->GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(1, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
v8::MicrotasksScope scope1(env->GetIsolate(),
|
|
v8::MicrotasksScope::kRunMicrotasks);
|
|
v8::MicrotasksScope::PerformCheckpoint(env->GetIsolate());
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(1, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
v8::MicrotasksScope scope(env->GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(2, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
v8::MicrotasksScope::PerformCheckpoint(env->GetIsolate());
|
|
|
|
{
|
|
v8::MicrotasksScope scope(env->GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(2, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
|
|
}
|
|
|
|
v8::MicrotasksScope::PerformCheckpoint(env->GetIsolate());
|
|
|
|
{
|
|
v8::MicrotasksScope scope(env->GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(3, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
env->GetIsolate()->EnqueueMicrotask(
|
|
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
|
|
{
|
|
v8::Isolate::SuppressMicrotaskExecutionScope scope1(env->GetIsolate());
|
|
v8::MicrotasksScope::PerformCheckpoint(env->GetIsolate());
|
|
v8::MicrotasksScope scope2(env->GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(3, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
v8::MicrotasksScope::PerformCheckpoint(env->GetIsolate());
|
|
|
|
{
|
|
v8::MicrotasksScope scope(env->GetIsolate(),
|
|
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
|
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(3, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
env->GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kAuto);
|
|
}
|
|
|
|
namespace {
|
|
|
|
void AssertCowElements(bool expected, const char* source) {
|
|
Local<Value> object = CompileRun(source);
|
|
i::Handle<i::JSObject> array =
|
|
i::Handle<i::JSObject>::cast(v8::Utils::OpenHandle(*object.As<Object>()));
|
|
CHECK_EQ(expected, array->elements().IsCowArray());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(CheckCOWArraysCreatedRuntimeCounter) {
|
|
LocalContext env;
|
|
v8::HandleScope scope(env->GetIsolate());
|
|
AssertCowElements(true, "[1, 2, 3]");
|
|
AssertCowElements(false, "[[1], 2, 3]");
|
|
AssertCowElements(true, "[[1], 2, 3][0]");
|
|
AssertCowElements(true, "({foo: [4, 5, 6], bar: [3, 0]}.foo)");
|
|
AssertCowElements(true, "({foo: [4, 5, 6], bar: [3, 0]}.bar)");
|
|
AssertCowElements(false, "({foo: [1, 2, 3, [4, 5, 6]], bar: 'hi'}.foo)");
|
|
AssertCowElements(true, "({foo: [1, 2, 3, [4, 5, 6]], bar: 'hi'}.foo[3])");
|
|
}
|
|
|
|
|
|
TEST(StaticGetters) {
|
|
LocalContext context;
|
|
i::Factory* factory = CcTest::i_isolate()->factory();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
i::Handle<i::Object> undefined_value = factory->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(isolate)) == *null_value);
|
|
i::Handle<i::Object> true_value = factory->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(isolate)) == *false_value);
|
|
}
|
|
|
|
UNINITIALIZED_TEST(IsolateEmbedderData) {
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
isolate->Enter();
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
for (uint32_t slot = 0; slot < v8::Isolate::GetNumberOfDataSlots(); ++slot) {
|
|
CHECK(!isolate->GetData(slot));
|
|
CHECK(!i_isolate->GetData(slot));
|
|
}
|
|
for (uint32_t slot = 0; slot < v8::Isolate::GetNumberOfDataSlots(); ++slot) {
|
|
void* data = reinterpret_cast<void*>(0xACCE55ED + slot);
|
|
isolate->SetData(slot, data);
|
|
}
|
|
for (uint32_t slot = 0; slot < v8::Isolate::GetNumberOfDataSlots(); ++slot) {
|
|
void* data = reinterpret_cast<void*>(0xACCE55ED + slot);
|
|
CHECK_EQ(data, isolate->GetData(slot));
|
|
CHECK_EQ(data, i_isolate->GetData(slot));
|
|
}
|
|
for (uint32_t slot = 0; slot < v8::Isolate::GetNumberOfDataSlots(); ++slot) {
|
|
void* data = reinterpret_cast<void*>(0xDECEA5ED + slot);
|
|
isolate->SetData(slot, data);
|
|
}
|
|
for (uint32_t slot = 0; slot < v8::Isolate::GetNumberOfDataSlots(); ++slot) {
|
|
void* data = reinterpret_cast<void*>(0xDECEA5ED + slot);
|
|
CHECK_EQ(data, isolate->GetData(slot));
|
|
CHECK_EQ(data, i_isolate->GetData(slot));
|
|
}
|
|
isolate->Exit();
|
|
isolate->Dispose();
|
|
}
|
|
|
|
TEST(StringEmpty) {
|
|
LocalContext context;
|
|
i::Factory* factory = CcTest::i_isolate()->factory();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
i::Handle<i::Object> empty_string = factory->empty_string();
|
|
CHECK(*v8::Utils::OpenHandle(*v8::String::Empty(isolate)) == *empty_string);
|
|
}
|
|
|
|
|
|
static int instance_checked_getter_count = 0;
|
|
static void InstanceCheckedGetter(
|
|
Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(name->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("foo"))
|
|
.FromJust());
|
|
instance_checked_getter_count++;
|
|
info.GetReturnValue().Set(v8_num(11));
|
|
}
|
|
|
|
|
|
static int instance_checked_setter_count = 0;
|
|
static void InstanceCheckedSetter(Local<String> name,
|
|
Local<Value> value,
|
|
const v8::PropertyCallbackInfo<void>& info) {
|
|
CHECK(name->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("foo"))
|
|
.FromJust());
|
|
CHECK(value->Equals(info.GetIsolate()->GetCurrentContext(), v8_num(23))
|
|
.FromJust());
|
|
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(CcTest::isolate());
|
|
|
|
// 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; };"
|
|
"%PrepareFunctionForOptimization(test_get);"
|
|
"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; }"
|
|
"%PrepareFunctionForOptimization(test_set);"
|
|
"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);"
|
|
"%ClearFunctionFeedback(test_get);"
|
|
"%DeoptimizeFunction(test_set);"
|
|
"%ClearFunctionFeedback(test_set);");
|
|
}
|
|
|
|
|
|
THREADED_TEST(InstanceCheckOnInstanceAccessor) {
|
|
v8::internal::FLAG_allow_natives_syntax = true;
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
Local<FunctionTemplate> templ = FunctionTemplate::New(context->GetIsolate());
|
|
Local<ObjectTemplate> inst = templ->InstanceTemplate();
|
|
inst->SetAccessor(v8_str("foo"), InstanceCheckedGetter, InstanceCheckedSetter,
|
|
Local<Value>(), v8::DEFAULT, v8::None,
|
|
v8::AccessorSignature::New(context->GetIsolate(), templ));
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f"),
|
|
templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
printf("Testing positive ...\n");
|
|
CompileRun("var obj = new f();");
|
|
CHECK(templ->HasInstance(
|
|
context->Global()->Get(context.local(), v8_str("obj")).ToLocalChecked()));
|
|
CheckInstanceCheckedAccessors(true);
|
|
|
|
printf("Testing negative ...\n");
|
|
CompileRun("var obj = {};"
|
|
"obj.__proto__ = new f();");
|
|
CHECK(!templ->HasInstance(
|
|
context->Global()->Get(context.local(), v8_str("obj")).ToLocalChecked()));
|
|
CheckInstanceCheckedAccessors(false);
|
|
}
|
|
|
|
static void EmptyInterceptorGetter(
|
|
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {}
|
|
|
|
static void EmptyInterceptorSetter(
|
|
Local<Name> name, Local<Value> value,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {}
|
|
|
|
THREADED_TEST(InstanceCheckOnInstanceAccessorWithInterceptor) {
|
|
v8::internal::FLAG_allow_natives_syntax = true;
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
Local<FunctionTemplate> templ = FunctionTemplate::New(context->GetIsolate());
|
|
Local<ObjectTemplate> inst = templ->InstanceTemplate();
|
|
templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
|
EmptyInterceptorGetter, EmptyInterceptorSetter));
|
|
inst->SetAccessor(v8_str("foo"), InstanceCheckedGetter, InstanceCheckedSetter,
|
|
Local<Value>(), v8::DEFAULT, v8::None,
|
|
v8::AccessorSignature::New(context->GetIsolate(), templ));
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f"),
|
|
templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
printf("Testing positive ...\n");
|
|
CompileRun("var obj = new f();");
|
|
CHECK(templ->HasInstance(
|
|
context->Global()->Get(context.local(), v8_str("obj")).ToLocalChecked()));
|
|
CheckInstanceCheckedAccessors(true);
|
|
|
|
printf("Testing negative ...\n");
|
|
CompileRun("var obj = {};"
|
|
"obj.__proto__ = new f();");
|
|
CHECK(!templ->HasInstance(
|
|
context->Global()->Get(context.local(), v8_str("obj")).ToLocalChecked()));
|
|
CheckInstanceCheckedAccessors(false);
|
|
}
|
|
|
|
|
|
THREADED_TEST(InstanceCheckOnPrototypeAccessor) {
|
|
v8::internal::FLAG_allow_natives_syntax = true;
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
Local<FunctionTemplate> templ = FunctionTemplate::New(context->GetIsolate());
|
|
Local<ObjectTemplate> proto = templ->PrototypeTemplate();
|
|
proto->SetAccessor(v8_str("foo"), InstanceCheckedGetter,
|
|
InstanceCheckedSetter, Local<Value>(), v8::DEFAULT,
|
|
v8::None,
|
|
v8::AccessorSignature::New(context->GetIsolate(), templ));
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f"),
|
|
templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
printf("Testing positive ...\n");
|
|
CompileRun("var obj = new f();");
|
|
CHECK(templ->HasInstance(
|
|
context->Global()->Get(context.local(), v8_str("obj")).ToLocalChecked()));
|
|
CheckInstanceCheckedAccessors(true);
|
|
|
|
printf("Testing negative ...\n");
|
|
CompileRun("var obj = {};"
|
|
"obj.__proto__ = new f();");
|
|
CHECK(!templ->HasInstance(
|
|
context->Global()->Get(context.local(), v8_str("obj")).ToLocalChecked()));
|
|
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(context.local(), v8_str("obj")).ToLocalChecked()));
|
|
CheckInstanceCheckedAccessors(true);
|
|
}
|
|
|
|
THREADED_TEST(CheckIsLeafTemplateForApiObject) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
Local<FunctionTemplate> templ = FunctionTemplate::New(context->GetIsolate());
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("f"),
|
|
templ->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
printf("Testing positive ...\n");
|
|
CompileRun("var obj = new f();");
|
|
CHECK(templ->IsLeafTemplateForApiObject(
|
|
context->Global()->Get(context.local(), v8_str("obj")).ToLocalChecked()));
|
|
|
|
printf("Testing negative ...\n");
|
|
CompileRun(
|
|
"var obj = {};"
|
|
"obj.__proto__ = new f();");
|
|
CHECK(!templ->IsLeafTemplateForApiObject(
|
|
context->Global()->Get(context.local(), v8_str("obj")).ToLocalChecked()));
|
|
|
|
printf("Testing positive with modified prototype chain ...\n");
|
|
CompileRun(
|
|
"var obj = new f();"
|
|
"var pro = {};"
|
|
"pro.__proto__ = obj.__proto__;"
|
|
"obj.__proto__ = pro;");
|
|
CHECK(templ->IsLeafTemplateForApiObject(
|
|
context->Global()->Get(context.local(), v8_str("obj")).ToLocalChecked()));
|
|
|
|
Local<FunctionTemplate> child_templ =
|
|
FunctionTemplate::New(context->GetIsolate());
|
|
child_templ->Inherit(templ);
|
|
Local<Object> instance = child_templ->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
|
|
printf("Testing positive for child ...\n");
|
|
CHECK(child_templ->IsLeafTemplateForApiObject(instance));
|
|
printf("Testing negative for parent ...\n");
|
|
CHECK(!templ->IsLeafTemplateForApiObject(instance));
|
|
}
|
|
|
|
TEST(TryFinallyMessage) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
{
|
|
// 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(context->GetIsolate());
|
|
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(context.local()).FromJust());
|
|
}
|
|
|
|
{
|
|
// Test that the original exception message is indeed overwritten if
|
|
// a new error is thrown in the finally block.
|
|
TryCatch try_catch(context->GetIsolate());
|
|
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(context.local()).FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
static void Helper137002(bool do_store,
|
|
bool polymorphic,
|
|
bool remove_accessor,
|
|
bool interceptor) {
|
|
LocalContext context;
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(context->GetIsolate());
|
|
if (interceptor) {
|
|
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(FooGetInterceptor,
|
|
FooSetInterceptor));
|
|
} else {
|
|
templ->SetAccessor(v8_str("foo"),
|
|
GetterWhichReturns42,
|
|
SetterWhichSetsYOnThisTo23);
|
|
}
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// 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("%PrepareFunctionForOptimization(f);");
|
|
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(context.local(), v8_str("result"))
|
|
.ToLocalChecked()
|
|
->IsUndefined());
|
|
} else {
|
|
CHECK_EQ(do_store ? 23 : 42, context->Global()
|
|
->Get(context.local(), v8_str("result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
THREADED_TEST(Regress137002a) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
i::FLAG_compilation_cache = false;
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
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;
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("foo"),
|
|
GetterWhichReturns42,
|
|
SetterWhichSetsYOnThisTo23);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// 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(context.local(), v8_str("load_result"))
|
|
.ToLocalChecked()
|
|
->IsUndefined());
|
|
CHECK(context->Global()
|
|
->Get(context.local(), v8_str("load_result2"))
|
|
.ToLocalChecked()
|
|
->IsUndefined());
|
|
CHECK(context->Global()
|
|
->Get(context.local(), v8_str("keyed_load_result"))
|
|
.ToLocalChecked()
|
|
->IsUndefined());
|
|
CHECK(context->Global()
|
|
->Get(context.local(), v8_str("keyed_load_result2"))
|
|
.ToLocalChecked()
|
|
->IsUndefined());
|
|
CHECK(context->Global()
|
|
->Get(context.local(), v8_str("y_from_obj"))
|
|
.ToLocalChecked()
|
|
->IsUndefined());
|
|
CHECK(context->Global()
|
|
->Get(context.local(), v8_str("y_from_subobj"))
|
|
.ToLocalChecked()
|
|
->IsUndefined());
|
|
}
|
|
|
|
|
|
THREADED_TEST(Regress142088) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("foo"),
|
|
GetterWhichReturns42,
|
|
SetterWhichSetsYOnThisTo23);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("obj"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
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;
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
// 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(context->GetIsolate());
|
|
try_catch.SetVerbose(true);
|
|
CompileRun("try { throw new Error(); } finally { gc(); }");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
|
|
THREADED_TEST(Regress157124) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
Local<Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
|
|
obj->GetIdentityHash();
|
|
obj->DeletePrivate(context.local(),
|
|
v8::Private::ForApi(isolate, v8_str("Bug")))
|
|
.FromJust();
|
|
}
|
|
|
|
|
|
THREADED_TEST(Regress2535) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
Local<Value> set_value = CompileRun("new Set();");
|
|
Local<Object> set_object(Local<Object>::Cast(set_value));
|
|
CHECK_EQ(0, set_object->InternalFieldCount());
|
|
Local<Value> map_value = CompileRun("new Map();");
|
|
Local<Object> map_object(Local<Object>::Cast(map_value));
|
|
CHECK_EQ(0, map_object->InternalFieldCount());
|
|
}
|
|
|
|
|
|
THREADED_TEST(Regress2746) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<Object> obj = Object::New(isolate);
|
|
Local<v8::Private> key = v8::Private::New(isolate, v8_str("key"));
|
|
CHECK(
|
|
obj->SetPrivate(context.local(), key, v8::Undefined(isolate)).FromJust());
|
|
Local<Value> value = obj->GetPrivate(context.local(), key).ToLocalChecked();
|
|
CHECK(!value.IsEmpty());
|
|
CHECK(value->IsUndefined());
|
|
}
|
|
|
|
|
|
THREADED_TEST(Regress260106) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<FunctionTemplate> templ = FunctionTemplate::New(isolate,
|
|
DummyCallHandler);
|
|
CompileRun("for (var i = 0; i < 128; i++) Object.prototype[i] = 0;");
|
|
Local<Function> function =
|
|
templ->GetFunction(context.local()).ToLocalChecked();
|
|
CHECK(!function.IsEmpty());
|
|
CHECK(function->IsFunction());
|
|
}
|
|
|
|
THREADED_TEST(JSONParseObject) {
|
|
LocalContext context;
|
|
HandleScope scope(context->GetIsolate());
|
|
Local<Value> obj =
|
|
v8::JSON::Parse(context.local(), v8_str("{\"x\":42}")).ToLocalChecked();
|
|
Local<Object> global = context->Global();
|
|
global->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
ExpectString("JSON.stringify(obj)", "{\"x\":42}");
|
|
}
|
|
|
|
THREADED_TEST(JSONParseNumber) {
|
|
LocalContext context;
|
|
HandleScope scope(context->GetIsolate());
|
|
Local<Value> obj =
|
|
v8::JSON::Parse(context.local(), v8_str("42")).ToLocalChecked();
|
|
Local<Object> global = context->Global();
|
|
global->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
ExpectString("JSON.stringify(obj)", "42");
|
|
}
|
|
|
|
namespace {
|
|
void TestJSONParseArray(Local<Context> context, const char* input_str,
|
|
const char* expected_output_str,
|
|
i::ElementsKind expected_elements_kind) {
|
|
Local<Value> obj =
|
|
v8::JSON::Parse(context, v8_str(input_str)).ToLocalChecked();
|
|
|
|
i::Handle<i::JSArray> a =
|
|
i::Handle<i::JSArray>::cast(v8::Utils::OpenHandle(*obj));
|
|
CHECK_EQ(expected_elements_kind, a->GetElementsKind());
|
|
|
|
Local<Object> global = context->Global();
|
|
global->Set(context, v8_str("obj"), obj).FromJust();
|
|
ExpectString("JSON.stringify(obj)", expected_output_str);
|
|
}
|
|
} // namespace
|
|
|
|
THREADED_TEST(JSONParseArray) {
|
|
LocalContext context;
|
|
HandleScope scope(context->GetIsolate());
|
|
|
|
TestJSONParseArray(context.local(), "[0, 1, 2]", "[0,1,2]",
|
|
i::PACKED_SMI_ELEMENTS);
|
|
TestJSONParseArray(context.local(), "[0, 1.2, 2]", "[0,1.2,2]",
|
|
i::PACKED_DOUBLE_ELEMENTS);
|
|
TestJSONParseArray(context.local(), "[0.2, 1, 2]", "[0.2,1,2]",
|
|
i::PACKED_DOUBLE_ELEMENTS);
|
|
TestJSONParseArray(context.local(), "[0, \"a\", 2]", "[0,\"a\",2]",
|
|
i::PACKED_ELEMENTS);
|
|
TestJSONParseArray(context.local(), "[\"a\", 1, 2]", "[\"a\",1,2]",
|
|
i::PACKED_ELEMENTS);
|
|
TestJSONParseArray(context.local(), "[\"a\", 1.2, 2]", "[\"a\",1.2,2]",
|
|
i::PACKED_ELEMENTS);
|
|
TestJSONParseArray(context.local(), "[0, 1.2, \"a\"]", "[0,1.2,\"a\"]",
|
|
i::PACKED_ELEMENTS);
|
|
}
|
|
|
|
THREADED_TEST(JSONStringifyObject) {
|
|
LocalContext context;
|
|
HandleScope scope(context->GetIsolate());
|
|
Local<Value> value =
|
|
v8::JSON::Parse(context.local(), v8_str("{\"x\":42}")).ToLocalChecked();
|
|
Local<Object> obj = value->ToObject(context.local()).ToLocalChecked();
|
|
Local<Object> global = context->Global();
|
|
global->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
Local<String> json =
|
|
v8::JSON::Stringify(context.local(), obj).ToLocalChecked();
|
|
v8::String::Utf8Value utf8(context->GetIsolate(), json);
|
|
ExpectString("JSON.stringify(obj)", *utf8);
|
|
}
|
|
|
|
THREADED_TEST(JSONStringifyObjectWithGap) {
|
|
LocalContext context;
|
|
HandleScope scope(context->GetIsolate());
|
|
Local<Value> value =
|
|
v8::JSON::Parse(context.local(), v8_str("{\"x\":42}")).ToLocalChecked();
|
|
Local<Object> obj = value->ToObject(context.local()).ToLocalChecked();
|
|
Local<Object> global = context->Global();
|
|
global->Set(context.local(), v8_str("obj"), obj).FromJust();
|
|
Local<String> json =
|
|
v8::JSON::Stringify(context.local(), obj, v8_str("*")).ToLocalChecked();
|
|
v8::String::Utf8Value utf8(context->GetIsolate(), json);
|
|
ExpectString("JSON.stringify(obj, null, '*')", *utf8);
|
|
}
|
|
|
|
#if V8_OS_POSIX
|
|
class ThreadInterruptTest {
|
|
public:
|
|
ThreadInterruptTest() : sem_(0), sem_value_(0) { }
|
|
~ThreadInterruptTest() = default;
|
|
|
|
void RunTest() {
|
|
InterruptThread i_thread(this);
|
|
CHECK(i_thread.Start());
|
|
|
|
sem_.Wait();
|
|
CHECK_EQ(kExpectedValue, sem_value_);
|
|
}
|
|
|
|
private:
|
|
static const int kExpectedValue = 1;
|
|
|
|
class InterruptThread : public v8::base::Thread {
|
|
public:
|
|
explicit InterruptThread(ThreadInterruptTest* test)
|
|
: Thread(Options("InterruptThread")), test_(test) {}
|
|
|
|
void Run() override {
|
|
struct sigaction action;
|
|
|
|
// Ensure that we'll enter waiting condition
|
|
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(100));
|
|
|
|
// Setup signal handler
|
|
memset(&action, 0, sizeof(action));
|
|
action.sa_handler = SignalHandler;
|
|
sigaction(SIGCHLD, &action, nullptr);
|
|
|
|
// Send signal
|
|
kill(getpid(), SIGCHLD);
|
|
|
|
// Ensure that if wait has returned because of error
|
|
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(100));
|
|
|
|
// Set value and signal semaphore
|
|
test_->sem_value_ = 1;
|
|
test_->sem_.Signal();
|
|
}
|
|
|
|
static void SignalHandler(int signal) {
|
|
}
|
|
|
|
private:
|
|
ThreadInterruptTest* test_;
|
|
};
|
|
|
|
v8::base::Semaphore sem_;
|
|
volatile int sem_value_;
|
|
};
|
|
|
|
|
|
THREADED_TEST(SemaphoreInterruption) {
|
|
ThreadInterruptTest().RunTest();
|
|
}
|
|
|
|
|
|
#endif // V8_OS_POSIX
|
|
|
|
|
|
void UnreachableCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
UNREACHABLE();
|
|
}
|
|
|
|
TEST(JSONStringifyAccessCheck) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Create an ObjectTemplate for global objects and install access
|
|
// check callbacks that will block access.
|
|
v8::Local<v8::ObjectTemplate> global_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
global_template->SetAccessCheckCallback(AccessAlwaysBlocked);
|
|
|
|
// Create a context and set an x property on it's global object.
|
|
LocalContext context0(nullptr, global_template);
|
|
v8::Local<v8::Object> global0 = context0->Global();
|
|
global0->Set(context0.local(), v8_str("x"), v8_num(42)).FromJust();
|
|
ExpectString("JSON.stringify(this)", "{\"x\":42}");
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
if (i == 1) {
|
|
// Install a toJSON function on the second run.
|
|
v8::Local<v8::FunctionTemplate> toJSON =
|
|
v8::FunctionTemplate::New(isolate, UnreachableCallback);
|
|
|
|
global0->Set(context0.local(), v8_str("toJSON"),
|
|
toJSON->GetFunction(context0.local()).ToLocalChecked())
|
|
.FromJust();
|
|
}
|
|
// Create a context with a different security token so that the
|
|
// failed access check callback will be called on each access.
|
|
LocalContext context1(nullptr, global_template);
|
|
CHECK(context1->Global()
|
|
->Set(context1.local(), v8_str("other"), global0)
|
|
.FromJust());
|
|
|
|
CHECK(CompileRun("JSON.stringify(other)").IsEmpty());
|
|
CHECK(CompileRun("JSON.stringify({ 'a' : other, 'b' : ['c'] })").IsEmpty());
|
|
CHECK(CompileRun("JSON.stringify([other, 'b', 'c'])").IsEmpty());
|
|
}
|
|
}
|
|
|
|
bool access_check_fail_thrown = false;
|
|
bool catch_callback_called = false;
|
|
|
|
|
|
// Failed access check callback that performs a GC on each invocation.
|
|
void FailedAccessCheckThrows(Local<v8::Object> target,
|
|
v8::AccessType type,
|
|
Local<v8::Value> data) {
|
|
access_check_fail_thrown = true;
|
|
i::PrintF("Access check failed. Error thrown.\n");
|
|
CcTest::isolate()->ThrowException(
|
|
v8::Exception::Error(v8_str("cross context")));
|
|
}
|
|
|
|
|
|
void CatcherCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
for (int i = 0; i < args.Length(); i++) {
|
|
i::PrintF("%s\n", *String::Utf8Value(args.GetIsolate(), args[i]));
|
|
}
|
|
catch_callback_called = true;
|
|
}
|
|
|
|
|
|
void HasOwnPropertyCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
|
|
CHECK(
|
|
args[0]
|
|
->ToObject(context)
|
|
.ToLocalChecked()
|
|
->HasOwnProperty(context, args[1]->ToString(context).ToLocalChecked())
|
|
.IsNothing());
|
|
}
|
|
|
|
|
|
void CheckCorrectThrow(const char* script) {
|
|
// Test that the script, when wrapped into a try-catch, triggers the catch
|
|
// clause due to failed access check throwing an exception.
|
|
// The subsequent try-catch should run without any exception.
|
|
access_check_fail_thrown = false;
|
|
catch_callback_called = false;
|
|
v8::base::ScopedVector<char> source(1024);
|
|
v8::base::SNPrintF(source, "try { %s; } catch (e) { catcher(e); }", script);
|
|
CompileRun(source.begin());
|
|
CHECK(access_check_fail_thrown);
|
|
CHECK(catch_callback_called);
|
|
|
|
access_check_fail_thrown = false;
|
|
catch_callback_called = false;
|
|
CompileRun("try { [1, 2, 3].sort(); } catch (e) { catcher(e) };");
|
|
CHECK(!access_check_fail_thrown);
|
|
CHECK(!catch_callback_called);
|
|
}
|
|
|
|
|
|
TEST(AccessCheckThrows) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
isolate->SetFailedAccessCheckCallbackFunction(&FailedAccessCheckThrows);
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Create an ObjectTemplate for global objects and install access
|
|
// check callbacks that will block access.
|
|
v8::Local<v8::ObjectTemplate> global_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
global_template->SetAccessCheckCallback(AccessAlwaysBlocked);
|
|
|
|
// Create a context and set an x property on it's global object.
|
|
LocalContext context0(nullptr, global_template);
|
|
v8::Local<v8::Object> global0 = context0->Global();
|
|
CHECK(global0->Set(context0.local(), v8_str("x"), global0).FromJust());
|
|
|
|
// Create a context with a different security token so that the
|
|
// failed access check callback will be called on each access.
|
|
LocalContext context1(nullptr, global_template);
|
|
CHECK(context1->Global()
|
|
->Set(context1.local(), v8_str("other"), global0)
|
|
.FromJust());
|
|
|
|
v8::Local<v8::FunctionTemplate> catcher_fun =
|
|
v8::FunctionTemplate::New(isolate, CatcherCallback);
|
|
CHECK(context1->Global()
|
|
->Set(context1.local(), v8_str("catcher"),
|
|
catcher_fun->GetFunction(context1.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
v8::Local<v8::FunctionTemplate> has_own_property_fun =
|
|
v8::FunctionTemplate::New(isolate, HasOwnPropertyCallback);
|
|
CHECK(context1->Global()
|
|
->Set(context1.local(), v8_str("has_own_property"),
|
|
has_own_property_fun->GetFunction(context1.local())
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
access_check_fail_thrown = false;
|
|
CompileRun("other.x;");
|
|
CHECK(access_check_fail_thrown);
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
CheckCorrectThrow("other.x");
|
|
CheckCorrectThrow("other[1]");
|
|
CheckCorrectThrow("JSON.stringify(other)");
|
|
CheckCorrectThrow("has_own_property(other, 'x')");
|
|
CheckCorrectThrow("%GetProperty(other, 'x')");
|
|
CheckCorrectThrow("%SetKeyedProperty(other, 'x', 'foo')");
|
|
CheckCorrectThrow("%SetNamedProperty(other, 'y', 'foo')");
|
|
STATIC_ASSERT(static_cast<int>(i::LanguageMode::kSloppy) == 0);
|
|
STATIC_ASSERT(static_cast<int>(i::LanguageMode::kStrict) == 1);
|
|
CheckCorrectThrow("%DeleteProperty(other, 'x', 0)"); // 0 == SLOPPY
|
|
CheckCorrectThrow("%DeleteProperty(other, 'x', 1)"); // 1 == STRICT
|
|
CheckCorrectThrow("%DeleteProperty(other, '1', 0)");
|
|
CheckCorrectThrow("%DeleteProperty(other, '1', 1)");
|
|
CheckCorrectThrow("Object.prototype.hasOwnProperty.call(other, 'x')");
|
|
CheckCorrectThrow("%HasProperty(other, 'x')");
|
|
CheckCorrectThrow("Object.prototype.propertyIsEnumerable(other, 'x')");
|
|
// PROPERTY_ATTRIBUTES_NONE = 0
|
|
CheckCorrectThrow("%DefineAccessorPropertyUnchecked("
|
|
"other, 'x', null, null, 1)");
|
|
|
|
// Reset the failed access check callback so it does not influence
|
|
// the other tests.
|
|
isolate->SetFailedAccessCheckCallbackFunction(nullptr);
|
|
}
|
|
|
|
namespace {
|
|
|
|
const char kOneByteSubjectString[] = {
|
|
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
|
|
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
|
|
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', '\0'};
|
|
const uint16_t kTwoByteSubjectString[] = {
|
|
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
|
|
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
|
|
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', '\0'};
|
|
|
|
const int kSubjectStringLength = arraysize(kOneByteSubjectString) - 1;
|
|
STATIC_ASSERT(arraysize(kOneByteSubjectString) ==
|
|
arraysize(kTwoByteSubjectString));
|
|
|
|
OneByteVectorResource one_byte_string_resource(v8::base::Vector<const char>(
|
|
&kOneByteSubjectString[0], kSubjectStringLength));
|
|
UC16VectorResource two_byte_string_resource(
|
|
v8::base::Vector<const v8::base::uc16>(&kTwoByteSubjectString[0],
|
|
kSubjectStringLength));
|
|
|
|
class RegExpInterruptTest {
|
|
public:
|
|
RegExpInterruptTest()
|
|
: i_thread(this),
|
|
env_(),
|
|
isolate_(env_->GetIsolate()),
|
|
sem_(0),
|
|
ran_test_body_(false),
|
|
ran_to_completion_(false) {}
|
|
|
|
void RunTest(v8::InterruptCallback test_body_fn) {
|
|
v8::HandleScope handle_scope(isolate_);
|
|
|
|
i_thread.SetTestBody(test_body_fn);
|
|
CHECK(i_thread.Start());
|
|
|
|
TestBody();
|
|
|
|
i_thread.Join();
|
|
}
|
|
|
|
static void CollectAllGarbage(v8::Isolate* isolate, void* data) {
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
i_isolate->heap()->PreciseCollectAllGarbage(
|
|
i::Heap::kNoGCFlags, i::GarbageCollectionReason::kRuntime);
|
|
}
|
|
|
|
static void MakeSubjectOneByteExternal(v8::Isolate* isolate, void* data) {
|
|
auto instance = reinterpret_cast<RegExpInterruptTest*>(data);
|
|
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::String> string =
|
|
v8::Local<v8::String>::New(isolate, instance->string_handle_);
|
|
CHECK(string->CanMakeExternal());
|
|
string->MakeExternal(&one_byte_string_resource);
|
|
}
|
|
|
|
static void MakeSubjectTwoByteExternal(v8::Isolate* isolate, void* data) {
|
|
auto instance = reinterpret_cast<RegExpInterruptTest*>(data);
|
|
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::String> string =
|
|
v8::Local<v8::String>::New(isolate, instance->string_handle_);
|
|
CHECK(string->CanMakeExternal());
|
|
string->MakeExternal(&two_byte_string_resource);
|
|
}
|
|
|
|
private:
|
|
static void SignalSemaphore(v8::Isolate* isolate, void* data) {
|
|
reinterpret_cast<RegExpInterruptTest*>(data)->sem_.Signal();
|
|
}
|
|
|
|
void CreateTestStrings() {
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate_);
|
|
|
|
// The string must be in old space to support externalization.
|
|
i::Handle<i::String> i_string =
|
|
i_isolate->factory()->NewStringFromAsciiChecked(
|
|
&kOneByteSubjectString[0], i::AllocationType::kOld);
|
|
v8::Local<v8::String> string = v8::Utils::ToLocal(i_string);
|
|
|
|
env_->Global()->Set(env_.local(), v8_str("a"), string).FromJust();
|
|
|
|
string_handle_.Reset(env_->GetIsolate(), string);
|
|
}
|
|
|
|
void TestBody() {
|
|
CHECK(!ran_test_body_.load());
|
|
CHECK(!ran_to_completion_.load());
|
|
|
|
CreateTestStrings();
|
|
|
|
v8::TryCatch try_catch(env_->GetIsolate());
|
|
|
|
isolate_->RequestInterrupt(&SignalSemaphore, this);
|
|
CompileRun("/((a*)*)*b/.exec(a)");
|
|
|
|
CHECK(try_catch.HasTerminated());
|
|
CHECK(ran_test_body_.load());
|
|
CHECK(ran_to_completion_.load());
|
|
}
|
|
|
|
class InterruptThread : public v8::base::Thread {
|
|
public:
|
|
explicit InterruptThread(RegExpInterruptTest* test)
|
|
: Thread(Options("RegExpInterruptTest")), test_(test) {}
|
|
|
|
void Run() override {
|
|
CHECK_NOT_NULL(test_body_fn_);
|
|
|
|
// Wait for JS execution to start.
|
|
test_->sem_.Wait();
|
|
|
|
// Sleep for a bit to allow irregexp execution to start up, then run the
|
|
// test body.
|
|
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(50));
|
|
test_->isolate_->RequestInterrupt(&RunTestBody, test_);
|
|
test_->isolate_->RequestInterrupt(&SignalSemaphore, test_);
|
|
|
|
// Wait for the scheduled interrupt to signal.
|
|
test_->sem_.Wait();
|
|
|
|
// Sleep again to resume irregexp execution, then terminate.
|
|
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(50));
|
|
test_->ran_to_completion_.store(true);
|
|
test_->isolate_->TerminateExecution();
|
|
}
|
|
|
|
static void RunTestBody(v8::Isolate* isolate, void* data) {
|
|
auto instance = reinterpret_cast<RegExpInterruptTest*>(data);
|
|
instance->i_thread.test_body_fn_(isolate, data);
|
|
instance->ran_test_body_.store(true);
|
|
}
|
|
|
|
void SetTestBody(v8::InterruptCallback callback) {
|
|
test_body_fn_ = callback;
|
|
}
|
|
|
|
private:
|
|
v8::InterruptCallback test_body_fn_;
|
|
RegExpInterruptTest* test_;
|
|
};
|
|
|
|
InterruptThread i_thread;
|
|
|
|
LocalContext env_;
|
|
v8::Isolate* isolate_;
|
|
v8::base::Semaphore sem_; // Coordinates between main and interrupt threads.
|
|
|
|
v8::Persistent<v8::String> string_handle_;
|
|
|
|
std::atomic<bool> ran_test_body_;
|
|
std::atomic<bool> ran_to_completion_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(RegExpInterruptAndCollectAllGarbage) {
|
|
// Move all movable objects on GC.
|
|
i::FLAG_compact_on_every_full_gc = true;
|
|
// We want to be stuck regexp execution, so no fallback to linear-time
|
|
// engine.
|
|
// TODO(mbid,v8:10765): Find a way to test interrupt support of the
|
|
// experimental engine.
|
|
i::FLAG_enable_experimental_regexp_engine_on_excessive_backtracks = false;
|
|
RegExpInterruptTest test;
|
|
test.RunTest(RegExpInterruptTest::CollectAllGarbage);
|
|
}
|
|
|
|
TEST(RegExpInterruptAndMakeSubjectOneByteExternal) {
|
|
// We want to be stuck regexp execution, so no fallback to linear-time
|
|
// engine.
|
|
// TODO(mbid,v8:10765): Find a way to test interrupt support of the
|
|
// experimental engine.
|
|
i::FLAG_enable_experimental_regexp_engine_on_excessive_backtracks = false;
|
|
RegExpInterruptTest test;
|
|
test.RunTest(RegExpInterruptTest::MakeSubjectOneByteExternal);
|
|
}
|
|
|
|
TEST(RegExpInterruptAndMakeSubjectTwoByteExternal) {
|
|
// We want to be stuck regexp execution, so no fallback to linear-time
|
|
// engine.
|
|
// TODO(mbid,v8:10765): Find a way to test interrupt support of the
|
|
// experimental engine.
|
|
i::FLAG_enable_experimental_regexp_engine_on_excessive_backtracks = false;
|
|
RegExpInterruptTest test;
|
|
test.RunTest(RegExpInterruptTest::MakeSubjectTwoByteExternal);
|
|
}
|
|
|
|
class RequestInterruptTestBase {
|
|
public:
|
|
RequestInterruptTestBase()
|
|
: env_(),
|
|
isolate_(env_->GetIsolate()),
|
|
sem_(0),
|
|
warmup_(20000),
|
|
should_continue_(true) {
|
|
}
|
|
|
|
virtual ~RequestInterruptTestBase() = default;
|
|
|
|
virtual void StartInterruptThread() = 0;
|
|
|
|
virtual void TestBody() = 0;
|
|
|
|
void RunTest() {
|
|
StartInterruptThread();
|
|
|
|
v8::HandleScope handle_scope(isolate_);
|
|
|
|
TestBody();
|
|
|
|
// Verify we arrived here because interruptor was called
|
|
// not due to a bug causing us to exit the loop too early.
|
|
CHECK(!should_continue());
|
|
}
|
|
|
|
void WakeUpInterruptor() {
|
|
sem_.Signal();
|
|
}
|
|
|
|
bool should_continue() const { return should_continue_; }
|
|
|
|
bool ShouldContinue() {
|
|
if (warmup_ > 0) {
|
|
if (--warmup_ == 0) {
|
|
WakeUpInterruptor();
|
|
}
|
|
}
|
|
|
|
return should_continue_;
|
|
}
|
|
|
|
static void ShouldContinueCallback(
|
|
const v8::FunctionCallbackInfo<Value>& info) {
|
|
RequestInterruptTestBase* test =
|
|
reinterpret_cast<RequestInterruptTestBase*>(
|
|
info.Data().As<v8::External>()->Value());
|
|
info.GetReturnValue().Set(test->ShouldContinue());
|
|
}
|
|
|
|
LocalContext env_;
|
|
v8::Isolate* isolate_;
|
|
v8::base::Semaphore sem_;
|
|
int warmup_;
|
|
bool should_continue_;
|
|
};
|
|
|
|
|
|
class RequestInterruptTestBaseWithSimpleInterrupt
|
|
: public RequestInterruptTestBase {
|
|
public:
|
|
RequestInterruptTestBaseWithSimpleInterrupt() : i_thread(this) { }
|
|
|
|
void StartInterruptThread() override { CHECK(i_thread.Start()); }
|
|
|
|
private:
|
|
class InterruptThread : public v8::base::Thread {
|
|
public:
|
|
explicit InterruptThread(RequestInterruptTestBase* test)
|
|
: Thread(Options("RequestInterruptTest")), test_(test) {}
|
|
|
|
void Run() override {
|
|
test_->sem_.Wait();
|
|
test_->isolate_->RequestInterrupt(&OnInterrupt, test_);
|
|
}
|
|
|
|
static void OnInterrupt(v8::Isolate* isolate, void* data) {
|
|
reinterpret_cast<RequestInterruptTestBase*>(data)->
|
|
should_continue_ = false;
|
|
}
|
|
|
|
private:
|
|
RequestInterruptTestBase* test_;
|
|
};
|
|
|
|
InterruptThread i_thread;
|
|
};
|
|
|
|
|
|
class RequestInterruptTestWithFunctionCall
|
|
: public RequestInterruptTestBaseWithSimpleInterrupt {
|
|
public:
|
|
void TestBody() override {
|
|
Local<Function> func = Function::New(env_.local(), ShouldContinueCallback,
|
|
v8::External::New(isolate_, this))
|
|
.ToLocalChecked();
|
|
CHECK(env_->Global()
|
|
->Set(env_.local(), v8_str("ShouldContinue"), func)
|
|
.FromJust());
|
|
|
|
CompileRun("while (ShouldContinue()) { }");
|
|
}
|
|
};
|
|
|
|
|
|
class RequestInterruptTestWithMethodCall
|
|
: public RequestInterruptTestBaseWithSimpleInterrupt {
|
|
public:
|
|
void TestBody() override {
|
|
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
|
|
v8::Local<v8::Template> proto = t->PrototypeTemplate();
|
|
proto->Set(isolate_, "shouldContinue",
|
|
FunctionTemplate::New(isolate_, ShouldContinueCallback,
|
|
v8::External::New(isolate_, this)));
|
|
CHECK(env_->Global()
|
|
->Set(env_.local(), v8_str("Klass"),
|
|
t->GetFunction(env_.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
CompileRun("var obj = new Klass; while (obj.shouldContinue()) { }");
|
|
}
|
|
};
|
|
|
|
|
|
class RequestInterruptTestWithAccessor
|
|
: public RequestInterruptTestBaseWithSimpleInterrupt {
|
|
public:
|
|
void TestBody() override {
|
|
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
|
|
v8::Local<v8::Template> proto = t->PrototypeTemplate();
|
|
proto->SetAccessorProperty(v8_str("shouldContinue"), FunctionTemplate::New(
|
|
isolate_, ShouldContinueCallback, v8::External::New(isolate_, this)));
|
|
CHECK(env_->Global()
|
|
->Set(env_.local(), v8_str("Klass"),
|
|
t->GetFunction(env_.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
CompileRun("var obj = new Klass; while (obj.shouldContinue) { }");
|
|
}
|
|
};
|
|
|
|
|
|
class RequestInterruptTestWithNativeAccessor
|
|
: public RequestInterruptTestBaseWithSimpleInterrupt {
|
|
public:
|
|
void TestBody() override {
|
|
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
|
|
t->InstanceTemplate()->SetNativeDataProperty(
|
|
v8_str("shouldContinue"), &ShouldContinueNativeGetter, nullptr,
|
|
v8::External::New(isolate_, this));
|
|
CHECK(env_->Global()
|
|
->Set(env_.local(), v8_str("Klass"),
|
|
t->GetFunction(env_.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
CompileRun("var obj = new Klass; while (obj.shouldContinue) { }");
|
|
}
|
|
|
|
private:
|
|
static void ShouldContinueNativeGetter(
|
|
Local<String> property,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
RequestInterruptTestBase* test =
|
|
reinterpret_cast<RequestInterruptTestBase*>(
|
|
info.Data().As<v8::External>()->Value());
|
|
info.GetReturnValue().Set(test->ShouldContinue());
|
|
}
|
|
};
|
|
|
|
|
|
class RequestInterruptTestWithMethodCallAndInterceptor
|
|
: public RequestInterruptTestBaseWithSimpleInterrupt {
|
|
public:
|
|
void TestBody() override {
|
|
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
|
|
v8::Local<v8::Template> proto = t->PrototypeTemplate();
|
|
proto->Set(isolate_, "shouldContinue",
|
|
FunctionTemplate::New(isolate_, ShouldContinueCallback,
|
|
v8::External::New(isolate_, this)));
|
|
v8::Local<v8::ObjectTemplate> instance_template = t->InstanceTemplate();
|
|
instance_template->SetHandler(
|
|
v8::NamedPropertyHandlerConfiguration(EmptyInterceptor));
|
|
|
|
CHECK(env_->Global()
|
|
->Set(env_.local(), v8_str("Klass"),
|
|
t->GetFunction(env_.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
CompileRun("var obj = new Klass; while (obj.shouldContinue()) { }");
|
|
}
|
|
|
|
private:
|
|
static void EmptyInterceptor(
|
|
Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) {}
|
|
};
|
|
|
|
|
|
class RequestInterruptTestWithMathAbs
|
|
: public RequestInterruptTestBaseWithSimpleInterrupt {
|
|
public:
|
|
void TestBody() override {
|
|
env_->Global()
|
|
->Set(env_.local(), v8_str("WakeUpInterruptor"),
|
|
Function::New(env_.local(), WakeUpInterruptorCallback,
|
|
v8::External::New(isolate_, this))
|
|
.ToLocalChecked())
|
|
.FromJust();
|
|
|
|
env_->Global()
|
|
->Set(env_.local(), v8_str("ShouldContinue"),
|
|
Function::New(env_.local(), ShouldContinueCallback,
|
|
v8::External::New(isolate_, this))
|
|
.ToLocalChecked())
|
|
.FromJust();
|
|
|
|
i::FLAG_allow_natives_syntax = true;
|
|
CompileRun(
|
|
"function loopish(o) {"
|
|
" var pre = 10;"
|
|
" while (o.abs(1) > 0) {"
|
|
" if (o.abs(1) >= 0 && !ShouldContinue()) break;"
|
|
" if (pre > 0) {"
|
|
" if (--pre === 0) WakeUpInterruptor(o === Math);"
|
|
" }"
|
|
" }"
|
|
"};"
|
|
"%PrepareFunctionForOptimization(loopish);"
|
|
"var i = 50;"
|
|
"var obj = {abs: function () { return i-- }, x: null};"
|
|
"delete obj.x;"
|
|
"loopish(obj);"
|
|
"%OptimizeFunctionOnNextCall(loopish);"
|
|
"loopish(Math);");
|
|
|
|
i::FLAG_allow_natives_syntax = false;
|
|
}
|
|
|
|
private:
|
|
static void WakeUpInterruptorCallback(
|
|
const v8::FunctionCallbackInfo<Value>& info) {
|
|
if (!info[0]->BooleanValue(info.GetIsolate())) {
|
|
return;
|
|
}
|
|
|
|
RequestInterruptTestBase* test =
|
|
reinterpret_cast<RequestInterruptTestBase*>(
|
|
info.Data().As<v8::External>()->Value());
|
|
test->WakeUpInterruptor();
|
|
}
|
|
|
|
static void ShouldContinueCallback(
|
|
const v8::FunctionCallbackInfo<Value>& info) {
|
|
RequestInterruptTestBase* test =
|
|
reinterpret_cast<RequestInterruptTestBase*>(
|
|
info.Data().As<v8::External>()->Value());
|
|
info.GetReturnValue().Set(test->should_continue());
|
|
}
|
|
};
|
|
|
|
TEST(RequestInterruptTestWithFunctionCall) {
|
|
RequestInterruptTestWithFunctionCall().RunTest();
|
|
}
|
|
|
|
|
|
TEST(RequestInterruptTestWithMethodCall) {
|
|
RequestInterruptTestWithMethodCall().RunTest();
|
|
}
|
|
|
|
|
|
TEST(RequestInterruptTestWithAccessor) {
|
|
RequestInterruptTestWithAccessor().RunTest();
|
|
}
|
|
|
|
|
|
TEST(RequestInterruptTestWithNativeAccessor) {
|
|
RequestInterruptTestWithNativeAccessor().RunTest();
|
|
}
|
|
|
|
|
|
TEST(RequestInterruptTestWithMethodCallAndInterceptor) {
|
|
RequestInterruptTestWithMethodCallAndInterceptor().RunTest();
|
|
}
|
|
|
|
|
|
TEST(RequestInterruptTestWithMathAbs) {
|
|
RequestInterruptTestWithMathAbs().RunTest();
|
|
}
|
|
|
|
class RequestMultipleInterrupts : public RequestInterruptTestBase {
|
|
public:
|
|
RequestMultipleInterrupts() : i_thread(this), counter_(0) {}
|
|
|
|
void StartInterruptThread() override { CHECK(i_thread.Start()); }
|
|
|
|
void TestBody() override {
|
|
Local<Function> func = Function::New(env_.local(), ShouldContinueCallback,
|
|
v8::External::New(isolate_, this))
|
|
.ToLocalChecked();
|
|
CHECK(env_->Global()
|
|
->Set(env_.local(), v8_str("ShouldContinue"), func)
|
|
.FromJust());
|
|
|
|
CompileRun("while (ShouldContinue()) { }");
|
|
}
|
|
|
|
private:
|
|
class InterruptThread : public v8::base::Thread {
|
|
public:
|
|
enum { NUM_INTERRUPTS = 10 };
|
|
explicit InterruptThread(RequestMultipleInterrupts* test)
|
|
: Thread(Options("RequestInterruptTest")), test_(test) {}
|
|
|
|
void Run() override {
|
|
test_->sem_.Wait();
|
|
for (int i = 0; i < NUM_INTERRUPTS; i++) {
|
|
test_->isolate_->RequestInterrupt(&OnInterrupt, test_);
|
|
}
|
|
}
|
|
|
|
static void OnInterrupt(v8::Isolate* isolate, void* data) {
|
|
RequestMultipleInterrupts* test =
|
|
reinterpret_cast<RequestMultipleInterrupts*>(data);
|
|
test->should_continue_ = ++test->counter_ < NUM_INTERRUPTS;
|
|
}
|
|
|
|
private:
|
|
RequestMultipleInterrupts* test_;
|
|
};
|
|
|
|
InterruptThread i_thread;
|
|
int counter_;
|
|
};
|
|
|
|
|
|
TEST(RequestMultipleInterrupts) { RequestMultipleInterrupts().RunTest(); }
|
|
|
|
|
|
static bool interrupt_was_called = false;
|
|
|
|
|
|
void SmallScriptsInterruptCallback(v8::Isolate* isolate, void* data) {
|
|
interrupt_was_called = true;
|
|
}
|
|
|
|
|
|
TEST(RequestInterruptSmallScripts) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
interrupt_was_called = false;
|
|
isolate->RequestInterrupt(&SmallScriptsInterruptCallback, nullptr);
|
|
CompileRun("(function(x){return x;})(1);");
|
|
CHECK(interrupt_was_called);
|
|
}
|
|
|
|
|
|
static Local<Value> function_new_expected_env;
|
|
static void FunctionNewCallback(const v8::FunctionCallbackInfo<Value>& info) {
|
|
CHECK(
|
|
function_new_expected_env->Equals(info.GetIsolate()->GetCurrentContext(),
|
|
info.Data())
|
|
.FromJust());
|
|
info.GetReturnValue().Set(17);
|
|
}
|
|
|
|
|
|
THREADED_TEST(FunctionNew) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<Object> data = v8::Object::New(isolate);
|
|
function_new_expected_env = data;
|
|
Local<Function> func =
|
|
Function::New(env.local(), FunctionNewCallback, data).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("func"), func).FromJust());
|
|
Local<Value> result = CompileRun("func();");
|
|
CHECK(v8::Integer::New(isolate, 17)->Equals(env.local(), result).FromJust());
|
|
// Serial number should be invalid => should not be cached.
|
|
auto serial_number =
|
|
i::Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*func))
|
|
->shared()
|
|
.get_api_func_data()
|
|
.serial_number();
|
|
CHECK_EQ(i::TemplateInfo::kDoNotCache, serial_number);
|
|
|
|
// Verify that each Function::New creates a new function instance
|
|
Local<Object> data2 = v8::Object::New(isolate);
|
|
function_new_expected_env = data2;
|
|
Local<Function> func2 =
|
|
Function::New(env.local(), FunctionNewCallback, data2).ToLocalChecked();
|
|
CHECK(!func2->IsNull());
|
|
CHECK(!func->Equals(env.local(), func2).FromJust());
|
|
CHECK(env->Global()->Set(env.local(), v8_str("func2"), func2).FromJust());
|
|
Local<Value> result2 = CompileRun("func2();");
|
|
CHECK(v8::Integer::New(isolate, 17)->Equals(env.local(), result2).FromJust());
|
|
}
|
|
|
|
namespace {
|
|
|
|
void Verify(v8::Isolate* isolate, Local<v8::Object> obj) {
|
|
#if VERIFY_HEAP
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
i::Handle<i::JSReceiver> i_obj = v8::Utils::OpenHandle(*obj);
|
|
i_obj->ObjectVerify(i_isolate);
|
|
#endif
|
|
}
|
|
|
|
} // namespace
|
|
|
|
THREADED_TEST(ObjectNew) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
{
|
|
// Verify that Object::New(null) produces an object with a null
|
|
// [[Prototype]].
|
|
Local<v8::Object> obj =
|
|
v8::Object::New(isolate, v8::Null(isolate), nullptr, nullptr, 0);
|
|
CHECK(obj->GetPrototype()->IsNull());
|
|
Verify(isolate, obj);
|
|
Local<Array> keys = obj->GetOwnPropertyNames(env.local()).ToLocalChecked();
|
|
CHECK_EQ(0, keys->Length());
|
|
}
|
|
{
|
|
// Verify that Object::New(proto) produces an object with
|
|
// proto as it's [[Prototype]].
|
|
Local<v8::Object> proto = v8::Object::New(isolate);
|
|
Local<v8::Object> obj =
|
|
v8::Object::New(isolate, proto, nullptr, nullptr, 0);
|
|
Verify(isolate, obj);
|
|
CHECK(obj->GetPrototype()->SameValue(proto));
|
|
}
|
|
{
|
|
// Verify that the properties are installed correctly.
|
|
Local<v8::Name> names[3] = {v8_str("a"), v8_str("b"), v8_str("c")};
|
|
Local<v8::Value> values[3] = {v8_num(1), v8_num(2), v8_num(3)};
|
|
Local<v8::Object> obj = v8::Object::New(isolate, v8::Null(isolate), names,
|
|
values, arraysize(values));
|
|
Verify(isolate, obj);
|
|
Local<Array> keys = obj->GetOwnPropertyNames(env.local()).ToLocalChecked();
|
|
CHECK_EQ(arraysize(names), keys->Length());
|
|
for (uint32_t i = 0; i < arraysize(names); ++i) {
|
|
CHECK(names[i]->SameValue(keys->Get(env.local(), i).ToLocalChecked()));
|
|
CHECK(values[i]->SameValue(
|
|
obj->Get(env.local(), names[i]).ToLocalChecked()));
|
|
}
|
|
}
|
|
{
|
|
// Same as above, but with non-null prototype.
|
|
Local<v8::Object> proto = v8::Object::New(isolate);
|
|
Local<v8::Name> names[3] = {v8_str("x"), v8_str("y"), v8_str("z")};
|
|
Local<v8::Value> values[3] = {v8_num(1), v8_num(2), v8_num(3)};
|
|
Local<v8::Object> obj =
|
|
v8::Object::New(isolate, proto, names, values, arraysize(values));
|
|
CHECK(obj->GetPrototype()->SameValue(proto));
|
|
Verify(isolate, obj);
|
|
Local<Array> keys = obj->GetOwnPropertyNames(env.local()).ToLocalChecked();
|
|
CHECK_EQ(arraysize(names), keys->Length());
|
|
for (uint32_t i = 0; i < arraysize(names); ++i) {
|
|
CHECK(names[i]->SameValue(keys->Get(env.local(), i).ToLocalChecked()));
|
|
CHECK(values[i]->SameValue(
|
|
obj->Get(env.local(), names[i]).ToLocalChecked()));
|
|
}
|
|
}
|
|
{
|
|
// This has to work with duplicate names too.
|
|
Local<v8::Name> names[3] = {v8_str("a"), v8_str("a"), v8_str("a")};
|
|
Local<v8::Value> values[3] = {v8_num(1), v8_num(2), v8_num(3)};
|
|
Local<v8::Object> obj = v8::Object::New(isolate, v8::Null(isolate), names,
|
|
values, arraysize(values));
|
|
Verify(isolate, obj);
|
|
Local<Array> keys = obj->GetOwnPropertyNames(env.local()).ToLocalChecked();
|
|
CHECK_EQ(1, keys->Length());
|
|
CHECK(v8_str("a")->SameValue(keys->Get(env.local(), 0).ToLocalChecked()));
|
|
CHECK(v8_num(3)->SameValue(
|
|
obj->Get(env.local(), v8_str("a")).ToLocalChecked()));
|
|
}
|
|
{
|
|
// This has to work with array indices too.
|
|
Local<v8::Name> names[2] = {v8_str("0"), v8_str("1")};
|
|
Local<v8::Value> values[2] = {v8_num(0), v8_num(1)};
|
|
Local<v8::Object> obj = v8::Object::New(isolate, v8::Null(isolate), names,
|
|
values, arraysize(values));
|
|
Verify(isolate, obj);
|
|
Local<Array> keys = obj->GetOwnPropertyNames(env.local()).ToLocalChecked();
|
|
CHECK_EQ(arraysize(names), keys->Length());
|
|
for (uint32_t i = 0; i < arraysize(names); ++i) {
|
|
CHECK(v8::Number::New(isolate, i)
|
|
->SameValue(keys->Get(env.local(), i).ToLocalChecked()));
|
|
CHECK(values[i]->SameValue(obj->Get(env.local(), i).ToLocalChecked()));
|
|
}
|
|
}
|
|
{
|
|
// This has to work with mixed array indices / property names too.
|
|
Local<v8::Name> names[2] = {v8_str("0"), v8_str("x")};
|
|
Local<v8::Value> values[2] = {v8_num(42), v8_num(24)};
|
|
Local<v8::Object> obj = v8::Object::New(isolate, v8::Null(isolate), names,
|
|
values, arraysize(values));
|
|
Verify(isolate, obj);
|
|
Local<Array> keys = obj->GetOwnPropertyNames(env.local()).ToLocalChecked();
|
|
CHECK_EQ(arraysize(names), keys->Length());
|
|
// 0 -> 42
|
|
CHECK(v8_num(0)->SameValue(keys->Get(env.local(), 0).ToLocalChecked()));
|
|
CHECK(
|
|
values[0]->SameValue(obj->Get(env.local(), names[0]).ToLocalChecked()));
|
|
// "x" -> 24
|
|
CHECK(v8_str("x")->SameValue(keys->Get(env.local(), 1).ToLocalChecked()));
|
|
CHECK(
|
|
values[1]->SameValue(obj->Get(env.local(), names[1]).ToLocalChecked()));
|
|
}
|
|
{
|
|
// Verify that this also works for a couple thousand properties.
|
|
size_t const kLength = 10 * 1024;
|
|
Local<v8::Name> names[kLength];
|
|
Local<v8::Value> values[kLength];
|
|
for (size_t i = 0; i < arraysize(names); ++i) {
|
|
std::ostringstream ost;
|
|
ost << "a" << i;
|
|
names[i] = v8_str(ost.str().c_str());
|
|
values[i] = v8_num(static_cast<double>(i));
|
|
}
|
|
Local<v8::Object> obj = v8::Object::New(isolate, v8::Null(isolate), names,
|
|
values, arraysize(names));
|
|
Verify(isolate, obj);
|
|
Local<Array> keys = obj->GetOwnPropertyNames(env.local()).ToLocalChecked();
|
|
CHECK_EQ(arraysize(names), keys->Length());
|
|
for (uint32_t i = 0; i < arraysize(names); ++i) {
|
|
CHECK(names[i]->SameValue(keys->Get(env.local(), i).ToLocalChecked()));
|
|
CHECK(values[i]->SameValue(
|
|
obj->Get(env.local(), names[i]).ToLocalChecked()));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(EscapableHandleScope) {
|
|
HandleScope outer_scope(CcTest::isolate());
|
|
LocalContext context;
|
|
const int runs = 10;
|
|
Local<String> values[runs];
|
|
for (int i = 0; i < runs; i++) {
|
|
v8::EscapableHandleScope inner_scope(CcTest::isolate());
|
|
Local<String> value;
|
|
if (i != 0) value = v8_str("escape value");
|
|
if (i < runs / 2) {
|
|
values[i] = inner_scope.Escape(value);
|
|
} else {
|
|
values[i] = inner_scope.EscapeMaybe(v8::MaybeLocal<String>(value))
|
|
.ToLocalChecked();
|
|
}
|
|
}
|
|
for (int i = 0; i < runs; i++) {
|
|
Local<String> expected;
|
|
if (i != 0) {
|
|
CHECK(v8_str("escape value")
|
|
->Equals(context.local(), values[i])
|
|
.FromJust());
|
|
} else {
|
|
CHECK(values[i].IsEmpty());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void SetterWhichExpectsThisAndHolderToDiffer(
|
|
Local<String>, Local<Value>, const v8::PropertyCallbackInfo<void>& info) {
|
|
CHECK(info.Holder() != info.This());
|
|
}
|
|
|
|
|
|
TEST(Regress239669) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessor(v8_str("x"), nullptr,
|
|
SetterWhichExpectsThisAndHolderToDiffer);
|
|
CHECK(context->Global()
|
|
->Set(context.local(), v8_str("P"),
|
|
templ->NewInstance(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
CompileRun(
|
|
"function C1() {"
|
|
" this.x = 23;"
|
|
"};"
|
|
"C1.prototype = P;"
|
|
"for (var i = 0; i < 4; i++ ) {"
|
|
" new C1();"
|
|
"}");
|
|
}
|
|
|
|
|
|
class ApiCallOptimizationChecker {
|
|
private:
|
|
static Local<Object> data;
|
|
static Local<Object> receiver;
|
|
static Local<Object> holder;
|
|
static Local<Object> callee;
|
|
static int count;
|
|
|
|
static void OptimizationCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
CHECK(data == info.Data());
|
|
CHECK(receiver == info.This());
|
|
if (info.Length() == 1) {
|
|
CHECK(v8_num(1)
|
|
->Equals(info.GetIsolate()->GetCurrentContext(), info[0])
|
|
.FromJust());
|
|
}
|
|
CHECK(holder == info.Holder());
|
|
count++;
|
|
info.GetReturnValue().Set(v8_str("returned"));
|
|
}
|
|
|
|
public:
|
|
enum SignatureType {
|
|
kNoSignature,
|
|
kSignatureOnReceiver,
|
|
kSignatureOnPrototype
|
|
};
|
|
|
|
void RunAll() {
|
|
SignatureType signature_types[] =
|
|
{kNoSignature, kSignatureOnReceiver, kSignatureOnPrototype};
|
|
for (unsigned i = 0; i < arraysize(signature_types); i++) {
|
|
SignatureType signature_type = signature_types[i];
|
|
for (int j = 0; j < 2; j++) {
|
|
bool global = j == 0;
|
|
int key = signature_type +
|
|
arraysize(signature_types) * (global ? 1 : 0);
|
|
Run(signature_type, global, key);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Run(SignatureType signature_type, bool global, int key) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
// Build a template for signature checks.
|
|
Local<v8::ObjectTemplate> signature_template;
|
|
Local<v8::Signature> signature;
|
|
{
|
|
Local<v8::FunctionTemplate> parent_template =
|
|
FunctionTemplate::New(isolate);
|
|
Local<v8::FunctionTemplate> function_template
|
|
= FunctionTemplate::New(isolate);
|
|
function_template->Inherit(parent_template);
|
|
switch (signature_type) {
|
|
case kNoSignature:
|
|
break;
|
|
case kSignatureOnReceiver:
|
|
signature = v8::Signature::New(isolate, function_template);
|
|
break;
|
|
case kSignatureOnPrototype:
|
|
signature = v8::Signature::New(isolate, parent_template);
|
|
break;
|
|
}
|
|
signature_template = function_template->InstanceTemplate();
|
|
}
|
|
// Global object must pass checks.
|
|
Local<v8::Context> context =
|
|
v8::Context::New(isolate, nullptr, signature_template);
|
|
v8::Context::Scope context_scope(context);
|
|
// Install regular object that can pass signature checks.
|
|
Local<Object> function_receiver =
|
|
signature_template->NewInstance(context).ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context, v8_str("function_receiver"), function_receiver)
|
|
.FromJust());
|
|
// Get the holder objects.
|
|
Local<Object> inner_global =
|
|
Local<Object>::Cast(context->Global()->GetPrototype());
|
|
data = Object::New(isolate);
|
|
Local<FunctionTemplate> function_template = FunctionTemplate::New(
|
|
isolate, OptimizationCallback, data, signature);
|
|
Local<Function> function =
|
|
function_template->GetFunction(context).ToLocalChecked();
|
|
Local<Object> global_holder = inner_global;
|
|
Local<Object> function_holder = function_receiver;
|
|
if (signature_type == kSignatureOnPrototype) {
|
|
function_holder = Local<Object>::Cast(function_holder->GetPrototype());
|
|
global_holder = Local<Object>::Cast(global_holder->GetPrototype());
|
|
}
|
|
global_holder->Set(context, v8_str("g_f"), function).FromJust();
|
|
global_holder->SetAccessorProperty(v8_str("g_acc"), function, function);
|
|
function_holder->Set(context, v8_str("f"), function).FromJust();
|
|
function_holder->SetAccessorProperty(v8_str("acc"), function, function);
|
|
// Initialize expected values.
|
|
callee = function;
|
|
count = 0;
|
|
if (global) {
|
|
receiver = context->Global();
|
|
holder = inner_global;
|
|
} else {
|
|
holder = function_receiver;
|
|
// If not using a signature, add something else to the prototype chain
|
|
// to test the case that holder != receiver
|
|
if (signature_type == kNoSignature) {
|
|
receiver = Local<Object>::Cast(CompileRun(
|
|
"var receiver_subclass = {};\n"
|
|
"receiver_subclass.__proto__ = function_receiver;\n"
|
|
"receiver_subclass"));
|
|
} else {
|
|
receiver = Local<Object>::Cast(CompileRun(
|
|
"var receiver_subclass = function_receiver;\n"
|
|
"receiver_subclass"));
|
|
}
|
|
}
|
|
// With no signature, the holder is not set.
|
|
if (signature_type == kNoSignature) holder = receiver;
|
|
// build wrap_function
|
|
v8::base::ScopedVector<char> wrap_function(200);
|
|
if (global) {
|
|
v8::base::SNPrintF(wrap_function,
|
|
"function wrap_f_%d() { var f = g_f; return f(); }\n"
|
|
"function wrap_get_%d() { return this.g_acc; }\n"
|
|
"function wrap_set_%d() { return this.g_acc = 1; }\n",
|
|
key, key, key);
|
|
} else {
|
|
v8::base::SNPrintF(
|
|
wrap_function,
|
|
"function wrap_f_%d() { return receiver_subclass.f(); }\n"
|
|
"function wrap_get_%d() { return receiver_subclass.acc; }\n"
|
|
"function wrap_set_%d() { return receiver_subclass.acc = 1; }\n",
|
|
key, key, key);
|
|
}
|
|
// build source string
|
|
v8::base::ScopedVector<char> source(1000);
|
|
v8::base::SNPrintF(source,
|
|
"%s\n" // wrap functions
|
|
"function wrap_f() { return wrap_f_%d(); }\n"
|
|
"function wrap_get() { return wrap_get_%d(); }\n"
|
|
"function wrap_set() { return wrap_set_%d(); }\n"
|
|
"check = function(returned) {\n"
|
|
" if (returned !== 'returned') { throw returned; }\n"
|
|
"};\n"
|
|
"\n"
|
|
"%%PrepareFunctionForOptimization(wrap_f_%d);"
|
|
"check(wrap_f());\n"
|
|
"check(wrap_f());\n"
|
|
"%%OptimizeFunctionOnNextCall(wrap_f_%d);\n"
|
|
"check(wrap_f());\n"
|
|
"\n"
|
|
"%%PrepareFunctionForOptimization(wrap_get_%d);"
|
|
"check(wrap_get());\n"
|
|
"check(wrap_get());\n"
|
|
"%%OptimizeFunctionOnNextCall(wrap_get_%d);\n"
|
|
"check(wrap_get());\n"
|
|
"\n"
|
|
"check = function(returned) {\n"
|
|
" if (returned !== 1) { throw returned; }\n"
|
|
"};\n"
|
|
"%%PrepareFunctionForOptimization(wrap_set_%d);"
|
|
"check(wrap_set());\n"
|
|
"check(wrap_set());\n"
|
|
"%%OptimizeFunctionOnNextCall(wrap_set_%d);\n"
|
|
"check(wrap_set());\n",
|
|
wrap_function.begin(), key, key, key, key, key, key, key,
|
|
key, key);
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun(source.begin());
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(9, count);
|
|
}
|
|
};
|
|
|
|
|
|
Local<Object> ApiCallOptimizationChecker::data;
|
|
Local<Object> ApiCallOptimizationChecker::receiver;
|
|
Local<Object> ApiCallOptimizationChecker::holder;
|
|
Local<Object> ApiCallOptimizationChecker::callee;
|
|
int ApiCallOptimizationChecker::count = 0;
|
|
|
|
|
|
TEST(FunctionCallOptimization) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
ApiCallOptimizationChecker checker;
|
|
checker.RunAll();
|
|
}
|
|
|
|
|
|
TEST(FunctionCallOptimizationMultipleArgs) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<Object> global = context->Global();
|
|
Local<v8::Function> function =
|
|
Function::New(context.local(), Returns42).ToLocalChecked();
|
|
global->Set(context.local(), v8_str("x"), function).FromJust();
|
|
CompileRun(
|
|
"function x_wrap() {\n"
|
|
" for (var i = 0; i < 5; i++) {\n"
|
|
" x(1,2,3);\n"
|
|
" }\n"
|
|
"}\n"
|
|
"%PrepareFunctionForOptimization(x_wrap);\n"
|
|
"x_wrap();\n"
|
|
"%OptimizeFunctionOnNextCall(x_wrap);"
|
|
"x_wrap();\n");
|
|
}
|
|
|
|
|
|
static void ReturnsSymbolCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(v8::Symbol::New(info.GetIsolate()));
|
|
}
|
|
|
|
|
|
TEST(ApiCallbackCanReturnSymbols) {
|
|
i::FLAG_allow_natives_syntax = true;
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<Object> global = context->Global();
|
|
Local<v8::Function> function =
|
|
Function::New(context.local(), ReturnsSymbolCallback).ToLocalChecked();
|
|
global->Set(context.local(), v8_str("x"), function).FromJust();
|
|
CompileRun(
|
|
"function x_wrap() {\n"
|
|
" for (var i = 0; i < 5; i++) {\n"
|
|
" x();\n"
|
|
" }\n"
|
|
"}\n"
|
|
"%PrepareFunctionForOptimization(x_wrap);\n"
|
|
"x_wrap();\n"
|
|
"%OptimizeFunctionOnNextCall(x_wrap);"
|
|
"x_wrap();\n");
|
|
}
|
|
|
|
|
|
TEST(EmptyApiCallback) {
|
|
LocalContext context;
|
|
auto isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
auto global = context->Global();
|
|
auto function = FunctionTemplate::New(isolate)
|
|
->GetFunction(context.local())
|
|
.ToLocalChecked();
|
|
global->Set(context.local(), v8_str("x"), function).FromJust();
|
|
|
|
auto result = CompileRun("x()");
|
|
CHECK(v8::Utils::OpenHandle(*result)->IsJSGlobalProxy());
|
|
|
|
result = CompileRun("x(1,2,3)");
|
|
CHECK(v8::Utils::OpenHandle(*result)->IsJSGlobalProxy());
|
|
|
|
result = CompileRun("x.call(undefined)");
|
|
CHECK(v8::Utils::OpenHandle(*result)->IsJSGlobalProxy());
|
|
|
|
result = CompileRun("x.call(null)");
|
|
CHECK(v8::Utils::OpenHandle(*result)->IsJSGlobalProxy());
|
|
|
|
result = CompileRun("7 + x.call(3) + 11");
|
|
CHECK(result->IsInt32());
|
|
CHECK_EQ(21, result->Int32Value(context.local()).FromJust());
|
|
|
|
result = CompileRun("7 + x.call(3, 101, 102, 103, 104) + 11");
|
|
CHECK(result->IsInt32());
|
|
CHECK_EQ(21, result->Int32Value(context.local()).FromJust());
|
|
|
|
result = CompileRun("var y = []; x.call(y)");
|
|
CHECK(result->IsArray());
|
|
|
|
result = CompileRun("x.call(y, 1, 2, 3, 4)");
|
|
CHECK(result->IsArray());
|
|
}
|
|
|
|
|
|
TEST(SimpleSignatureCheck) {
|
|
LocalContext context;
|
|
auto isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
auto global = context->Global();
|
|
auto sig_obj = FunctionTemplate::New(isolate);
|
|
auto sig = v8::Signature::New(isolate, sig_obj);
|
|
auto x = FunctionTemplate::New(isolate, Returns42, Local<Value>(), sig);
|
|
global->Set(context.local(), v8_str("sig_obj"),
|
|
sig_obj->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
global->Set(context.local(), v8_str("x"),
|
|
x->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
CompileRun("var s = new sig_obj();");
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
CompileRun("x()");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
CompileRun("x.call(1)");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
auto result = CompileRun("s.x = x; s.x()");
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(42, result->Int32Value(context.local()).FromJust());
|
|
}
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
auto result = CompileRun("x.call(s)");
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(42, result->Int32Value(context.local()).FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
TEST(ChainSignatureCheck) {
|
|
LocalContext context;
|
|
auto isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
auto global = context->Global();
|
|
auto sig_obj = FunctionTemplate::New(isolate);
|
|
auto sig = v8::Signature::New(isolate, sig_obj);
|
|
for (int i = 0; i < 4; ++i) {
|
|
auto temp = FunctionTemplate::New(isolate);
|
|
temp->Inherit(sig_obj);
|
|
sig_obj = temp;
|
|
}
|
|
auto x = FunctionTemplate::New(isolate, Returns42, Local<Value>(), sig);
|
|
global->Set(context.local(), v8_str("sig_obj"),
|
|
sig_obj->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
global->Set(context.local(), v8_str("x"),
|
|
x->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust();
|
|
CompileRun("var s = new sig_obj();");
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
CompileRun("x()");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
CompileRun("x.call(1)");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
auto result = CompileRun("s.x = x; s.x()");
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(42, result->Int32Value(context.local()).FromJust());
|
|
}
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
auto result = CompileRun("x.call(s)");
|
|
CHECK(!try_catch.HasCaught());
|
|
CHECK_EQ(42, result->Int32Value(context.local()).FromJust());
|
|
}
|
|
}
|
|
|
|
|
|
static const char* last_event_message;
|
|
// See v8::LogEventStatus
|
|
static v8::LogEventStatus last_event_status;
|
|
static int event_count = 0;
|
|
void StoringEventLoggerCallback(const char* message, int status) {
|
|
last_event_message = message;
|
|
last_event_status = static_cast<v8::LogEventStatus>(status);
|
|
event_count++;
|
|
}
|
|
|
|
|
|
TEST(EventLogging) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
isolate->SetEventLogger(StoringEventLoggerCallback);
|
|
v8::internal::NestedTimedHistogram histogram(
|
|
"V8.Test", 0, 10000, v8::internal::TimedHistogramResolution::MILLISECOND,
|
|
50, reinterpret_cast<v8::internal::Isolate*>(isolate)->counters());
|
|
event_count = 0;
|
|
int count = 0;
|
|
{
|
|
CHECK_EQ(0, event_count);
|
|
{
|
|
CHECK_EQ(0, event_count);
|
|
v8::internal::NestedTimedHistogramScope scope0(&histogram);
|
|
CHECK_EQ(0, strcmp("V8.Test", last_event_message));
|
|
CHECK_EQ(v8::LogEventStatus::kStart, last_event_status);
|
|
CHECK_EQ(++count, event_count);
|
|
}
|
|
CHECK_EQ(v8::LogEventStatus::kEnd, last_event_status);
|
|
CHECK_EQ(++count, event_count);
|
|
|
|
v8::internal::NestedTimedHistogramScope scope1(&histogram);
|
|
CHECK_EQ(0, strcmp("V8.Test", last_event_message));
|
|
CHECK_EQ(v8::LogEventStatus::kStart, last_event_status);
|
|
CHECK_EQ(++count, event_count);
|
|
{
|
|
CHECK_EQ(count, event_count);
|
|
v8::internal::NestedTimedHistogramScope scope2(&histogram);
|
|
CHECK_EQ(0, strcmp("V8.Test", last_event_message));
|
|
CHECK_EQ(v8::LogEventStatus::kStart, last_event_status);
|
|
CHECK_EQ(++count, event_count);
|
|
{
|
|
CHECK_EQ(count, event_count);
|
|
v8::internal::NestedTimedHistogramScope scope3(&histogram);
|
|
CHECK_EQ(++count, event_count);
|
|
v8::internal::PauseNestedTimedHistogramScope scope4(&histogram);
|
|
// The outer timer scope is just paused, no event is emited yet.
|
|
CHECK_EQ(count, event_count);
|
|
{
|
|
CHECK_EQ(count, event_count);
|
|
v8::internal::NestedTimedHistogramScope scope5(&histogram);
|
|
v8::internal::NestedTimedHistogramScope scope5_1(&histogram);
|
|
CHECK_EQ(0, strcmp("V8.Test", last_event_message));
|
|
CHECK_EQ(v8::LogEventStatus::kStart, last_event_status);
|
|
count++;
|
|
CHECK_EQ(++count, event_count);
|
|
}
|
|
CHECK_EQ(0, strcmp("V8.Test", last_event_message));
|
|
CHECK_EQ(v8::LogEventStatus::kEnd, last_event_status);
|
|
count++;
|
|
CHECK_EQ(++count, event_count);
|
|
}
|
|
CHECK_EQ(0, strcmp("V8.Test", last_event_message));
|
|
CHECK_EQ(v8::LogEventStatus::kEnd, last_event_status);
|
|
CHECK_EQ(++count, event_count);
|
|
v8::internal::PauseNestedTimedHistogramScope scope6(&histogram);
|
|
// The outer timer scope is just paused, no event is emited yet.
|
|
CHECK_EQ(count, event_count);
|
|
{
|
|
v8::internal::PauseNestedTimedHistogramScope scope7(&histogram);
|
|
CHECK_EQ(count, event_count);
|
|
}
|
|
CHECK_EQ(count, event_count);
|
|
}
|
|
CHECK_EQ(0, strcmp("V8.Test", last_event_message));
|
|
CHECK_EQ(v8::LogEventStatus::kEnd, last_event_status);
|
|
CHECK_EQ(++count, event_count);
|
|
}
|
|
CHECK_EQ(0, strcmp("V8.Test", last_event_message));
|
|
CHECK_EQ(v8::LogEventStatus::kEnd, last_event_status);
|
|
CHECK_EQ(++count, event_count);
|
|
}
|
|
|
|
TEST(PropertyDescriptor) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
{ // empty descriptor
|
|
v8::PropertyDescriptor desc;
|
|
CHECK(!desc.has_value());
|
|
CHECK(!desc.has_set());
|
|
CHECK(!desc.has_get());
|
|
CHECK(!desc.has_enumerable());
|
|
CHECK(!desc.has_configurable());
|
|
CHECK(!desc.has_writable());
|
|
}
|
|
{
|
|
// data descriptor
|
|
v8::PropertyDescriptor desc(v8_num(42));
|
|
desc.set_enumerable(false);
|
|
CHECK(desc.value() == v8_num(42));
|
|
CHECK(desc.has_value());
|
|
CHECK(!desc.has_set());
|
|
CHECK(!desc.has_get());
|
|
CHECK(desc.has_enumerable());
|
|
CHECK(!desc.enumerable());
|
|
CHECK(!desc.has_configurable());
|
|
CHECK(!desc.has_writable());
|
|
}
|
|
{
|
|
// data descriptor
|
|
v8::PropertyDescriptor desc(v8_num(42));
|
|
desc.set_configurable(true);
|
|
CHECK(desc.value() == v8_num(42));
|
|
CHECK(desc.has_value());
|
|
CHECK(!desc.has_set());
|
|
CHECK(!desc.has_get());
|
|
CHECK(desc.has_configurable());
|
|
CHECK(desc.configurable());
|
|
CHECK(!desc.has_enumerable());
|
|
CHECK(!desc.has_writable());
|
|
}
|
|
{
|
|
// data descriptor
|
|
v8::PropertyDescriptor desc(v8_num(42));
|
|
desc.set_configurable(false);
|
|
CHECK(desc.value() == v8_num(42));
|
|
CHECK(desc.has_value());
|
|
CHECK(!desc.has_set());
|
|
CHECK(!desc.has_get());
|
|
CHECK(desc.has_configurable());
|
|
CHECK(!desc.configurable());
|
|
CHECK(!desc.has_enumerable());
|
|
CHECK(!desc.has_writable());
|
|
}
|
|
{
|
|
// data descriptor
|
|
v8::PropertyDescriptor desc(v8_num(42), false);
|
|
CHECK(desc.value() == v8_num(42));
|
|
CHECK(desc.has_value());
|
|
CHECK(!desc.has_set());
|
|
CHECK(!desc.has_get());
|
|
CHECK(!desc.has_enumerable());
|
|
CHECK(!desc.has_configurable());
|
|
CHECK(desc.has_writable());
|
|
CHECK(!desc.writable());
|
|
}
|
|
{
|
|
// data descriptor
|
|
v8::PropertyDescriptor desc(v8::Local<v8::Value>(), true);
|
|
CHECK(!desc.has_value());
|
|
CHECK(!desc.has_set());
|
|
CHECK(!desc.has_get());
|
|
CHECK(!desc.has_enumerable());
|
|
CHECK(!desc.has_configurable());
|
|
CHECK(desc.has_writable());
|
|
CHECK(desc.writable());
|
|
}
|
|
{
|
|
// accessor descriptor
|
|
CompileRun("var set = function() {return 43;};");
|
|
|
|
v8::Local<v8::Function> set =
|
|
v8::Local<v8::Function>::Cast(context->Global()
|
|
->Get(context.local(), v8_str("set"))
|
|
.ToLocalChecked());
|
|
v8::PropertyDescriptor desc(v8::Undefined(isolate), set);
|
|
desc.set_configurable(false);
|
|
CHECK(!desc.has_value());
|
|
CHECK(desc.has_get());
|
|
CHECK(desc.get() == v8::Undefined(isolate));
|
|
CHECK(desc.has_set());
|
|
CHECK(desc.set() == set);
|
|
CHECK(!desc.has_enumerable());
|
|
CHECK(desc.has_configurable());
|
|
CHECK(!desc.configurable());
|
|
CHECK(!desc.has_writable());
|
|
}
|
|
{
|
|
// accessor descriptor with Proxy
|
|
CompileRun(
|
|
"var set = new Proxy(function() {}, {});"
|
|
"var get = undefined;");
|
|
|
|
v8::Local<v8::Value> get =
|
|
v8::Local<v8::Value>::Cast(context->Global()
|
|
->Get(context.local(), v8_str("get"))
|
|
.ToLocalChecked());
|
|
v8::Local<v8::Function> set =
|
|
v8::Local<v8::Function>::Cast(context->Global()
|
|
->Get(context.local(), v8_str("set"))
|
|
.ToLocalChecked());
|
|
v8::PropertyDescriptor desc(get, set);
|
|
desc.set_configurable(false);
|
|
CHECK(!desc.has_value());
|
|
CHECK(desc.get() == v8::Undefined(isolate));
|
|
CHECK(desc.has_get());
|
|
CHECK(desc.set() == set);
|
|
CHECK(desc.has_set());
|
|
CHECK(!desc.has_enumerable());
|
|
CHECK(desc.has_configurable());
|
|
CHECK(!desc.configurable());
|
|
CHECK(!desc.has_writable());
|
|
}
|
|
{
|
|
// accessor descriptor with empty function handle
|
|
v8::Local<v8::Function> get = v8::Local<v8::Function>();
|
|
v8::PropertyDescriptor desc(get, get);
|
|
CHECK(!desc.has_value());
|
|
CHECK(!desc.has_get());
|
|
CHECK(!desc.has_set());
|
|
CHECK(!desc.has_enumerable());
|
|
CHECK(!desc.has_configurable());
|
|
CHECK(!desc.has_writable());
|
|
}
|
|
}
|
|
|
|
TEST(Promises) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Creation.
|
|
Local<v8::Promise::Resolver> pr =
|
|
v8::Promise::Resolver::New(context.local()).ToLocalChecked();
|
|
Local<v8::Promise::Resolver> rr =
|
|
v8::Promise::Resolver::New(context.local()).ToLocalChecked();
|
|
Local<v8::Promise> p = pr->GetPromise();
|
|
Local<v8::Promise> r = rr->GetPromise();
|
|
|
|
// IsPromise predicate.
|
|
CHECK(p->IsPromise());
|
|
CHECK(r->IsPromise());
|
|
Local<Value> o = v8::Object::New(isolate);
|
|
CHECK(!o->IsPromise());
|
|
|
|
// Resolution and rejection.
|
|
pr->Resolve(context.local(), v8::Integer::New(isolate, 1)).FromJust();
|
|
CHECK(p->IsPromise());
|
|
rr->Reject(context.local(), v8::Integer::New(isolate, 2)).FromJust();
|
|
CHECK(r->IsPromise());
|
|
}
|
|
|
|
// Promise.Then(on_fulfilled)
|
|
TEST(PromiseThen) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
|
|
v8::HandleScope scope(isolate);
|
|
Local<Object> global = context->Global();
|
|
|
|
// Creation.
|
|
Local<v8::Promise::Resolver> pr =
|
|
v8::Promise::Resolver::New(context.local()).ToLocalChecked();
|
|
Local<v8::Promise::Resolver> qr =
|
|
v8::Promise::Resolver::New(context.local()).ToLocalChecked();
|
|
Local<v8::Promise> p = pr->GetPromise();
|
|
Local<v8::Promise> q = qr->GetPromise();
|
|
|
|
CHECK(p->IsPromise());
|
|
CHECK(q->IsPromise());
|
|
|
|
pr->Resolve(context.local(), v8::Integer::New(isolate, 1)).FromJust();
|
|
qr->Resolve(context.local(), p).FromJust();
|
|
|
|
// Chaining non-pending promises.
|
|
CompileRun(
|
|
"var x1 = 0;\n"
|
|
"var x2 = 0;\n"
|
|
"function f1(x) { x1 = x; return x+1 };\n"
|
|
"function f2(x) { x2 = x; return x+1 };\n");
|
|
Local<Function> f1 = Local<Function>::Cast(
|
|
global->Get(context.local(), v8_str("f1")).ToLocalChecked());
|
|
Local<Function> f2 = Local<Function>::Cast(
|
|
global->Get(context.local(), v8_str("f2")).ToLocalChecked());
|
|
|
|
// Then
|
|
CompileRun("x1 = x2 = 0;");
|
|
q->Then(context.local(), f1).ToLocalChecked();
|
|
CHECK_EQ(0, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(1, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
// Then
|
|
CompileRun("x1 = x2 = 0;");
|
|
pr = v8::Promise::Resolver::New(context.local()).ToLocalChecked();
|
|
qr = v8::Promise::Resolver::New(context.local()).ToLocalChecked();
|
|
|
|
qr->Resolve(context.local(), pr).FromJust();
|
|
qr->GetPromise()
|
|
->Then(context.local(), f1)
|
|
.ToLocalChecked()
|
|
->Then(context.local(), f2)
|
|
.ToLocalChecked();
|
|
|
|
CHECK_EQ(0, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(0, global->Get(context.local(), v8_str("x2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(0, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(0, global->Get(context.local(), v8_str("x2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
pr->Resolve(context.local(), v8::Integer::New(isolate, 3)).FromJust();
|
|
|
|
CHECK_EQ(0, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(0, global->Get(context.local(), v8_str("x2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(3, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(4, global->Get(context.local(), v8_str("x2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
// Promise.Then(on_fulfilled, on_rejected)
|
|
TEST(PromiseThen2) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
|
|
v8::HandleScope scope(isolate);
|
|
Local<Object> global = context->Global();
|
|
|
|
// Creation.
|
|
Local<v8::Promise::Resolver> pr =
|
|
v8::Promise::Resolver::New(context.local()).ToLocalChecked();
|
|
Local<v8::Promise> p = pr->GetPromise();
|
|
|
|
CHECK(p->IsPromise());
|
|
|
|
pr->Resolve(context.local(), v8::Integer::New(isolate, 1)).FromJust();
|
|
|
|
// Chaining non-pending promises.
|
|
CompileRun(
|
|
"var x1 = 0;\n"
|
|
"var x2 = 0;\n"
|
|
"function f1(x) { x1 = x; return x+1 };\n"
|
|
"function f2(x) { x2 = x; return x+1 };\n"
|
|
"function f3(x) { throw x + 100 };\n");
|
|
Local<Function> f1 = Local<Function>::Cast(
|
|
global->Get(context.local(), v8_str("f1")).ToLocalChecked());
|
|
Local<Function> f2 = Local<Function>::Cast(
|
|
global->Get(context.local(), v8_str("f2")).ToLocalChecked());
|
|
Local<Function> f3 = Local<Function>::Cast(
|
|
global->Get(context.local(), v8_str("f3")).ToLocalChecked());
|
|
|
|
// Then
|
|
CompileRun("x1 = x2 = 0;");
|
|
Local<v8::Promise> a = p->Then(context.local(), f1, f2).ToLocalChecked();
|
|
CHECK_EQ(0, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(1, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(0, global->Get(context.local(), v8_str("x2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
Local<v8::Promise> b = a->Then(context.local(), f3, f2).ToLocalChecked();
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(1, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(0, global->Get(context.local(), v8_str("x2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
Local<v8::Promise> c = b->Then(context.local(), f1, f2).ToLocalChecked();
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(1, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(102, global->Get(context.local(), v8_str("x2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
v8::Local<v8::Promise> d = c->Then(context.local(), f1, f2).ToLocalChecked();
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(103, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(102, global->Get(context.local(), v8_str("x2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
v8::Local<v8::Promise> e = d->Then(context.local(), f3, f2).ToLocalChecked();
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(103, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(102, global->Get(context.local(), v8_str("x2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
v8::Local<v8::Promise> f = e->Then(context.local(), f1, f3).ToLocalChecked();
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(103, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(102, global->Get(context.local(), v8_str("x2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
|
|
f->Then(context.local(), f1, f2).ToLocalChecked();
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(103, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
CHECK_EQ(304, global->Get(context.local(), v8_str("x2"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
TEST(PromiseCatchCallsBuiltin) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<Object> global = context->Global();
|
|
|
|
v8::Local<v8::Promise::Resolver> resolver =
|
|
v8::Promise::Resolver::New(context.local()).ToLocalChecked();
|
|
v8::Local<v8::Promise> promise = resolver->GetPromise();
|
|
|
|
resolver->Reject(context.local(), v8::Integer::New(isolate, 1)).FromJust();
|
|
|
|
CompileRun(
|
|
"var x1 = 0;\n"
|
|
"function f(x) { x1 = x; }\n"
|
|
"Promise.prototype.then = function () { throw 'unreachable'; };\n");
|
|
Local<Function> f = Local<Function>::Cast(
|
|
global->Get(context.local(), v8_str("f")).ToLocalChecked());
|
|
|
|
// Catch should not call monkey-patched Promise.prototype.then.
|
|
promise->Catch(context.local(), f).ToLocalChecked();
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK_EQ(1, global->Get(context.local(), v8_str("x1"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context.local())
|
|
.FromJust());
|
|
}
|
|
|
|
TEST(PromiseStateAndValue) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Value> result = CompileRun(
|
|
"var resolver;"
|
|
"new Promise((res, rej) => { resolver = res; })");
|
|
v8::Local<v8::Promise> promise = v8::Local<v8::Promise>::Cast(result);
|
|
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kPending);
|
|
|
|
CompileRun("resolver('fulfilled')");
|
|
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kFulfilled);
|
|
CHECK(v8_str("fulfilled")->SameValue(promise->Result()));
|
|
|
|
result = CompileRun("Promise.reject('rejected')");
|
|
promise = v8::Local<v8::Promise>::Cast(result);
|
|
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kRejected);
|
|
CHECK(v8_str("rejected")->SameValue(promise->Result()));
|
|
}
|
|
|
|
TEST(ResolvedPromiseReFulfill) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::String> value1 = v8::String::NewFromUtf8Literal(isolate, "foo");
|
|
v8::Local<v8::String> value2 = v8::String::NewFromUtf8Literal(isolate, "bar");
|
|
|
|
v8::Local<v8::Promise::Resolver> resolver =
|
|
v8::Promise::Resolver::New(context.local()).ToLocalChecked();
|
|
v8::Local<v8::Promise> promise = resolver->GetPromise();
|
|
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kPending);
|
|
|
|
resolver->Resolve(context.local(), value1).ToChecked();
|
|
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kFulfilled);
|
|
CHECK_EQ(promise->Result(), value1);
|
|
|
|
// This should be a no-op.
|
|
resolver->Resolve(context.local(), value2).ToChecked();
|
|
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kFulfilled);
|
|
CHECK_EQ(promise->Result(), value1);
|
|
|
|
// This should be a no-op.
|
|
resolver->Reject(context.local(), value2).ToChecked();
|
|
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kFulfilled);
|
|
CHECK_EQ(promise->Result(), value1);
|
|
}
|
|
|
|
TEST(RejectedPromiseReFulfill) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::String> value1 = v8::String::NewFromUtf8Literal(isolate, "foo");
|
|
v8::Local<v8::String> value2 = v8::String::NewFromUtf8Literal(isolate, "bar");
|
|
|
|
v8::Local<v8::Promise::Resolver> resolver =
|
|
v8::Promise::Resolver::New(context.local()).ToLocalChecked();
|
|
v8::Local<v8::Promise> promise = resolver->GetPromise();
|
|
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kPending);
|
|
|
|
resolver->Reject(context.local(), value1).ToChecked();
|
|
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kRejected);
|
|
CHECK_EQ(promise->Result(), value1);
|
|
|
|
// This should be a no-op.
|
|
resolver->Reject(context.local(), value2).ToChecked();
|
|
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kRejected);
|
|
CHECK_EQ(promise->Result(), value1);
|
|
|
|
// This should be a no-op.
|
|
resolver->Resolve(context.local(), value2).ToChecked();
|
|
CHECK_EQ(promise->State(), v8::Promise::PromiseState::kRejected);
|
|
CHECK_EQ(promise->Result(), value1);
|
|
}
|
|
|
|
TEST(DisallowJavascriptExecutionScope) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Isolate::DisallowJavascriptExecutionScope no_js(
|
|
isolate, v8::Isolate::DisallowJavascriptExecutionScope::CRASH_ON_FAILURE);
|
|
CompileRun("2+2");
|
|
}
|
|
|
|
TEST(AllowJavascriptExecutionScope) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Isolate::DisallowJavascriptExecutionScope no_js(
|
|
isolate, v8::Isolate::DisallowJavascriptExecutionScope::CRASH_ON_FAILURE);
|
|
v8::Isolate::DisallowJavascriptExecutionScope throw_js(
|
|
isolate, v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
|
|
{ v8::Isolate::AllowJavascriptExecutionScope yes_js(isolate);
|
|
CompileRun("1+1");
|
|
}
|
|
}
|
|
|
|
TEST(ThrowOnJavascriptExecution) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
v8::Isolate::DisallowJavascriptExecutionScope throw_js(
|
|
isolate, v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
|
|
CompileRun("1+1");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
namespace {
|
|
|
|
class MockPlatform : public TestPlatform {
|
|
public:
|
|
MockPlatform() : old_platform_(i::V8::GetCurrentPlatform()) {
|
|
// Now that it's completely constructed, make this the current platform.
|
|
i::V8::SetPlatformForTesting(this);
|
|
}
|
|
~MockPlatform() override { i::V8::SetPlatformForTesting(old_platform_); }
|
|
|
|
bool dump_without_crashing_called() const {
|
|
return dump_without_crashing_called_;
|
|
}
|
|
|
|
void DumpWithoutCrashing() override { dump_without_crashing_called_ = true; }
|
|
|
|
private:
|
|
v8::Platform* old_platform_;
|
|
bool dump_without_crashing_called_ = false;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(DumpOnJavascriptExecution) {
|
|
MockPlatform platform;
|
|
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Isolate::DisallowJavascriptExecutionScope throw_js(
|
|
isolate, v8::Isolate::DisallowJavascriptExecutionScope::DUMP_ON_FAILURE);
|
|
CHECK(!platform.dump_without_crashing_called());
|
|
CompileRun("1+1");
|
|
CHECK(platform.dump_without_crashing_called());
|
|
}
|
|
|
|
TEST(Regress354123) {
|
|
LocalContext current;
|
|
v8::Isolate* isolate = current->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate);
|
|
templ->SetAccessCheckCallback(AccessCounter);
|
|
CHECK(current->Global()
|
|
->Set(current.local(), v8_str("friend"),
|
|
templ->NewInstance(current.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// Test access using __proto__ from the prototype chain.
|
|
access_count = 0;
|
|
CompileRun("friend.__proto__ = {};");
|
|
CHECK_EQ(2, access_count);
|
|
CompileRun("friend.__proto__;");
|
|
CHECK_EQ(4, access_count);
|
|
|
|
// Test access using __proto__ as a hijacked function (A).
|
|
access_count = 0;
|
|
CompileRun("var p = Object.prototype;"
|
|
"var f = Object.getOwnPropertyDescriptor(p, '__proto__').set;"
|
|
"f.call(friend, {});");
|
|
CHECK_EQ(1, access_count);
|
|
CompileRun("var p = Object.prototype;"
|
|
"var f = Object.getOwnPropertyDescriptor(p, '__proto__').get;"
|
|
"f.call(friend);");
|
|
CHECK_EQ(2, access_count);
|
|
|
|
// Test access using __proto__ as a hijacked function (B).
|
|
access_count = 0;
|
|
CompileRun("var f = Object.prototype.__lookupSetter__('__proto__');"
|
|
"f.call(friend, {});");
|
|
CHECK_EQ(1, access_count);
|
|
CompileRun("var f = Object.prototype.__lookupGetter__('__proto__');"
|
|
"f.call(friend);");
|
|
CHECK_EQ(2, access_count);
|
|
|
|
// Test access using Object.setPrototypeOf reflective method.
|
|
access_count = 0;
|
|
CompileRun("Object.setPrototypeOf(friend, {});");
|
|
CHECK_EQ(1, access_count);
|
|
CompileRun("Object.getPrototypeOf(friend);");
|
|
CHECK_EQ(2, access_count);
|
|
}
|
|
|
|
|
|
namespace {
|
|
bool ValueEqualsString(v8::Isolate* isolate, Local<Value> lhs,
|
|
const char* rhs) {
|
|
CHECK(!lhs.IsEmpty());
|
|
CHECK(lhs->IsString());
|
|
String::Utf8Value utf8_lhs(isolate, lhs);
|
|
return strcmp(rhs, *utf8_lhs) == 0;
|
|
}
|
|
} // namespace
|
|
|
|
TEST(ScriptNameAndLineNumber) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
const char* url = "http://www.foo.com/foo.js";
|
|
v8::ScriptOrigin origin(isolate, v8_str(url), 13, 0);
|
|
v8::ScriptCompiler::Source script_source(v8_str("var foo;"), origin);
|
|
|
|
Local<Script> script =
|
|
v8::ScriptCompiler::Compile(env.local(), &script_source).ToLocalChecked();
|
|
CHECK(ValueEqualsString(isolate, script->GetUnboundScript()->GetScriptName(),
|
|
url));
|
|
|
|
int line_number = script->GetUnboundScript()->GetLineNumber(0);
|
|
CHECK_EQ(13, line_number);
|
|
}
|
|
|
|
TEST(ScriptPositionInfo) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
const char* url = "http://www.foo.com/foo.js";
|
|
v8::ScriptOrigin origin(isolate, v8_str(url), 13, 0);
|
|
v8::ScriptCompiler::Source script_source(v8_str("var foo;\n"
|
|
"var bar;\n"
|
|
"var fisk = foo + bar;\n"),
|
|
origin);
|
|
Local<Script> script =
|
|
v8::ScriptCompiler::Compile(env.local(), &script_source).ToLocalChecked();
|
|
|
|
i::Handle<i::SharedFunctionInfo> obj = i::Handle<i::SharedFunctionInfo>::cast(
|
|
v8::Utils::OpenHandle(*script->GetUnboundScript()));
|
|
CHECK(obj->script().IsScript());
|
|
|
|
i::Handle<i::Script> script1(i::Script::cast(obj->script()), i_isolate);
|
|
|
|
v8::internal::Script::PositionInfo info;
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
// With offset.
|
|
|
|
// Behave as if 0 was passed if position is negative.
|
|
CHECK(script1->GetPositionInfo(-1, &info, script1->WITH_OFFSET));
|
|
CHECK_EQ(13, info.line);
|
|
CHECK_EQ(0, info.column);
|
|
CHECK_EQ(0, info.line_start);
|
|
CHECK_EQ(8, info.line_end);
|
|
|
|
CHECK(script1->GetPositionInfo(0, &info, script1->WITH_OFFSET));
|
|
CHECK_EQ(13, info.line);
|
|
CHECK_EQ(0, info.column);
|
|
CHECK_EQ(0, info.line_start);
|
|
CHECK_EQ(8, info.line_end);
|
|
|
|
CHECK(script1->GetPositionInfo(8, &info, script1->WITH_OFFSET));
|
|
CHECK_EQ(13, info.line);
|
|
CHECK_EQ(8, info.column);
|
|
CHECK_EQ(0, info.line_start);
|
|
CHECK_EQ(8, info.line_end);
|
|
|
|
CHECK(script1->GetPositionInfo(9, &info, script1->WITH_OFFSET));
|
|
CHECK_EQ(14, info.line);
|
|
CHECK_EQ(0, info.column);
|
|
CHECK_EQ(9, info.line_start);
|
|
CHECK_EQ(17, info.line_end);
|
|
|
|
// Fail when position is larger than script size.
|
|
CHECK(!script1->GetPositionInfo(220384, &info, script1->WITH_OFFSET));
|
|
|
|
// Without offset.
|
|
|
|
// Behave as if 0 was passed if position is negative.
|
|
CHECK(script1->GetPositionInfo(-1, &info, script1->NO_OFFSET));
|
|
CHECK_EQ(0, info.line);
|
|
CHECK_EQ(0, info.column);
|
|
CHECK_EQ(0, info.line_start);
|
|
CHECK_EQ(8, info.line_end);
|
|
|
|
CHECK(script1->GetPositionInfo(0, &info, script1->NO_OFFSET));
|
|
CHECK_EQ(0, info.line);
|
|
CHECK_EQ(0, info.column);
|
|
CHECK_EQ(0, info.line_start);
|
|
CHECK_EQ(8, info.line_end);
|
|
|
|
CHECK(script1->GetPositionInfo(8, &info, script1->NO_OFFSET));
|
|
CHECK_EQ(0, info.line);
|
|
CHECK_EQ(8, info.column);
|
|
CHECK_EQ(0, info.line_start);
|
|
CHECK_EQ(8, info.line_end);
|
|
|
|
CHECK(script1->GetPositionInfo(9, &info, script1->NO_OFFSET));
|
|
CHECK_EQ(1, info.line);
|
|
CHECK_EQ(0, info.column);
|
|
CHECK_EQ(9, info.line_start);
|
|
CHECK_EQ(17, info.line_end);
|
|
|
|
// Fail when position is larger than script size.
|
|
CHECK(!script1->GetPositionInfo(220384, &info, script1->NO_OFFSET));
|
|
|
|
i::Script::InitLineEnds(i_isolate, script1);
|
|
}
|
|
}
|
|
|
|
void CheckMagicComments(v8::Isolate* isolate, Local<Script> script,
|
|
const char* expected_source_url,
|
|
const char* expected_source_mapping_url) {
|
|
if (expected_source_url != nullptr) {
|
|
v8::String::Utf8Value url(isolate,
|
|
script->GetUnboundScript()->GetSourceURL());
|
|
CHECK_EQ(0, strcmp(expected_source_url, *url));
|
|
} else {
|
|
CHECK(script->GetUnboundScript()->GetSourceURL()->IsUndefined());
|
|
}
|
|
if (expected_source_mapping_url != nullptr) {
|
|
v8::String::Utf8Value url(
|
|
isolate, script->GetUnboundScript()->GetSourceMappingURL());
|
|
CHECK_EQ(0, strcmp(expected_source_mapping_url, *url));
|
|
} else {
|
|
CHECK(script->GetUnboundScript()->GetSourceMappingURL()->IsUndefined());
|
|
}
|
|
}
|
|
|
|
void SourceURLHelper(v8::Isolate* isolate, const char* source,
|
|
const char* expected_source_url,
|
|
const char* expected_source_mapping_url) {
|
|
Local<Script> script = v8_compile(source);
|
|
CheckMagicComments(isolate, script, expected_source_url,
|
|
expected_source_mapping_url);
|
|
}
|
|
|
|
|
|
TEST(ScriptSourceURLAndSourceMappingURL) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceURL=bar1.js\n",
|
|
"bar1.js", nullptr);
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceMappingURL=bar2.js\n",
|
|
nullptr, "bar2.js");
|
|
|
|
// Both sourceURL and sourceMappingURL.
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceURL=bar3.js\n"
|
|
"//# sourceMappingURL=bar4.js\n",
|
|
"bar3.js", "bar4.js");
|
|
|
|
// Two source URLs; the first one is ignored.
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceURL=ignoreme.js\n"
|
|
"//# sourceURL=bar5.js\n",
|
|
"bar5.js", nullptr);
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceMappingURL=ignoreme.js\n"
|
|
"//# sourceMappingURL=bar6.js\n",
|
|
nullptr, "bar6.js");
|
|
|
|
// SourceURL or sourceMappingURL in the middle of the script.
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceURL=bar7.js\n"
|
|
"function baz() {}\n",
|
|
"bar7.js", nullptr);
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceMappingURL=bar8.js\n"
|
|
"function baz() {}\n",
|
|
nullptr, "bar8.js");
|
|
|
|
// Too much whitespace.
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceURL=bar9.js\n"
|
|
"//# sourceMappingURL=bar10.js\n",
|
|
nullptr, nullptr);
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceURL =bar11.js\n"
|
|
"//# sourceMappingURL =bar12.js\n",
|
|
nullptr, nullptr);
|
|
|
|
// Disallowed characters in value.
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceURL=bar13 .js \n"
|
|
"//# sourceMappingURL=bar14 .js \n",
|
|
nullptr, nullptr);
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceURL=bar15\t.js \n"
|
|
"//# sourceMappingURL=bar16\t.js \n",
|
|
nullptr, nullptr);
|
|
|
|
// Not too much whitespace.
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceURL= bar21.js \n"
|
|
"//# sourceMappingURL= bar22.js \n",
|
|
"bar21.js", "bar22.js");
|
|
|
|
// Comments in eval'd script should be ignored.
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"eval(\"\\\n//# sourceURL=bar23.js\");\n"
|
|
"eval(\"\\\n//# sourceMappingURL=bar24.js\");\n",
|
|
nullptr, nullptr);
|
|
SourceURLHelper(isolate,
|
|
"function foo() {}\n"
|
|
"eval('\\\n//# sourceURL=bar23.js');\n"
|
|
"eval('\\\n//# sourceMappingURL=bar24.js');\n",
|
|
nullptr, nullptr);
|
|
|
|
// Inline data: URLs are allowed.
|
|
SourceURLHelper(
|
|
isolate,
|
|
"function foo() {}\n"
|
|
"//# sourceMappingURL= data:application/json,{\"version\":3} \n",
|
|
nullptr, "data:application/json,{\"version\":3}");
|
|
}
|
|
|
|
|
|
TEST(GetOwnPropertyDescriptor) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
CompileRun(
|
|
"var x = { value : 13};"
|
|
"Object.defineProperty(x, 'p0', {value : 12});"
|
|
"Object.defineProperty(x, Symbol.toStringTag, {value: 'foo'});"
|
|
"Object.defineProperty(x, 'p1', {"
|
|
" set : function(value) { this.value = value; },"
|
|
" get : function() { return this.value; },"
|
|
"});");
|
|
Local<Object> x = Local<Object>::Cast(
|
|
env->Global()->Get(env.local(), v8_str("x")).ToLocalChecked());
|
|
Local<Value> desc =
|
|
x->GetOwnPropertyDescriptor(env.local(), v8_str("no_prop"))
|
|
.ToLocalChecked();
|
|
CHECK(desc->IsUndefined());
|
|
desc =
|
|
x->GetOwnPropertyDescriptor(env.local(), v8_str("p0")).ToLocalChecked();
|
|
CHECK(v8_num(12)
|
|
->Equals(env.local(), Local<Object>::Cast(desc)
|
|
->Get(env.local(), v8_str("value"))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
desc =
|
|
x->GetOwnPropertyDescriptor(env.local(), v8_str("p1")).ToLocalChecked();
|
|
Local<Function> set =
|
|
Local<Function>::Cast(Local<Object>::Cast(desc)
|
|
->Get(env.local(), v8_str("set"))
|
|
.ToLocalChecked());
|
|
Local<Function> get =
|
|
Local<Function>::Cast(Local<Object>::Cast(desc)
|
|
->Get(env.local(), v8_str("get"))
|
|
.ToLocalChecked());
|
|
CHECK(v8_num(13)
|
|
->Equals(env.local(),
|
|
get->Call(env.local(), x, 0, nullptr).ToLocalChecked())
|
|
.FromJust());
|
|
Local<Value> args[] = {v8_num(14)};
|
|
set->Call(env.local(), x, 1, args).ToLocalChecked();
|
|
CHECK(v8_num(14)
|
|
->Equals(env.local(),
|
|
get->Call(env.local(), x, 0, nullptr).ToLocalChecked())
|
|
.FromJust());
|
|
desc =
|
|
x->GetOwnPropertyDescriptor(env.local(), Symbol::GetToStringTag(isolate))
|
|
.ToLocalChecked();
|
|
CHECK(v8_str("foo")
|
|
->Equals(env.local(), Local<Object>::Cast(desc)
|
|
->Get(env.local(), v8_str("value"))
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
|
|
|
|
TEST(Regress411877) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> object_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
object_template->SetAccessCheckCallback(AccessCounter);
|
|
|
|
v8::Local<Context> context = Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
CHECK(context->Global()
|
|
->Set(context, v8_str("o"),
|
|
object_template->NewInstance(context).ToLocalChecked())
|
|
.FromJust());
|
|
CompileRun("Object.getOwnPropertyNames(o)");
|
|
}
|
|
|
|
|
|
TEST(GetHiddenPropertyTableAfterAccessCheck) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> object_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
object_template->SetAccessCheckCallback(AccessCounter);
|
|
|
|
v8::Local<Context> context = Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
v8::Local<v8::Object> obj =
|
|
object_template->NewInstance(context).ToLocalChecked();
|
|
obj->Set(context, v8_str("key"), v8_str("value")).FromJust();
|
|
obj->Delete(context, v8_str("key")).FromJust();
|
|
|
|
obj->SetPrivate(context, v8::Private::New(isolate, v8_str("hidden key 2")),
|
|
v8_str("hidden value 2"))
|
|
.FromJust();
|
|
}
|
|
|
|
|
|
TEST(Regress411793) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> object_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
object_template->SetAccessCheckCallback(AccessCounter);
|
|
|
|
v8::Local<Context> context = Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
CHECK(context->Global()
|
|
->Set(context, v8_str("o"),
|
|
object_template->NewInstance(context).ToLocalChecked())
|
|
.FromJust());
|
|
CompileRun(
|
|
"Object.defineProperty(o, 'key', "
|
|
" { get: function() {}, set: function() {} });");
|
|
}
|
|
|
|
class TestSourceStream : public v8::ScriptCompiler::ExternalSourceStream {
|
|
public:
|
|
explicit TestSourceStream(const char** chunks) : chunks_(chunks), index_(0) {}
|
|
|
|
size_t GetMoreData(const uint8_t** src) override {
|
|
// Unlike in real use cases, this function will never block.
|
|
if (chunks_[index_] == nullptr) {
|
|
return 0;
|
|
}
|
|
// Copy the data, since the caller takes ownership of it.
|
|
size_t len = strlen(chunks_[index_]);
|
|
// We don't need to zero-terminate since we return the length.
|
|
uint8_t* copy = new uint8_t[len];
|
|
memcpy(copy, chunks_[index_], len);
|
|
*src = copy;
|
|
++index_;
|
|
return len;
|
|
}
|
|
|
|
// Helper for constructing a string from chunks (the compilation needs it
|
|
// too).
|
|
static char* FullSourceString(const char** chunks) {
|
|
size_t total_len = 0;
|
|
for (size_t i = 0; chunks[i] != nullptr; ++i) {
|
|
total_len += strlen(chunks[i]);
|
|
}
|
|
char* full_string = new char[total_len + 1];
|
|
size_t offset = 0;
|
|
for (size_t i = 0; chunks[i] != nullptr; ++i) {
|
|
size_t len = strlen(chunks[i]);
|
|
memcpy(full_string + offset, chunks[i], len);
|
|
offset += len;
|
|
}
|
|
full_string[total_len] = 0;
|
|
return full_string;
|
|
}
|
|
|
|
private:
|
|
const char** chunks_;
|
|
unsigned index_;
|
|
};
|
|
|
|
v8::MaybeLocal<Module> UnexpectedModuleResolveCallback(
|
|
Local<Context> context, Local<String> specifier,
|
|
Local<FixedArray> import_assertions, Local<Module> referrer) {
|
|
CHECK_WITH_MSG(false, "Unexpected call to resolve callback");
|
|
}
|
|
|
|
// Helper function for running streaming tests.
|
|
void RunStreamingTest(const char** chunks, v8::ScriptType type,
|
|
v8::ScriptCompiler::StreamedSource::Encoding encoding =
|
|
v8::ScriptCompiler::StreamedSource::ONE_BYTE,
|
|
bool expected_success = true,
|
|
const char* expected_source_url = nullptr,
|
|
const char* expected_source_mapping_url = nullptr) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
v8::ScriptCompiler::StreamedSource source(
|
|
std::make_unique<TestSourceStream>(chunks), encoding);
|
|
v8::ScriptCompiler::ScriptStreamingTask* task =
|
|
v8::ScriptCompiler::StartStreaming(isolate, &source, type);
|
|
|
|
// TestSourceStream::GetMoreData won't block, so it's OK to just join the
|
|
// background task.
|
|
StreamerThread::StartThreadForTaskAndJoin(task);
|
|
delete task;
|
|
|
|
// Possible errors are only produced while compiling.
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
v8::ScriptOrigin origin(isolate, v8_str("http://foo.com"), 0, 0, false, -1,
|
|
v8::Local<v8::Value>(), false, false,
|
|
type == v8::ScriptType::kModule);
|
|
|
|
char* full_source = TestSourceStream::FullSourceString(chunks);
|
|
if (type == v8::ScriptType::kClassic) {
|
|
v8::MaybeLocal<Script> script = v8::ScriptCompiler::Compile(
|
|
env.local(), &source, v8_str(full_source), origin);
|
|
if (expected_success) {
|
|
CHECK(!script.IsEmpty());
|
|
v8::Local<Value> result(
|
|
script.ToLocalChecked()->Run(env.local()).ToLocalChecked());
|
|
// All scripts are supposed to return the fixed value 13 when ran.
|
|
CHECK_EQ(13, result->Int32Value(env.local()).FromJust());
|
|
CheckMagicComments(isolate, script.ToLocalChecked(), expected_source_url,
|
|
expected_source_mapping_url);
|
|
} else {
|
|
CHECK(script.IsEmpty());
|
|
}
|
|
} else {
|
|
v8::MaybeLocal<Module> maybe_module = v8::ScriptCompiler::CompileModule(
|
|
env.local(), &source, v8_str(full_source), origin);
|
|
if (expected_success) {
|
|
v8::Local<v8::Module> module = maybe_module.ToLocalChecked();
|
|
CHECK(
|
|
module
|
|
->InstantiateModule(env.local(), UnexpectedModuleResolveCallback)
|
|
.FromJust());
|
|
CHECK_EQ(Module::kInstantiated, module->GetStatus());
|
|
v8::Local<Value> result = module->Evaluate(env.local()).ToLocalChecked();
|
|
CHECK_EQ(Module::kEvaluated, module->GetStatus());
|
|
v8::Local<v8::Promise> promise = result.As<v8::Promise>();
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
CHECK(promise->Result()->IsUndefined());
|
|
// Fulfilled top-level await promises always resolve to undefined. Check
|
|
// the test result via a global variable.
|
|
CHECK_EQ(13, env->Global()
|
|
->Get(env.local(), v8_str("Result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
} else {
|
|
CHECK(maybe_module.IsEmpty());
|
|
}
|
|
}
|
|
if (!expected_success) CHECK(try_catch.HasCaught());
|
|
delete[] full_source;
|
|
}
|
|
|
|
void RunStreamingTest(const char** chunks,
|
|
v8::ScriptCompiler::StreamedSource::Encoding encoding =
|
|
v8::ScriptCompiler::StreamedSource::ONE_BYTE,
|
|
bool expected_success = true,
|
|
const char* expected_source_url = nullptr,
|
|
const char* expected_source_mapping_url = nullptr) {
|
|
RunStreamingTest(chunks, v8::ScriptType::kClassic, encoding, expected_success,
|
|
expected_source_url, expected_source_mapping_url);
|
|
RunStreamingTest(chunks, v8::ScriptType::kModule, encoding, expected_success,
|
|
expected_source_url, expected_source_mapping_url);
|
|
}
|
|
|
|
TEST(StreamingSimpleScript) {
|
|
// This script is unrealistically small, since no one chunk is enough to fill
|
|
// the backing buffer of Scanner, let alone overflow it.
|
|
const char* chunks[] = {"function foo() { ret",
|
|
"urn 13; } globalThis.Result = f", "oo(); ", nullptr};
|
|
RunStreamingTest(chunks);
|
|
}
|
|
|
|
TEST(StreamingScriptConstantArray) {
|
|
// When run with Ignition, tests that the streaming parser canonicalizes
|
|
// handles so that they are only added to the constant pool array once.
|
|
const char* chunks[] = {"var a = {};",
|
|
"var b = {};",
|
|
"var c = 'testing';",
|
|
"var d = 'testing';",
|
|
"globalThis.Result = 13;",
|
|
nullptr};
|
|
RunStreamingTest(chunks);
|
|
}
|
|
|
|
TEST(StreamingScriptEvalShadowing) {
|
|
// When run with Ignition, tests that the streaming parser canonicalizes
|
|
// handles so the Variable::is_possibly_eval() is correct.
|
|
const char* chunk1 =
|
|
"(function() {\n"
|
|
" var y = 2;\n"
|
|
" return (function() {\n"
|
|
" eval('var y = 13;');\n"
|
|
" function g() {\n"
|
|
" return y\n"
|
|
" }\n"
|
|
" return (globalThis.Result = g());\n"
|
|
" })()\n"
|
|
"})()\n";
|
|
const char* chunks[] = {chunk1, nullptr};
|
|
// Only run the script version of this test.
|
|
RunStreamingTest(chunks, v8::ScriptType::kClassic);
|
|
}
|
|
|
|
TEST(StreamingBiggerScript) {
|
|
const char* chunk1 =
|
|
"function foo() {\n"
|
|
" // Make this chunk sufficiently long so that it will overflow the\n"
|
|
" // backing buffer of the Scanner.\n"
|
|
" var i = 0;\n"
|
|
" var result = 0;\n"
|
|
" for (i = 0; i < 13; ++i) { result = result + 1; }\n"
|
|
" result = 0;\n"
|
|
" for (i = 0; i < 13; ++i) { result = result + 1; }\n"
|
|
" result = 0;\n"
|
|
" for (i = 0; i < 13; ++i) { result = result + 1; }\n"
|
|
" result = 0;\n"
|
|
" for (i = 0; i < 13; ++i) { result = result + 1; }\n"
|
|
" return result;\n"
|
|
"}\n";
|
|
const char* chunks[] = {chunk1, "globalThis.Result = foo(); ", nullptr};
|
|
RunStreamingTest(chunks);
|
|
}
|
|
|
|
|
|
TEST(StreamingScriptWithParseError) {
|
|
// Test that parse errors from streamed scripts are propagated correctly.
|
|
{
|
|
char chunk1[] =
|
|
" // This will result in a parse error.\n"
|
|
" var if else then foo";
|
|
char chunk2[] = " 13\n";
|
|
const char* chunks[] = {chunk1, chunk2, "globalThis.Result = foo();",
|
|
nullptr};
|
|
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::ONE_BYTE,
|
|
false);
|
|
}
|
|
// Test that the next script succeeds normally.
|
|
{
|
|
char chunk1[] =
|
|
" // This will be parsed successfully.\n"
|
|
" function foo() { return ";
|
|
char chunk2[] = " 13; }\n";
|
|
const char* chunks[] = {chunk1, chunk2, "globalThis.Result = foo();",
|
|
nullptr};
|
|
|
|
RunStreamingTest(chunks);
|
|
}
|
|
}
|
|
|
|
|
|
TEST(StreamingUtf8Script) {
|
|
// We'd want to write \uc481 instead of \xec\x92\x81, but Windows compilers
|
|
// don't like it.
|
|
const char* chunk1 =
|
|
"function foo() {\n"
|
|
" // This function will contain an UTF-8 character which is not in\n"
|
|
" // ASCII.\n"
|
|
" var foob\xec\x92\x81r = 13;\n"
|
|
" return foob\xec\x92\x81r;\n"
|
|
"}\n";
|
|
const char* chunks[] = {chunk1, "globalThis.Result = foo(); ", nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8);
|
|
}
|
|
|
|
|
|
TEST(StreamingUtf8ScriptWithSplitCharactersSanityCheck) {
|
|
// A sanity check to prove that the approach of splitting UTF-8
|
|
// characters is correct. Here is an UTF-8 character which will take three
|
|
// bytes.
|
|
const char* reference = "\xec\x92\x81";
|
|
CHECK_EQ(3, strlen(reference));
|
|
|
|
char chunk1[] =
|
|
"function foo() {\n"
|
|
" // This function will contain an UTF-8 character which is not in\n"
|
|
" // ASCII.\n"
|
|
" var foob";
|
|
char chunk2[] =
|
|
"XXXr = 13;\n"
|
|
" return foob\xec\x92\x81r;\n"
|
|
"}\n";
|
|
for (int i = 0; i < 3; ++i) {
|
|
chunk2[i] = reference[i];
|
|
}
|
|
const char* chunks[] = {chunk1, chunk2, "globalThis.Result = foo();",
|
|
nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8);
|
|
}
|
|
|
|
|
|
TEST(StreamingUtf8ScriptWithSplitCharacters) {
|
|
// Stream data where a multi-byte UTF-8 character is split between two data
|
|
// chunks.
|
|
const char* reference = "\xec\x92\x81";
|
|
char chunk1[] =
|
|
"function foo() {\n"
|
|
" // This function will contain an UTF-8 character which is not in\n"
|
|
" // ASCII.\n"
|
|
" var foobX";
|
|
char chunk2[] =
|
|
"XXr = 13;\n"
|
|
" return foob\xec\x92\x81r;\n"
|
|
"}\n";
|
|
chunk1[strlen(chunk1) - 1] = reference[0];
|
|
chunk2[0] = reference[1];
|
|
chunk2[1] = reference[2];
|
|
const char* chunks[] = {chunk1, chunk2, "globalThis.Result = foo();",
|
|
nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8);
|
|
}
|
|
|
|
|
|
TEST(StreamingUtf8ScriptWithSplitCharactersValidEdgeCases) {
|
|
// Tests edge cases which should still be decoded correctly.
|
|
|
|
// Case 1: a chunk contains only bytes for a split character (and no other
|
|
// data). This kind of a chunk would be exceptionally small, but we should
|
|
// still decode it correctly.
|
|
const char* reference = "\xec\x92\x81";
|
|
// The small chunk is at the beginning of the split character
|
|
{
|
|
char chunk1[] =
|
|
"function foo() {\n"
|
|
" // This function will contain an UTF-8 character which is not in\n"
|
|
" // ASCII.\n"
|
|
" var foob";
|
|
char chunk2[] = "XX";
|
|
char chunk3[] =
|
|
"Xr = 13;\n"
|
|
" return foob\xec\x92\x81r;\n"
|
|
"}\n";
|
|
chunk2[0] = reference[0];
|
|
chunk2[1] = reference[1];
|
|
chunk3[0] = reference[2];
|
|
const char* chunks[] = {chunk1, chunk2, chunk3,
|
|
"globalThis.Result = foo();", nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8);
|
|
}
|
|
// The small chunk is at the end of a character
|
|
{
|
|
char chunk1[] =
|
|
"function foo() {\n"
|
|
" // This function will contain an UTF-8 character which is not in\n"
|
|
" // ASCII.\n"
|
|
" var foobX";
|
|
char chunk2[] = "XX";
|
|
char chunk3[] =
|
|
"r = 13;\n"
|
|
" return foob\xec\x92\x81r;\n"
|
|
"}\n";
|
|
chunk1[strlen(chunk1) - 1] = reference[0];
|
|
chunk2[0] = reference[1];
|
|
chunk2[1] = reference[2];
|
|
const char* chunks[] = {chunk1, chunk2, chunk3,
|
|
"globalThis.Result = foo();", nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8);
|
|
}
|
|
// Case 2: the script ends with a multi-byte character. Make sure that it's
|
|
// decoded correctly and not just ignored.
|
|
{
|
|
char chunk1[] =
|
|
"var foob\xec\x92\x81 = 13;\n"
|
|
"globalThis.Result = foob\xec\x92\x81";
|
|
const char* chunks[] = {chunk1, nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8);
|
|
}
|
|
}
|
|
|
|
|
|
TEST(StreamingUtf8ScriptWithSplitCharactersInvalidEdgeCases) {
|
|
// Test cases where a UTF-8 character is split over several chunks. Those
|
|
// cases are not supported (the embedder should give the data in big enough
|
|
// chunks), but we shouldn't crash and parse this just fine.
|
|
const char* reference = "\xec\x92\x81";
|
|
char chunk1[] =
|
|
"function foo() {\n"
|
|
" // This function will contain an UTF-8 character which is not in\n"
|
|
" // ASCII.\n"
|
|
" var foobX";
|
|
char chunk2[] = "X";
|
|
char chunk3[] =
|
|
"Xr = 13;\n"
|
|
" return foob\xec\x92\x81r;\n"
|
|
"}\n";
|
|
chunk1[strlen(chunk1) - 1] = reference[0];
|
|
chunk2[0] = reference[1];
|
|
chunk3[0] = reference[2];
|
|
const char* chunks[] = {chunk1, chunk2, chunk3, "globalThis.Result = foo();",
|
|
nullptr};
|
|
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8);
|
|
}
|
|
|
|
|
|
|
|
TEST(StreamingWithDebuggingEnabledLate) {
|
|
// The streaming parser can only parse lazily, i.e. inner functions are not
|
|
// fully parsed. However, we may compile inner functions eagerly when
|
|
// debugging. Make sure that we can deal with this when turning on debugging
|
|
// after streaming parser has already finished parsing.
|
|
const char* chunks[] = {"with({x:1}) {",
|
|
" var foo = function foo(y) {",
|
|
" return x + y;",
|
|
" };",
|
|
" foo(2);",
|
|
"}",
|
|
nullptr};
|
|
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
v8::ScriptCompiler::StreamedSource source(
|
|
std::make_unique<TestSourceStream>(chunks),
|
|
v8::ScriptCompiler::StreamedSource::ONE_BYTE);
|
|
v8::ScriptCompiler::ScriptStreamingTask* task =
|
|
v8::ScriptCompiler::StartStreaming(isolate, &source);
|
|
|
|
// TestSourceStream::GetMoreData won't block, so it's OK to just join the
|
|
// background task.
|
|
StreamerThread::StartThreadForTaskAndJoin(task);
|
|
delete task;
|
|
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
v8::ScriptOrigin origin(isolate, v8_str("http://foo.com"));
|
|
char* full_source = TestSourceStream::FullSourceString(chunks);
|
|
|
|
EnableDebugger(isolate);
|
|
|
|
v8::Local<Script> script =
|
|
v8::ScriptCompiler::Compile(env.local(), &source, v8_str(full_source),
|
|
origin)
|
|
.ToLocalChecked();
|
|
|
|
Maybe<uint32_t> result =
|
|
script->Run(env.local()).ToLocalChecked()->Uint32Value(env.local());
|
|
CHECK_EQ(3U, result.FromMaybe(0));
|
|
|
|
delete[] full_source;
|
|
|
|
DisableDebugger(isolate);
|
|
}
|
|
|
|
|
|
TEST(StreamingScriptWithInvalidUtf8) {
|
|
// Regression test for a crash: test that invalid UTF-8 bytes in the end of a
|
|
// chunk don't produce a crash.
|
|
const char* reference = "\xec\x92\x81\x80\x80";
|
|
char chunk1[] =
|
|
"function foo() {\n"
|
|
" // This function will contain an UTF-8 character which is not in\n"
|
|
" // ASCII.\n"
|
|
" var foobXXXXX"; // Too many bytes which look like incomplete chars!
|
|
char chunk2[] =
|
|
"r = 13;\n"
|
|
" return foob\xec\x92\x81\x80\x80r;\n"
|
|
"}\n";
|
|
for (int i = 0; i < 5; ++i) chunk1[strlen(chunk1) - 5 + i] = reference[i];
|
|
|
|
const char* chunks[] = {chunk1, chunk2, "globalThis.Result = foo();",
|
|
nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8, false);
|
|
}
|
|
|
|
|
|
TEST(StreamingUtf8ScriptWithMultipleMultibyteCharactersSomeSplit) {
|
|
// Regression test: Stream data where there are several multi-byte UTF-8
|
|
// characters in a sequence and one of them is split between two data chunks.
|
|
const char* reference = "\xec\x92\x81";
|
|
char chunk1[] =
|
|
"function foo() {\n"
|
|
" // This function will contain an UTF-8 character which is not in\n"
|
|
" // ASCII.\n"
|
|
" var foob\xec\x92\x81X";
|
|
char chunk2[] =
|
|
"XXr = 13;\n"
|
|
" return foob\xec\x92\x81\xec\x92\x81r;\n"
|
|
"}\n";
|
|
chunk1[strlen(chunk1) - 1] = reference[0];
|
|
chunk2[0] = reference[1];
|
|
chunk2[1] = reference[2];
|
|
const char* chunks[] = {chunk1, chunk2, "globalThis.Result = foo();",
|
|
nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8);
|
|
}
|
|
|
|
|
|
TEST(StreamingUtf8ScriptWithMultipleMultibyteCharactersSomeSplit2) {
|
|
// Another regression test, similar to the previous one. The difference is
|
|
// that the split character is not the last one in the sequence.
|
|
const char* reference = "\xec\x92\x81";
|
|
char chunk1[] =
|
|
"function foo() {\n"
|
|
" // This function will contain an UTF-8 character which is not in\n"
|
|
" // ASCII.\n"
|
|
" var foobX";
|
|
char chunk2[] =
|
|
"XX\xec\x92\x81r = 13;\n"
|
|
" return foob\xec\x92\x81\xec\x92\x81r;\n"
|
|
"}\n";
|
|
chunk1[strlen(chunk1) - 1] = reference[0];
|
|
chunk2[0] = reference[1];
|
|
chunk2[1] = reference[2];
|
|
const char* chunks[] = {chunk1, chunk2, "globalThis.Result = foo();",
|
|
nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8);
|
|
}
|
|
|
|
|
|
TEST(StreamingWithHarmonyScopes) {
|
|
// Don't use RunStreamingTest here so that both scripts get to use the same
|
|
// LocalContext and HandleScope.
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// First, run a script with a let variable.
|
|
CompileRun("\"use strict\"; let x = 1;");
|
|
|
|
// Then stream a script which (erroneously) tries to introduce the same
|
|
// variable again.
|
|
const char* chunks[] = {"\"use strict\"; let x = 2;", nullptr};
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
v8::ScriptCompiler::StreamedSource source(
|
|
std::make_unique<TestSourceStream>(chunks),
|
|
v8::ScriptCompiler::StreamedSource::ONE_BYTE);
|
|
v8::ScriptCompiler::ScriptStreamingTask* task =
|
|
v8::ScriptCompiler::StartStreaming(isolate, &source);
|
|
|
|
// TestSourceStream::GetMoreData won't block, so it's OK to just join the
|
|
// background task.
|
|
StreamerThread::StartThreadForTaskAndJoin(task);
|
|
delete task;
|
|
|
|
// Parsing should succeed (the script will be parsed and compiled in a context
|
|
// independent way, so the error is not detected).
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
v8::ScriptOrigin origin(isolate, v8_str("http://foo.com"));
|
|
char* full_source = TestSourceStream::FullSourceString(chunks);
|
|
v8::Local<Script> script =
|
|
v8::ScriptCompiler::Compile(env.local(), &source, v8_str(full_source),
|
|
origin)
|
|
.ToLocalChecked();
|
|
CHECK(!script.IsEmpty());
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
// Running the script exposes the error.
|
|
CHECK(script->Run(env.local()).IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
delete[] full_source;
|
|
}
|
|
|
|
|
|
TEST(CodeCache) {
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
|
|
const char* source = "Math.sqrt(4)";
|
|
const char* origin = "code cache test";
|
|
v8::ScriptCompiler::CachedData* cache;
|
|
|
|
v8::Isolate* isolate1 = v8::Isolate::New(create_params);
|
|
{
|
|
v8::Isolate::Scope iscope(isolate1);
|
|
v8::HandleScope scope(isolate1);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate1);
|
|
v8::Context::Scope cscope(context);
|
|
v8::Local<v8::String> source_string = v8_str(source);
|
|
v8::ScriptOrigin script_origin(isolate1, v8_str(origin));
|
|
v8::ScriptCompiler::Source script_source(source_string, script_origin);
|
|
v8::ScriptCompiler::CompileOptions option =
|
|
v8::ScriptCompiler::kNoCompileOptions;
|
|
v8::Local<v8::Script> script =
|
|
v8::ScriptCompiler::Compile(context, &script_source, option)
|
|
.ToLocalChecked();
|
|
cache = v8::ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
|
|
}
|
|
isolate1->Dispose();
|
|
|
|
v8::Isolate* isolate2 = v8::Isolate::New(create_params);
|
|
{
|
|
v8::Isolate::Scope iscope(isolate2);
|
|
v8::HandleScope scope(isolate2);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate2);
|
|
v8::Context::Scope cscope(context);
|
|
v8::Local<v8::String> source_string = v8_str(source);
|
|
v8::ScriptOrigin script_origin(isolate2, v8_str(origin));
|
|
v8::ScriptCompiler::Source script_source(source_string, script_origin,
|
|
cache);
|
|
v8::ScriptCompiler::CompileOptions option =
|
|
v8::ScriptCompiler::kConsumeCodeCache;
|
|
v8::Local<v8::Script> script;
|
|
{
|
|
i::DisallowCompilation no_compile(
|
|
reinterpret_cast<i::Isolate*>(isolate2));
|
|
script = v8::ScriptCompiler::Compile(context, &script_source, option)
|
|
.ToLocalChecked();
|
|
}
|
|
CHECK_EQ(2, script->Run(context)
|
|
.ToLocalChecked()
|
|
->ToInt32(context)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
isolate2->Dispose();
|
|
}
|
|
|
|
v8::MaybeLocal<Value> UnexpectedSyntheticModuleEvaluationStepsCallback(
|
|
Local<Context> context, Local<Module> module) {
|
|
CHECK_WITH_MSG(false, "Unexpected call to synthetic module re callback");
|
|
}
|
|
|
|
static int synthetic_module_callback_count;
|
|
|
|
v8::MaybeLocal<Value> SyntheticModuleEvaluationStepsCallback(
|
|
Local<Context> context, Local<Module> module) {
|
|
synthetic_module_callback_count++;
|
|
return v8::Undefined(reinterpret_cast<v8::Isolate*>(context->GetIsolate()));
|
|
}
|
|
|
|
v8::MaybeLocal<Value> SyntheticModuleEvaluationStepsCallbackFail(
|
|
Local<Context> context, Local<Module> module) {
|
|
synthetic_module_callback_count++;
|
|
context->GetIsolate()->ThrowException(
|
|
v8_str("SyntheticModuleEvaluationStepsCallbackFail exception"));
|
|
return v8::MaybeLocal<Value>();
|
|
}
|
|
|
|
v8::MaybeLocal<Value> SyntheticModuleEvaluationStepsCallbackSetExport(
|
|
Local<Context> context, Local<Module> module) {
|
|
Maybe<bool> set_export_result = module->SetSyntheticModuleExport(
|
|
context->GetIsolate(), v8_str("test_export"), v8_num(42));
|
|
CHECK(set_export_result.FromJust());
|
|
return v8::Undefined(reinterpret_cast<v8::Isolate*>(context->GetIsolate()));
|
|
}
|
|
|
|
namespace {
|
|
|
|
Local<Module> CompileAndInstantiateModule(v8::Isolate* isolate,
|
|
Local<Context> context,
|
|
const char* resource_name,
|
|
const char* source) {
|
|
Local<String> source_string = v8_str(source);
|
|
v8::ScriptOrigin script_origin(isolate, v8_str(resource_name), 0, 0, false,
|
|
-1, Local<v8::Value>(), false, false, true);
|
|
v8::ScriptCompiler::Source script_compiler_source(source_string,
|
|
script_origin);
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &script_compiler_source)
|
|
.ToLocalChecked();
|
|
module->InstantiateModule(context, UnexpectedModuleResolveCallback)
|
|
.ToChecked();
|
|
|
|
return module;
|
|
}
|
|
|
|
Local<Module> CreateAndInstantiateSyntheticModule(
|
|
v8::Isolate* isolate, Local<String> module_name, Local<Context> context,
|
|
std::vector<v8::Local<v8::String>> export_names,
|
|
v8::Module::SyntheticModuleEvaluationSteps evaluation_steps) {
|
|
Local<Module> module = v8::Module::CreateSyntheticModule(
|
|
isolate, module_name, export_names, evaluation_steps);
|
|
module->InstantiateModule(context, UnexpectedModuleResolveCallback)
|
|
.ToChecked();
|
|
|
|
return module;
|
|
}
|
|
|
|
Local<Module> CompileAndInstantiateModuleFromCache(
|
|
v8::Isolate* isolate, Local<Context> context, const char* resource_name,
|
|
const char* source, v8::ScriptCompiler::CachedData* cache) {
|
|
Local<String> source_string = v8_str(source);
|
|
v8::ScriptOrigin script_origin(isolate, v8_str(resource_name), 0, 0, false,
|
|
-1, Local<v8::Value>(), false, false, true);
|
|
v8::ScriptCompiler::Source script_compiler_source(source_string,
|
|
script_origin, cache);
|
|
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &script_compiler_source,
|
|
v8::ScriptCompiler::kConsumeCodeCache)
|
|
.ToLocalChecked();
|
|
module->InstantiateModule(context, UnexpectedModuleResolveCallback)
|
|
.ToChecked();
|
|
|
|
return module;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
v8::MaybeLocal<Module> SyntheticModuleResolveCallback(
|
|
Local<Context> context, Local<String> specifier,
|
|
Local<FixedArray> import_assertions, Local<Module> referrer) {
|
|
std::vector<v8::Local<v8::String>> export_names{v8_str("test_export")};
|
|
Local<Module> module = CreateAndInstantiateSyntheticModule(
|
|
context->GetIsolate(),
|
|
v8_str("SyntheticModuleResolveCallback-TestSyntheticModule"), context,
|
|
export_names, SyntheticModuleEvaluationStepsCallbackSetExport);
|
|
return v8::MaybeLocal<Module>(module);
|
|
}
|
|
|
|
v8::MaybeLocal<Module> SyntheticModuleThatThrowsDuringEvaluateResolveCallback(
|
|
Local<Context> context, Local<String> specifier,
|
|
Local<FixedArray> import_assertions, Local<Module> referrer) {
|
|
std::vector<v8::Local<v8::String>> export_names{v8_str("test_export")};
|
|
Local<Module> module = CreateAndInstantiateSyntheticModule(
|
|
context->GetIsolate(),
|
|
v8_str("SyntheticModuleThatThrowsDuringEvaluateResolveCallback-"
|
|
"TestSyntheticModule"),
|
|
context, export_names, SyntheticModuleEvaluationStepsCallbackFail);
|
|
return v8::MaybeLocal<Module>(module);
|
|
}
|
|
|
|
TEST(ModuleCodeCache) {
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
|
|
const char* origin = "code cache test";
|
|
const char* source =
|
|
"export default 5; export const a = 10; function f() { return 42; } "
|
|
"(function() { globalThis.Result = f(); })();";
|
|
|
|
v8::ScriptCompiler::CachedData* cache;
|
|
{
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
{
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
Local<Module> module =
|
|
CompileAndInstantiateModule(isolate, context, origin, source);
|
|
|
|
// Fetch the shared function info before evaluation.
|
|
Local<v8::UnboundModuleScript> unbound_module_script =
|
|
module->GetUnboundModuleScript();
|
|
|
|
// Evaluate for possible lazy compilation.
|
|
Local<Value> completion_value =
|
|
module->Evaluate(context).ToLocalChecked();
|
|
Local<v8::Promise> promise(Local<v8::Promise>::Cast(completion_value));
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
CHECK(promise->Result()->IsUndefined());
|
|
CHECK_EQ(42, context->Global()
|
|
->Get(context, v8_str("Result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
|
|
// Now create the cache. Note that it is freed, obscurely, when
|
|
// ScriptCompiler::Source goes out of scope below.
|
|
cache = v8::ScriptCompiler::CreateCodeCache(unbound_module_script);
|
|
}
|
|
isolate->Dispose();
|
|
}
|
|
|
|
// Test that the cache is consumed and execution still works.
|
|
{
|
|
// Disable --always_opt, otherwise we try to optimize during module
|
|
// instantiation, violating the DisallowCompilation scope.
|
|
i::FLAG_always_opt = false;
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
{
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
Local<Module> module;
|
|
{
|
|
i::DisallowCompilation no_compile(
|
|
reinterpret_cast<i::Isolate*>(isolate));
|
|
module = CompileAndInstantiateModuleFromCache(isolate, context, origin,
|
|
source, cache);
|
|
}
|
|
|
|
Local<Value> completion_value =
|
|
module->Evaluate(context).ToLocalChecked();
|
|
Local<v8::Promise> promise(Local<v8::Promise>::Cast(completion_value));
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
CHECK(promise->Result()->IsUndefined());
|
|
CHECK_EQ(42, context->Global()
|
|
->Get(context, v8_str("Result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
isolate->Dispose();
|
|
}
|
|
}
|
|
|
|
TEST(CreateSyntheticModule) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
auto i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
std::vector<v8::Local<v8::String>> export_names{v8_str("default")};
|
|
|
|
Local<Module> module = CreateAndInstantiateSyntheticModule(
|
|
isolate, v8_str("CreateSyntheticModule-TestSyntheticModule"), context,
|
|
export_names, UnexpectedSyntheticModuleEvaluationStepsCallback);
|
|
i::Handle<i::SyntheticModule> i_module =
|
|
i::Handle<i::SyntheticModule>::cast(v8::Utils::OpenHandle(*module));
|
|
i::Handle<i::ObjectHashTable> exports(i_module->exports(), i_isolate);
|
|
i::Handle<i::String> default_name =
|
|
i_isolate->factory()->NewStringFromAsciiChecked("default");
|
|
|
|
CHECK(
|
|
i::Handle<i::Object>(exports->Lookup(default_name), i_isolate)->IsCell());
|
|
CHECK(i::Handle<i::Cell>::cast(
|
|
i::Handle<i::Object>(exports->Lookup(default_name), i_isolate))
|
|
->value()
|
|
.IsUndefined());
|
|
CHECK_EQ(i_module->export_names().length(), 1);
|
|
CHECK(i::String::cast(i_module->export_names().get(0)).Equals(*default_name));
|
|
CHECK_EQ(i_module->status(), i::Module::kLinked);
|
|
CHECK(module->IsSyntheticModule());
|
|
CHECK(!module->IsSourceTextModule());
|
|
CHECK_EQ(module->GetModuleRequests()->Length(), 0);
|
|
}
|
|
|
|
TEST(CreateSyntheticModuleGC) {
|
|
// Try to make sure that CreateSyntheticModule() deals well with a GC
|
|
// happening during its execution.
|
|
i::FLAG_gc_interval = 10;
|
|
i::FLAG_inline_new = false;
|
|
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
std::vector<v8::Local<v8::String>> export_names{v8_str("default")};
|
|
v8::Local<v8::String> module_name =
|
|
v8_str("CreateSyntheticModule-TestSyntheticModuleGC");
|
|
|
|
for (int i = 0; i < 200; i++) {
|
|
Local<Module> module = v8::Module::CreateSyntheticModule(
|
|
isolate, module_name, export_names,
|
|
UnexpectedSyntheticModuleEvaluationStepsCallback);
|
|
USE(module);
|
|
}
|
|
}
|
|
|
|
TEST(CreateSyntheticModuleGCName) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
Local<Module> module;
|
|
|
|
{
|
|
v8::EscapableHandleScope inner_scope(isolate);
|
|
std::vector<v8::Local<v8::String>> export_names{v8_str("default")};
|
|
v8::Local<v8::String> module_name =
|
|
v8_str("CreateSyntheticModuleGCName-TestSyntheticModule");
|
|
module = inner_scope.Escape(v8::Module::CreateSyntheticModule(
|
|
isolate, module_name, export_names,
|
|
UnexpectedSyntheticModuleEvaluationStepsCallback));
|
|
}
|
|
|
|
CcTest::CollectAllGarbage();
|
|
#ifdef VERIFY_HEAP
|
|
i::Handle<i::HeapObject> i_module =
|
|
i::Handle<i::HeapObject>::cast(v8::Utils::OpenHandle(*module));
|
|
i_module->HeapObjectVerify(reinterpret_cast<i::Isolate*>(isolate));
|
|
#endif
|
|
}
|
|
|
|
TEST(SyntheticModuleSetExports) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
auto i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
Local<String> foo_string = v8_str("foo");
|
|
Local<String> bar_string = v8_str("bar");
|
|
std::vector<v8::Local<v8::String>> export_names{foo_string};
|
|
|
|
Local<Module> module = CreateAndInstantiateSyntheticModule(
|
|
isolate, v8_str("SyntheticModuleSetExports-TestSyntheticModule"), context,
|
|
export_names, UnexpectedSyntheticModuleEvaluationStepsCallback);
|
|
|
|
i::Handle<i::SyntheticModule> i_module =
|
|
i::Handle<i::SyntheticModule>::cast(v8::Utils::OpenHandle(*module));
|
|
i::Handle<i::ObjectHashTable> exports(i_module->exports(), i_isolate);
|
|
|
|
i::Handle<i::Cell> foo_cell = i::Handle<i::Cell>::cast(i::Handle<i::Object>(
|
|
exports->Lookup(v8::Utils::OpenHandle(*foo_string)), i_isolate));
|
|
|
|
// During Instantiation there should be a Cell for the export initialized to
|
|
// undefined.
|
|
CHECK(foo_cell->value().IsUndefined());
|
|
|
|
Maybe<bool> set_export_result =
|
|
module->SetSyntheticModuleExport(isolate, foo_string, bar_string);
|
|
CHECK(set_export_result.FromJust());
|
|
|
|
// After setting the export the Cell should still have the same idenitity.
|
|
CHECK_EQ(exports->Lookup(v8::Utils::OpenHandle(*foo_string)), *foo_cell);
|
|
|
|
// Test that the export value was actually set.
|
|
CHECK(i::Handle<i::String>::cast(
|
|
i::Handle<i::Object>(foo_cell->value(), i_isolate))
|
|
->Equals(*v8::Utils::OpenHandle(*bar_string)));
|
|
}
|
|
|
|
TEST(SyntheticModuleSetMissingExport) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
auto i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
Local<String> foo_string = v8_str("foo");
|
|
Local<String> bar_string = v8_str("bar");
|
|
|
|
Local<Module> module = CreateAndInstantiateSyntheticModule(
|
|
isolate, v8_str("SyntheticModuleSetExports-TestSyntheticModule"), context,
|
|
std::vector<v8::Local<v8::String>>(),
|
|
UnexpectedSyntheticModuleEvaluationStepsCallback);
|
|
|
|
i::Handle<i::SyntheticModule> i_module =
|
|
i::Handle<i::SyntheticModule>::cast(v8::Utils::OpenHandle(*module));
|
|
i::Handle<i::ObjectHashTable> exports(i_module->exports(), i_isolate);
|
|
|
|
TryCatch try_catch(isolate);
|
|
Maybe<bool> set_export_result =
|
|
module->SetSyntheticModuleExport(isolate, foo_string, bar_string);
|
|
CHECK(set_export_result.IsNothing());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
TEST(SyntheticModuleEvaluationStepsNoThrow) {
|
|
synthetic_module_callback_count = 0;
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
std::vector<v8::Local<v8::String>> export_names{v8_str("default")};
|
|
|
|
Local<Module> module = CreateAndInstantiateSyntheticModule(
|
|
isolate,
|
|
v8_str("SyntheticModuleEvaluationStepsNoThrow-TestSyntheticModule"),
|
|
context, export_names, SyntheticModuleEvaluationStepsCallback);
|
|
CHECK_EQ(synthetic_module_callback_count, 0);
|
|
Local<Value> completion_value = module->Evaluate(context).ToLocalChecked();
|
|
CHECK(completion_value->IsUndefined());
|
|
CHECK_EQ(synthetic_module_callback_count, 1);
|
|
CHECK_EQ(module->GetStatus(), Module::kEvaluated);
|
|
}
|
|
|
|
TEST(SyntheticModuleEvaluationStepsThrow) {
|
|
synthetic_module_callback_count = 0;
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
|
|
v8::Context::Scope cscope(context);
|
|
|
|
std::vector<v8::Local<v8::String>> export_names{v8_str("default")};
|
|
|
|
Local<Module> module = CreateAndInstantiateSyntheticModule(
|
|
isolate,
|
|
v8_str("SyntheticModuleEvaluationStepsThrow-TestSyntheticModule"),
|
|
context, export_names, SyntheticModuleEvaluationStepsCallbackFail);
|
|
TryCatch try_catch(isolate);
|
|
CHECK_EQ(synthetic_module_callback_count, 0);
|
|
v8::MaybeLocal<Value> completion_value = module->Evaluate(context);
|
|
CHECK(completion_value.IsEmpty());
|
|
CHECK_EQ(synthetic_module_callback_count, 1);
|
|
CHECK_EQ(module->GetStatus(), Module::kErrored);
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
TEST(SyntheticModuleEvaluationStepsSetExport) {
|
|
synthetic_module_callback_count = 0;
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
auto i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
Local<String> test_export_string = v8_str("test_export");
|
|
std::vector<v8::Local<v8::String>> export_names{test_export_string};
|
|
|
|
Local<Module> module = CreateAndInstantiateSyntheticModule(
|
|
isolate,
|
|
v8_str("SyntheticModuleEvaluationStepsSetExport-TestSyntheticModule"),
|
|
context, export_names, SyntheticModuleEvaluationStepsCallbackSetExport);
|
|
|
|
i::Handle<i::SyntheticModule> i_module =
|
|
i::Handle<i::SyntheticModule>::cast(v8::Utils::OpenHandle(*module));
|
|
i::Handle<i::ObjectHashTable> exports(i_module->exports(), i_isolate);
|
|
|
|
i::Handle<i::Cell> test_export_cell =
|
|
i::Handle<i::Cell>::cast(i::Handle<i::Object>(
|
|
exports->Lookup(v8::Utils::OpenHandle(*test_export_string)),
|
|
i_isolate));
|
|
CHECK(test_export_cell->value().IsUndefined());
|
|
|
|
Local<Value> completion_value = module->Evaluate(context).ToLocalChecked();
|
|
CHECK(completion_value->IsUndefined());
|
|
CHECK_EQ(42, test_export_cell->value().Number());
|
|
CHECK_EQ(module->GetStatus(), Module::kEvaluated);
|
|
}
|
|
|
|
TEST(ImportFromSyntheticModule) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
Local<String> url = v8_str("www.test.com");
|
|
Local<String> source_text = v8_str(
|
|
"import {test_export} from './synthetic.module'; "
|
|
"(function() { globalThis.Result = test_export; })();");
|
|
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
|
|
false, false, true);
|
|
v8::ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
|
|
module->InstantiateModule(context, SyntheticModuleResolveCallback)
|
|
.ToChecked();
|
|
|
|
Local<Value> completion_value = module->Evaluate(context).ToLocalChecked();
|
|
Local<v8::Promise> promise(Local<v8::Promise>::Cast(completion_value));
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
CHECK(promise->Result()->IsUndefined());
|
|
CHECK_EQ(42, context->Global()
|
|
->Get(context, v8_str("Result"))
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
|
|
TEST(ImportFromSyntheticModuleThrow) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
Local<String> url = v8_str("www.test.com");
|
|
Local<String> source_text = v8_str(
|
|
"import {test_export} from './synthetic.module';"
|
|
"(function() { return test_export; })();");
|
|
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
|
|
false, false, true);
|
|
v8::ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
|
|
module
|
|
->InstantiateModule(
|
|
context, SyntheticModuleThatThrowsDuringEvaluateResolveCallback)
|
|
.ToChecked();
|
|
|
|
CHECK_EQ(module->GetStatus(), Module::kInstantiated);
|
|
TryCatch try_catch(isolate);
|
|
v8::MaybeLocal<Value> completion_value = module->Evaluate(context);
|
|
Local<v8::Promise> promise(
|
|
Local<v8::Promise>::Cast(completion_value.ToLocalChecked()));
|
|
CHECK_EQ(promise->State(), v8::Promise::kRejected);
|
|
CHECK_EQ(promise->Result(), try_catch.Exception());
|
|
|
|
CHECK_EQ(module->GetStatus(), Module::kErrored);
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
// Tests that the code cache does not confuse the same source code compiled as a
|
|
// script and as a module.
|
|
TEST(CodeCacheModuleScriptMismatch) {
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
|
|
const char* origin = "code cache test";
|
|
const char* source = "42";
|
|
|
|
v8::ScriptCompiler::CachedData* cache;
|
|
{
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
{
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
Local<Module> module =
|
|
CompileAndInstantiateModule(isolate, context, origin, source);
|
|
|
|
// Fetch the shared function info before evaluation.
|
|
Local<v8::UnboundModuleScript> unbound_module_script =
|
|
module->GetUnboundModuleScript();
|
|
|
|
// Evaluate for possible lazy compilation.
|
|
Local<Value> completion_value =
|
|
module->Evaluate(context).ToLocalChecked();
|
|
Local<v8::Promise> promise(Local<v8::Promise>::Cast(completion_value));
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
CHECK(promise->Result()->IsUndefined());
|
|
|
|
// Now create the cache. Note that it is freed, obscurely, when
|
|
// ScriptCompiler::Source goes out of scope below.
|
|
cache = v8::ScriptCompiler::CreateCodeCache(unbound_module_script);
|
|
}
|
|
isolate->Dispose();
|
|
}
|
|
|
|
// Test that the cache is not consumed when source is compiled as a script.
|
|
{
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
{
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
v8::ScriptOrigin script_origin(isolate, v8_str(origin));
|
|
v8::ScriptCompiler::Source script_compiler_source(v8_str(source),
|
|
script_origin, cache);
|
|
|
|
v8::Local<v8::Script> script =
|
|
v8::ScriptCompiler::Compile(context, &script_compiler_source,
|
|
v8::ScriptCompiler::kConsumeCodeCache)
|
|
.ToLocalChecked();
|
|
|
|
CHECK(cache->rejected);
|
|
|
|
CHECK_EQ(42, script->Run(context)
|
|
.ToLocalChecked()
|
|
->ToInt32(context)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
isolate->Dispose();
|
|
}
|
|
}
|
|
|
|
// Same as above but other way around.
|
|
TEST(CodeCacheScriptModuleMismatch) {
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
|
|
const char* origin = "code cache test";
|
|
const char* source = "42";
|
|
|
|
v8::ScriptCompiler::CachedData* cache;
|
|
{
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
{
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
v8::Local<v8::String> source_string = v8_str(source);
|
|
v8::ScriptOrigin script_origin(isolate, v8_str(origin));
|
|
v8::ScriptCompiler::Source script_source(source_string, script_origin);
|
|
v8::ScriptCompiler::CompileOptions option =
|
|
v8::ScriptCompiler::kNoCompileOptions;
|
|
v8::Local<v8::Script> script =
|
|
v8::ScriptCompiler::Compile(context, &script_source, option)
|
|
.ToLocalChecked();
|
|
cache = v8::ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
|
|
}
|
|
isolate->Dispose();
|
|
}
|
|
|
|
// Test that the cache is not consumed when source is compiled as a module.
|
|
{
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
{
|
|
v8::Isolate::Scope iscope(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope cscope(context);
|
|
|
|
v8::ScriptOrigin script_origin(isolate, v8_str(origin), 0, 0, false, -1,
|
|
Local<v8::Value>(), false, false, true);
|
|
v8::ScriptCompiler::Source script_compiler_source(v8_str(source),
|
|
script_origin, cache);
|
|
|
|
Local<Module> module = v8::ScriptCompiler::CompileModule(
|
|
isolate, &script_compiler_source,
|
|
v8::ScriptCompiler::kConsumeCodeCache)
|
|
.ToLocalChecked();
|
|
module->InstantiateModule(context, UnexpectedModuleResolveCallback)
|
|
.ToChecked();
|
|
|
|
CHECK(cache->rejected);
|
|
|
|
Local<Value> completion_value =
|
|
module->Evaluate(context).ToLocalChecked();
|
|
Local<v8::Promise> promise(Local<v8::Promise>::Cast(completion_value));
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
CHECK(promise->Result()->IsUndefined());
|
|
}
|
|
isolate->Dispose();
|
|
}
|
|
}
|
|
|
|
// Tests that compilation can handle a garbled cache.
|
|
TEST(InvalidCodeCacheDataInCompileModule) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext local_context;
|
|
|
|
const char* garbage = "garbage garbage garbage garbage garbage garbage";
|
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(garbage);
|
|
Local<String> origin = v8_str("origin");
|
|
int length = 16;
|
|
v8::ScriptCompiler::CachedData* cached_data =
|
|
new v8::ScriptCompiler::CachedData(data, length);
|
|
CHECK(!cached_data->rejected);
|
|
|
|
v8::ScriptOrigin script_origin(isolate, origin, 0, 0, false, -1,
|
|
Local<v8::Value>(), false, false, true);
|
|
v8::ScriptCompiler::Source source(v8_str("42"), script_origin, cached_data);
|
|
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
|
|
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &source,
|
|
v8::ScriptCompiler::kConsumeCodeCache)
|
|
.ToLocalChecked();
|
|
module->InstantiateModule(context, UnexpectedModuleResolveCallback)
|
|
.ToChecked();
|
|
|
|
CHECK(cached_data->rejected);
|
|
Local<Value> completion_value = module->Evaluate(context).ToLocalChecked();
|
|
Local<v8::Promise> promise(Local<v8::Promise>::Cast(completion_value));
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
CHECK(promise->Result()->IsUndefined());
|
|
}
|
|
|
|
void TestInvalidCacheData(v8::ScriptCompiler::CompileOptions option) {
|
|
const char* garbage = "garbage garbage garbage garbage garbage garbage";
|
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(garbage);
|
|
int length = 16;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::ScriptCompiler::CachedData* cached_data =
|
|
new v8::ScriptCompiler::CachedData(data, length);
|
|
CHECK(!cached_data->rejected);
|
|
v8::ScriptOrigin origin(isolate, v8_str("origin"));
|
|
v8::ScriptCompiler::Source source(v8_str("42"), origin, cached_data);
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
v8::Local<v8::Script> script =
|
|
v8::ScriptCompiler::Compile(context, &source, option).ToLocalChecked();
|
|
CHECK(cached_data->rejected);
|
|
CHECK_EQ(
|
|
42,
|
|
script->Run(context).ToLocalChecked()->Int32Value(context).FromJust());
|
|
}
|
|
|
|
TEST(InvalidCodeCacheData) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
LocalContext context;
|
|
TestInvalidCacheData(v8::ScriptCompiler::kConsumeCodeCache);
|
|
}
|
|
|
|
TEST(StringConcatOverflow) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
RandomLengthOneByteResource* r =
|
|
new RandomLengthOneByteResource(i::String::kMaxLength);
|
|
v8::Local<v8::String> str =
|
|
v8::String::NewExternalOneByte(isolate, r).ToLocalChecked();
|
|
CHECK(!str.IsEmpty());
|
|
v8::TryCatch try_catch(isolate);
|
|
v8::Local<v8::String> result = v8::String::Concat(isolate, str, str);
|
|
v8::String::Concat(CcTest::isolate(), str, str);
|
|
CHECK(result.IsEmpty());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
TEST(TurboAsmDisablesDetach) {
|
|
#ifndef V8_LITE_MODE
|
|
i::FLAG_opt = true;
|
|
i::FLAG_allow_natives_syntax = true;
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
LocalContext context;
|
|
const char* load =
|
|
"function Module(stdlib, foreign, heap) {"
|
|
" 'use asm';"
|
|
" var MEM32 = new stdlib.Int32Array(heap);"
|
|
" function load() { return MEM32[0] | 0; }"
|
|
" return { load: load };"
|
|
"}"
|
|
"var buffer = new ArrayBuffer(4096);"
|
|
"var module = Module(this, {}, buffer);"
|
|
"module.load();"
|
|
"buffer";
|
|
|
|
v8::Local<v8::ArrayBuffer> result = CompileRun(load).As<v8::ArrayBuffer>();
|
|
CHECK(!result->IsDetachable());
|
|
|
|
const char* store =
|
|
"function Module(stdlib, foreign, heap) {"
|
|
" 'use asm';"
|
|
" var MEM32 = new stdlib.Int32Array(heap);"
|
|
" function store() { MEM32[0] = 0; }"
|
|
" return { store: store };"
|
|
"}"
|
|
"var buffer = new ArrayBuffer(4096);"
|
|
"var module = Module(this, {}, buffer);"
|
|
"module.store();"
|
|
"buffer";
|
|
|
|
result = CompileRun(store).As<v8::ArrayBuffer>();
|
|
CHECK(!result->IsDetachable());
|
|
#endif // V8_LITE_MODE
|
|
}
|
|
|
|
TEST(ClassPrototypeCreationContext) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext env;
|
|
|
|
Local<Object> result = Local<Object>::Cast(
|
|
CompileRun("'use strict'; class Example { }; Example.prototype"));
|
|
CHECK(env.local() == result->GetCreationContext().ToLocalChecked());
|
|
}
|
|
|
|
|
|
TEST(SimpleStreamingScriptWithSourceURL) {
|
|
const char* chunks[] = {"function foo() { ret",
|
|
"urn 13; } globalThis.Result = f", "oo();\n",
|
|
"//# sourceURL=bar2.js\n", nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8, true,
|
|
"bar2.js");
|
|
}
|
|
|
|
|
|
TEST(StreamingScriptWithSplitSourceURL) {
|
|
const char* chunks[] = {"function foo() { ret",
|
|
"urn 13; } globalThis.Result = f",
|
|
"oo();\n//# sourceURL=b", "ar2.js\n", nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8, true,
|
|
"bar2.js");
|
|
}
|
|
|
|
|
|
TEST(StreamingScriptWithSourceMappingURLInTheMiddle) {
|
|
const char* chunks[] = {"function foo() { ret", "urn 13; }\n//#",
|
|
" sourceMappingURL=bar2.js\n",
|
|
"globalThis.Result = foo();", nullptr};
|
|
RunStreamingTest(chunks, v8::ScriptCompiler::StreamedSource::UTF8, true,
|
|
nullptr, "bar2.js");
|
|
}
|
|
|
|
|
|
TEST(NewStringRangeError) {
|
|
// This test uses a lot of memory and fails with flaky OOM when run
|
|
// with --stress-incremental-marking on TSAN.
|
|
i::FLAG_stress_incremental_marking = false;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
const int length = i::String::kMaxLength + 1;
|
|
const int buffer_size = length * sizeof(uint16_t);
|
|
void* buffer = malloc(buffer_size);
|
|
if (buffer == nullptr) return;
|
|
memset(buffer, 'A', buffer_size);
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
char* data = reinterpret_cast<char*>(buffer);
|
|
CHECK(v8::String::NewFromUtf8(isolate, data, v8::NewStringType::kNormal,
|
|
length)
|
|
.IsEmpty());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
uint8_t* data = reinterpret_cast<uint8_t*>(buffer);
|
|
CHECK(v8::String::NewFromOneByte(isolate, data, v8::NewStringType::kNormal,
|
|
length)
|
|
.IsEmpty());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
uint16_t* data = reinterpret_cast<uint16_t*>(buffer);
|
|
CHECK(v8::String::NewFromTwoByte(isolate, data, v8::NewStringType::kNormal,
|
|
length)
|
|
.IsEmpty());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
free(buffer);
|
|
}
|
|
|
|
|
|
TEST(SealHandleScope) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext env;
|
|
|
|
v8::SealHandleScope seal(isolate);
|
|
|
|
// Should fail
|
|
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
|
|
|
USE(obj);
|
|
}
|
|
|
|
|
|
TEST(SealHandleScopeNested) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext env;
|
|
|
|
v8::SealHandleScope seal(isolate);
|
|
|
|
{
|
|
v8::HandleScope inner_handle_scope(isolate);
|
|
|
|
// Should work
|
|
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
|
|
|
USE(obj);
|
|
}
|
|
}
|
|
|
|
TEST(Map) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext env;
|
|
|
|
v8::Local<v8::Map> map = v8::Map::New(isolate);
|
|
CHECK(map->IsObject());
|
|
CHECK(map->IsMap());
|
|
CHECK(map->GetPrototype()->StrictEquals(CompileRun("Map.prototype")));
|
|
CHECK_EQ(0U, map->Size());
|
|
|
|
v8::Local<v8::Value> val = CompileRun("new Map([[1, 2], [3, 4]])");
|
|
CHECK(val->IsMap());
|
|
map = v8::Local<v8::Map>::Cast(val);
|
|
CHECK_EQ(2U, map->Size());
|
|
|
|
v8::Local<v8::Array> contents = map->AsArray();
|
|
CHECK_EQ(4U, contents->Length());
|
|
CHECK_EQ(
|
|
1,
|
|
contents->Get(env.local(), 0).ToLocalChecked().As<v8::Int32>()->Value());
|
|
CHECK_EQ(
|
|
2,
|
|
contents->Get(env.local(), 1).ToLocalChecked().As<v8::Int32>()->Value());
|
|
CHECK_EQ(
|
|
3,
|
|
contents->Get(env.local(), 2).ToLocalChecked().As<v8::Int32>()->Value());
|
|
CHECK_EQ(
|
|
4,
|
|
contents->Get(env.local(), 3).ToLocalChecked().As<v8::Int32>()->Value());
|
|
|
|
CHECK_EQ(2U, map->Size());
|
|
|
|
CHECK(map->Has(env.local(), v8::Integer::New(isolate, 1)).FromJust());
|
|
CHECK(map->Has(env.local(), v8::Integer::New(isolate, 3)).FromJust());
|
|
|
|
CHECK(!map->Has(env.local(), v8::Integer::New(isolate, 2)).FromJust());
|
|
CHECK(!map->Has(env.local(), map).FromJust());
|
|
|
|
CHECK_EQ(2, map->Get(env.local(), v8::Integer::New(isolate, 1))
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
CHECK_EQ(4, map->Get(env.local(), v8::Integer::New(isolate, 3))
|
|
.ToLocalChecked()
|
|
->Int32Value(env.local())
|
|
.FromJust());
|
|
|
|
CHECK(map->Get(env.local(), v8::Integer::New(isolate, 42))
|
|
.ToLocalChecked()
|
|
->IsUndefined());
|
|
|
|
CHECK(!map->Set(env.local(), map, map).IsEmpty());
|
|
CHECK_EQ(3U, map->Size());
|
|
CHECK(map->Has(env.local(), map).FromJust());
|
|
|
|
CHECK(map->Delete(env.local(), map).FromJust());
|
|
CHECK_EQ(2U, map->Size());
|
|
CHECK(!map->Has(env.local(), map).FromJust());
|
|
CHECK(!map->Delete(env.local(), map).FromJust());
|
|
|
|
map->Clear();
|
|
CHECK_EQ(0U, map->Size());
|
|
}
|
|
|
|
|
|
TEST(Set) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext env;
|
|
|
|
v8::Local<v8::Set> set = v8::Set::New(isolate);
|
|
CHECK(set->IsObject());
|
|
CHECK(set->IsSet());
|
|
CHECK(set->GetPrototype()->StrictEquals(CompileRun("Set.prototype")));
|
|
CHECK_EQ(0U, set->Size());
|
|
|
|
v8::Local<v8::Value> val = CompileRun("new Set([1, 2])");
|
|
CHECK(val->IsSet());
|
|
set = v8::Local<v8::Set>::Cast(val);
|
|
CHECK_EQ(2U, set->Size());
|
|
|
|
v8::Local<v8::Array> keys = set->AsArray();
|
|
CHECK_EQ(2U, keys->Length());
|
|
CHECK_EQ(1,
|
|
keys->Get(env.local(), 0).ToLocalChecked().As<v8::Int32>()->Value());
|
|
CHECK_EQ(2,
|
|
keys->Get(env.local(), 1).ToLocalChecked().As<v8::Int32>()->Value());
|
|
|
|
CHECK_EQ(2U, set->Size());
|
|
|
|
CHECK(set->Has(env.local(), v8::Integer::New(isolate, 1)).FromJust());
|
|
CHECK(set->Has(env.local(), v8::Integer::New(isolate, 2)).FromJust());
|
|
|
|
CHECK(!set->Has(env.local(), v8::Integer::New(isolate, 3)).FromJust());
|
|
CHECK(!set->Has(env.local(), set).FromJust());
|
|
|
|
CHECK(!set->Add(env.local(), set).IsEmpty());
|
|
CHECK_EQ(3U, set->Size());
|
|
CHECK(set->Has(env.local(), set).FromJust());
|
|
|
|
CHECK(set->Delete(env.local(), set).FromJust());
|
|
CHECK_EQ(2U, set->Size());
|
|
CHECK(!set->Has(env.local(), set).FromJust());
|
|
CHECK(!set->Delete(env.local(), set).FromJust());
|
|
|
|
set->Clear();
|
|
CHECK_EQ(0U, set->Size());
|
|
}
|
|
|
|
TEST(SetDeleteThenAsArray) {
|
|
// https://bugs.chromium.org/p/v8/issues/detail?id=4946
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext env;
|
|
|
|
// make a Set
|
|
v8::Local<v8::Value> val = CompileRun("new Set([1, 2, 3])");
|
|
v8::Local<v8::Set> set = v8::Local<v8::Set>::Cast(val);
|
|
CHECK_EQ(3U, set->Size());
|
|
|
|
// delete the "middle" element (using AsArray to
|
|
// determine which element is the "middle" element)
|
|
v8::Local<v8::Array> array1 = set->AsArray();
|
|
CHECK_EQ(3U, array1->Length());
|
|
CHECK(set->Delete(env.local(), array1->Get(env.local(), 1).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// make sure there are no undefined values when we convert to an array again.
|
|
v8::Local<v8::Array> array2 = set->AsArray();
|
|
uint32_t length = array2->Length();
|
|
CHECK_EQ(2U, length);
|
|
for (uint32_t i = 0; i < length; i++) {
|
|
CHECK(!array2->Get(env.local(), i).ToLocalChecked()->IsUndefined());
|
|
}
|
|
}
|
|
|
|
TEST(MapDeleteThenAsArray) {
|
|
// https://bugs.chromium.org/p/v8/issues/detail?id=4946
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
LocalContext env;
|
|
|
|
// make a Map
|
|
v8::Local<v8::Value> val = CompileRun("new Map([[1, 2], [3, 4], [5, 6]])");
|
|
v8::Local<v8::Map> map = v8::Local<v8::Map>::Cast(val);
|
|
CHECK_EQ(3U, map->Size());
|
|
|
|
// delete the "middle" element (using AsArray to
|
|
// determine which element is the "middle" element)
|
|
v8::Local<v8::Array> array1 = map->AsArray();
|
|
CHECK_EQ(6U, array1->Length());
|
|
// Map::AsArray returns a flat array, so the second key is at index 2.
|
|
v8::Local<v8::Value> key = array1->Get(env.local(), 2).ToLocalChecked();
|
|
CHECK(map->Delete(env.local(), key).FromJust());
|
|
|
|
// make sure there are no undefined values when we convert to an array again.
|
|
v8::Local<v8::Array> array2 = map->AsArray();
|
|
uint32_t length = array2->Length();
|
|
CHECK_EQ(4U, length);
|
|
for (uint32_t i = 0; i < length; i++) {
|
|
CHECK(!array2->Get(env.local(), i).ToLocalChecked()->IsUndefined());
|
|
}
|
|
}
|
|
|
|
TEST(CompatibleReceiverCheckOnCachedICHandler) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::FunctionTemplate> parent = FunctionTemplate::New(isolate);
|
|
v8::Local<v8::Signature> signature = v8::Signature::New(isolate, parent);
|
|
auto returns_42 =
|
|
v8::FunctionTemplate::New(isolate, Returns42, Local<Value>(), signature);
|
|
parent->PrototypeTemplate()->SetAccessorProperty(v8_str("age"), returns_42);
|
|
v8::Local<v8::FunctionTemplate> child = v8::FunctionTemplate::New(isolate);
|
|
child->Inherit(parent);
|
|
LocalContext env;
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("Child"),
|
|
child->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
// Make sure there's a compiled stub for "Child.prototype.age" in the cache.
|
|
CompileRun(
|
|
"var real = new Child();\n"
|
|
"for (var i = 0; i < 3; ++i) {\n"
|
|
" real.age;\n"
|
|
"}\n");
|
|
|
|
// Check that the cached stub is never used.
|
|
ExpectInt32(
|
|
"var fake = Object.create(Child.prototype);\n"
|
|
"var result = 0;\n"
|
|
"function test(d) {\n"
|
|
" if (d == 3) return;\n"
|
|
" try {\n"
|
|
" fake.age;\n"
|
|
" result = 1;\n"
|
|
" } catch (e) {\n"
|
|
" }\n"
|
|
" test(d+1);\n"
|
|
"}\n"
|
|
"test(0);\n"
|
|
"result;\n",
|
|
0);
|
|
}
|
|
|
|
THREADED_TEST(ReceiverConversionForAccessors) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<v8::FunctionTemplate> acc =
|
|
v8::FunctionTemplate::New(isolate, Returns42);
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("acc"),
|
|
acc->GetFunction(env.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->SetAccessorProperty(v8_str("acc"), acc, acc);
|
|
Local<v8::Object> instance = templ->NewInstance(env.local()).ToLocalChecked();
|
|
|
|
CHECK(env->Global()->Set(env.local(), v8_str("p"), instance).FromJust());
|
|
CHECK(CompileRun("(p.acc == 42)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(p.acc = 7) == 7")->BooleanValue(isolate));
|
|
|
|
CHECK(!CompileRun("Number.prototype.__proto__ = p;"
|
|
"var a = 1;")
|
|
.IsEmpty());
|
|
CHECK(CompileRun("(a.acc == 42)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(a.acc = 7) == 7")->BooleanValue(isolate));
|
|
|
|
CHECK(!CompileRun("Boolean.prototype.__proto__ = p;"
|
|
"var a = true;")
|
|
.IsEmpty());
|
|
CHECK(CompileRun("(a.acc == 42)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(a.acc = 7) == 7")->BooleanValue(isolate));
|
|
|
|
CHECK(!CompileRun("String.prototype.__proto__ = p;"
|
|
"var a = 'foo';")
|
|
.IsEmpty());
|
|
CHECK(CompileRun("(a.acc == 42)")->BooleanValue(isolate));
|
|
CHECK(CompileRun("(a.acc = 7) == 7")->BooleanValue(isolate));
|
|
|
|
CHECK(CompileRun("acc.call(1) == 42")->BooleanValue(isolate));
|
|
CHECK(CompileRun("acc.call(true)==42")->BooleanValue(isolate));
|
|
CHECK(CompileRun("acc.call('aa')==42")->BooleanValue(isolate));
|
|
CHECK(CompileRun("acc.call(null) == 42")->BooleanValue(isolate));
|
|
CHECK(CompileRun("acc.call(undefined) == 42")->BooleanValue(isolate));
|
|
}
|
|
|
|
class TerminateExecutionThread : public v8::base::Thread {
|
|
public:
|
|
explicit TerminateExecutionThread(v8::Isolate* isolate)
|
|
: Thread(Options("TerminateExecutionThread")), isolate_(isolate) {}
|
|
|
|
void Run() override {
|
|
// Wait a bit before terminating.
|
|
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(100));
|
|
isolate_->TerminateExecution();
|
|
}
|
|
|
|
private:
|
|
v8::Isolate* isolate_;
|
|
};
|
|
|
|
TEST(FutexInterruption) {
|
|
i::FLAG_harmony_sharedarraybuffer = true;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
TerminateExecutionThread timeout_thread(isolate);
|
|
|
|
v8::TryCatch try_catch(CcTest::isolate());
|
|
CHECK(timeout_thread.Start());
|
|
|
|
CompileRun(
|
|
"var ab = new SharedArrayBuffer(4);"
|
|
"var i32a = new Int32Array(ab);"
|
|
"Atomics.wait(i32a, 0, 0);");
|
|
CHECK(try_catch.HasTerminated());
|
|
timeout_thread.Join();
|
|
}
|
|
|
|
TEST(StackCheckTermination) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
i::Isolate* i_isolate = CcTest::i_isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
TerminateExecutionThread timeout_thread(isolate);
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(timeout_thread.Start());
|
|
auto should_continue = [i_isolate]() {
|
|
using StackLimitCheck = i::StackLimitCheck;
|
|
STACK_CHECK(i_isolate, false);
|
|
return true;
|
|
};
|
|
while (should_continue()) {
|
|
}
|
|
if (i_isolate->has_pending_exception()) i_isolate->ReportPendingMessages();
|
|
CHECK(try_catch.HasTerminated());
|
|
timeout_thread.Join();
|
|
}
|
|
|
|
static int nb_uncaught_exception_callback_calls = 0;
|
|
|
|
|
|
bool NoAbortOnUncaughtException(v8::Isolate* isolate) {
|
|
++nb_uncaught_exception_callback_calls;
|
|
return false;
|
|
}
|
|
|
|
|
|
TEST(AbortOnUncaughtExceptionNoAbort) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> global_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
LocalContext env(nullptr, global_template);
|
|
|
|
i::FLAG_abort_on_uncaught_exception = true;
|
|
isolate->SetAbortOnUncaughtExceptionCallback(NoAbortOnUncaughtException);
|
|
|
|
CompileRun("function boom() { throw new Error(\"boom\") }");
|
|
|
|
v8::Local<v8::Object> global_object = env->Global();
|
|
v8::Local<v8::Function> foo = v8::Local<v8::Function>::Cast(
|
|
global_object->Get(env.local(), v8_str("boom")).ToLocalChecked());
|
|
|
|
CHECK(foo->Call(env.local(), global_object, 0, nullptr).IsEmpty());
|
|
|
|
CHECK_EQ(1, nb_uncaught_exception_callback_calls);
|
|
}
|
|
|
|
|
|
TEST(AccessCheckedIsConcatSpreadable) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
// Object with access check
|
|
Local<ObjectTemplate> spreadable_template = v8::ObjectTemplate::New(isolate);
|
|
spreadable_template->SetAccessCheckCallback(AccessBlocker);
|
|
spreadable_template->Set(v8::Symbol::GetIsConcatSpreadable(isolate),
|
|
v8::Boolean::New(isolate, true));
|
|
Local<Object> object =
|
|
spreadable_template->NewInstance(env.local()).ToLocalChecked();
|
|
|
|
allowed_access = true;
|
|
CHECK(env->Global()->Set(env.local(), v8_str("object"), object).FromJust());
|
|
object->Set(env.local(), v8_str("length"), v8_num(2)).FromJust();
|
|
object->Set(env.local(), 0U, v8_str("a")).FromJust();
|
|
object->Set(env.local(), 1U, v8_str("b")).FromJust();
|
|
|
|
// Access check is allowed, and the object is spread
|
|
CompileRun("var result = [].concat(object)");
|
|
ExpectTrue("Array.isArray(result)");
|
|
ExpectString("result[0]", "a");
|
|
ExpectString("result[1]", "b");
|
|
ExpectTrue("result.length === 2");
|
|
ExpectTrue("object[Symbol.isConcatSpreadable]");
|
|
|
|
// If access check fails, the value of @@isConcatSpreadable is ignored
|
|
allowed_access = false;
|
|
CompileRun("var result = [].concat(object)");
|
|
ExpectTrue("Array.isArray(result)");
|
|
ExpectTrue("result[0] === object");
|
|
ExpectTrue("result.length === 1");
|
|
ExpectTrue("object[Symbol.isConcatSpreadable] === undefined");
|
|
}
|
|
|
|
|
|
TEST(AccessCheckedToStringTag) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
// Object with access check
|
|
Local<ObjectTemplate> object_template = v8::ObjectTemplate::New(isolate);
|
|
object_template->SetAccessCheckCallback(AccessBlocker);
|
|
Local<Object> object =
|
|
object_template->NewInstance(env.local()).ToLocalChecked();
|
|
|
|
allowed_access = true;
|
|
env->Global()->Set(env.local(), v8_str("object"), object).FromJust();
|
|
object->Set(env.local(), v8::Symbol::GetToStringTag(isolate), v8_str("hello"))
|
|
.FromJust();
|
|
|
|
// Access check is allowed, and the toStringTag is read
|
|
CompileRun("var result = Object.prototype.toString.call(object)");
|
|
ExpectString("result", "[object hello]");
|
|
ExpectString("object[Symbol.toStringTag]", "hello");
|
|
|
|
// ToString through the API should succeed too.
|
|
String::Utf8Value result_allowed(
|
|
isolate, object->ObjectProtoToString(env.local()).ToLocalChecked());
|
|
CHECK_EQ(0, strcmp(*result_allowed, "[object hello]"));
|
|
|
|
// If access check fails, the value of @@toStringTag is ignored
|
|
allowed_access = false;
|
|
CompileRun("var result = Object.prototype.toString.call(object)");
|
|
ExpectString("result", "[object Object]");
|
|
ExpectTrue("object[Symbol.toStringTag] === undefined");
|
|
|
|
// ToString through the API should also fail.
|
|
String::Utf8Value result_denied(
|
|
isolate, object->ObjectProtoToString(env.local()).ToLocalChecked());
|
|
CHECK_EQ(0, strcmp(*result_denied, "[object Object]"));
|
|
}
|
|
|
|
TEST(TemplateIteratorPrototypeIntrinsics) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
// Object templates.
|
|
{
|
|
Local<ObjectTemplate> object_template = v8::ObjectTemplate::New(isolate);
|
|
object_template->SetIntrinsicDataProperty(v8_str("iter_proto"),
|
|
v8::kIteratorPrototype);
|
|
Local<Object> object =
|
|
object_template->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj"), object).FromJust());
|
|
ExpectTrue("obj.iter_proto === [][Symbol.iterator]().__proto__.__proto__");
|
|
}
|
|
// Setting %IteratorProto% on the function object's prototype template.
|
|
{
|
|
Local<FunctionTemplate> func_template = v8::FunctionTemplate::New(isolate);
|
|
func_template->PrototypeTemplate()->SetIntrinsicDataProperty(
|
|
v8_str("iter_proto"), v8::kIteratorPrototype);
|
|
Local<Function> func1 =
|
|
func_template->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("func1"), func1).FromJust());
|
|
Local<Function> func2 =
|
|
func_template->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("func2"), func2).FromJust());
|
|
ExpectTrue(
|
|
"func1.prototype.iter_proto === "
|
|
"[][Symbol.iterator]().__proto__.__proto__");
|
|
ExpectTrue(
|
|
"func2.prototype.iter_proto === "
|
|
"[][Symbol.iterator]().__proto__.__proto__");
|
|
ExpectTrue("func1.prototype.iter_proto === func2.prototype.iter_proto");
|
|
|
|
Local<Object> instance1 = func1->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("instance1"), instance1)
|
|
.FromJust());
|
|
ExpectFalse("instance1.hasOwnProperty('iter_proto')");
|
|
ExpectTrue("'iter_proto' in instance1.__proto__");
|
|
ExpectTrue(
|
|
"instance1.iter_proto === [][Symbol.iterator]().__proto__.__proto__");
|
|
}
|
|
// Put %IteratorProto% in a function object's inheritance chain.
|
|
{
|
|
Local<FunctionTemplate> parent_template =
|
|
v8::FunctionTemplate::New(isolate);
|
|
parent_template->RemovePrototype(); // Remove so there is no name clash.
|
|
parent_template->SetIntrinsicDataProperty(v8_str("prototype"),
|
|
v8::kIteratorPrototype);
|
|
Local<FunctionTemplate> func_template = v8::FunctionTemplate::New(isolate);
|
|
func_template->Inherit(parent_template);
|
|
|
|
Local<Function> func =
|
|
func_template->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("func"), func).FromJust());
|
|
ExpectTrue(
|
|
"func.prototype.__proto__ === "
|
|
"[][Symbol.iterator]().__proto__.__proto__");
|
|
|
|
Local<Object> func_instance =
|
|
func->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("instance"), func_instance)
|
|
.FromJust());
|
|
ExpectTrue(
|
|
"instance.__proto__.__proto__ === "
|
|
"[][Symbol.iterator]().__proto__.__proto__");
|
|
ExpectTrue("instance.__proto__.__proto__.__proto__ === Object.prototype");
|
|
}
|
|
}
|
|
|
|
TEST(TemplateAsyncIteratorPrototypeIntrinsics) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
// Object templates.
|
|
{
|
|
Local<ObjectTemplate> object_template = v8::ObjectTemplate::New(isolate);
|
|
object_template->SetIntrinsicDataProperty(v8_str("iter_proto"),
|
|
v8::kAsyncIteratorPrototype);
|
|
Local<Object> object =
|
|
object_template->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj"), object).FromJust());
|
|
ExpectTrue(
|
|
"obj.iter_proto === "
|
|
"(async function* (){}).prototype.__proto__.__proto__");
|
|
}
|
|
// Setting %AsyncIteratorProto% on the function object's prototype template.
|
|
{
|
|
Local<FunctionTemplate> func_template = v8::FunctionTemplate::New(isolate);
|
|
func_template->PrototypeTemplate()->SetIntrinsicDataProperty(
|
|
v8_str("iter_proto"), v8::kAsyncIteratorPrototype);
|
|
Local<Function> func1 =
|
|
func_template->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("func1"), func1).FromJust());
|
|
Local<Function> func2 =
|
|
func_template->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("func2"), func2).FromJust());
|
|
ExpectTrue(
|
|
"func1.prototype.iter_proto === "
|
|
"(async function* (){}).prototype.__proto__.__proto__");
|
|
ExpectTrue(
|
|
"func2.prototype.iter_proto === "
|
|
"(async function* (){}).prototype.__proto__.__proto__");
|
|
ExpectTrue("func1.prototype.iter_proto === func2.prototype.iter_proto");
|
|
|
|
Local<Object> instance1 = func1->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("instance1"), instance1)
|
|
.FromJust());
|
|
ExpectFalse("instance1.hasOwnProperty('iter_proto')");
|
|
ExpectTrue("'iter_proto' in instance1.__proto__");
|
|
ExpectTrue(
|
|
"instance1.iter_proto === "
|
|
"(async function* (){}).prototype.__proto__.__proto__");
|
|
}
|
|
// Put %AsyncIteratorProto% in a function object's inheritance chain.
|
|
{
|
|
Local<FunctionTemplate> parent_template =
|
|
v8::FunctionTemplate::New(isolate);
|
|
parent_template->RemovePrototype(); // Remove so there is no name clash.
|
|
parent_template->SetIntrinsicDataProperty(v8_str("prototype"),
|
|
v8::kAsyncIteratorPrototype);
|
|
Local<FunctionTemplate> func_template = v8::FunctionTemplate::New(isolate);
|
|
func_template->Inherit(parent_template);
|
|
|
|
Local<Function> func =
|
|
func_template->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("func"), func).FromJust());
|
|
ExpectTrue(
|
|
"func.prototype.__proto__ === "
|
|
"(async function* (){}).prototype.__proto__.__proto__");
|
|
|
|
Local<Object> func_instance =
|
|
func->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("instance"), func_instance)
|
|
.FromJust());
|
|
ExpectTrue(
|
|
"instance.__proto__.__proto__ === "
|
|
"(async function* (){}).prototype.__proto__.__proto__");
|
|
ExpectTrue("instance.__proto__.__proto__.__proto__ === Object.prototype");
|
|
}
|
|
}
|
|
|
|
TEST(TemplateErrorPrototypeIntrinsics) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
// Object templates.
|
|
{
|
|
Local<ObjectTemplate> object_template = v8::ObjectTemplate::New(isolate);
|
|
object_template->SetIntrinsicDataProperty(v8_str("error_proto"),
|
|
v8::kErrorPrototype);
|
|
Local<Object> object =
|
|
object_template->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj"), object).FromJust());
|
|
ExpectTrue("obj.error_proto === Error.prototype");
|
|
Local<Value> error = v8::Exception::Error(v8_str("error message"));
|
|
CHECK(env->Global()->Set(env.local(), v8_str("err"), error).FromJust());
|
|
ExpectTrue("obj.error_proto === Object.getPrototypeOf(err)");
|
|
}
|
|
// Setting %ErrorPrototype% on the function object's prototype template.
|
|
{
|
|
Local<FunctionTemplate> func_template = v8::FunctionTemplate::New(isolate);
|
|
func_template->PrototypeTemplate()->SetIntrinsicDataProperty(
|
|
v8_str("error_proto"), v8::kErrorPrototype);
|
|
Local<Function> func1 =
|
|
func_template->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("func1"), func1).FromJust());
|
|
Local<Function> func2 =
|
|
func_template->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("func2"), func2).FromJust());
|
|
ExpectTrue("func1.prototype.error_proto === Error.prototype");
|
|
ExpectTrue("func2.prototype.error_proto === Error.prototype");
|
|
ExpectTrue("func1.prototype.error_proto === func2.prototype.error_proto");
|
|
|
|
Local<Object> instance1 = func1->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("instance1"), instance1)
|
|
.FromJust());
|
|
ExpectFalse("instance1.hasOwnProperty('error_proto')");
|
|
ExpectTrue("'error_proto' in instance1.__proto__");
|
|
ExpectTrue("instance1.error_proto === Error.prototype");
|
|
}
|
|
// Put %ErrorPrototype% in a function object's inheritance chain.
|
|
{
|
|
Local<FunctionTemplate> parent_template =
|
|
v8::FunctionTemplate::New(isolate);
|
|
parent_template->RemovePrototype(); // Remove so there is no name clash.
|
|
parent_template->SetIntrinsicDataProperty(v8_str("prototype"),
|
|
v8::kErrorPrototype);
|
|
Local<FunctionTemplate> func_template = v8::FunctionTemplate::New(isolate);
|
|
func_template->Inherit(parent_template);
|
|
|
|
Local<Function> func =
|
|
func_template->GetFunction(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("func"), func).FromJust());
|
|
ExpectTrue("func.prototype.__proto__ === Error.prototype");
|
|
|
|
Local<Object> func_instance =
|
|
func->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("instance"), func_instance)
|
|
.FromJust());
|
|
ExpectTrue("instance.__proto__.__proto__.__proto__ === Object.prototype");
|
|
// Now let's check if %ErrorPrototype% properties are in the instance.
|
|
ExpectTrue("'constructor' in instance");
|
|
ExpectTrue("'message' in instance");
|
|
ExpectTrue("'name' in instance");
|
|
ExpectTrue("'toString' in instance");
|
|
}
|
|
}
|
|
|
|
TEST(ObjectTemplateArrayProtoIntrinsics) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
Local<ObjectTemplate> object_template = v8::ObjectTemplate::New(isolate);
|
|
object_template->SetIntrinsicDataProperty(v8_str("prop_entries"),
|
|
v8::kArrayProto_entries);
|
|
object_template->SetIntrinsicDataProperty(v8_str("prop_forEach"),
|
|
v8::kArrayProto_forEach);
|
|
object_template->SetIntrinsicDataProperty(v8_str("prop_keys"),
|
|
v8::kArrayProto_keys);
|
|
object_template->SetIntrinsicDataProperty(v8_str("prop_values"),
|
|
v8::kArrayProto_values);
|
|
Local<Object> object =
|
|
object_template->NewInstance(env.local()).ToLocalChecked();
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj1"), object).FromJust());
|
|
|
|
const struct {
|
|
const char* const object_property_name;
|
|
const char* const array_property_name;
|
|
} intrinsics_comparisons[] = {
|
|
{"prop_entries", "Array.prototype.entries"},
|
|
{"prop_forEach", "Array.prototype.forEach"},
|
|
{"prop_keys", "Array.prototype.keys"},
|
|
{"prop_values", "Array.prototype[Symbol.iterator]"},
|
|
};
|
|
|
|
for (unsigned i = 0; i < arraysize(intrinsics_comparisons); i++) {
|
|
v8::base::ScopedVector<char> test_string(64);
|
|
|
|
v8::base::SNPrintF(test_string, "typeof obj1.%s",
|
|
intrinsics_comparisons[i].object_property_name);
|
|
ExpectString(test_string.begin(), "function");
|
|
|
|
v8::base::SNPrintF(test_string, "obj1.%s === %s",
|
|
intrinsics_comparisons[i].object_property_name,
|
|
intrinsics_comparisons[i].array_property_name);
|
|
ExpectTrue(test_string.begin());
|
|
|
|
v8::base::SNPrintF(test_string, "obj1.%s = 42",
|
|
intrinsics_comparisons[i].object_property_name);
|
|
CompileRun(test_string.begin());
|
|
|
|
v8::base::SNPrintF(test_string, "obj1.%s === %s",
|
|
intrinsics_comparisons[i].object_property_name,
|
|
intrinsics_comparisons[i].array_property_name);
|
|
ExpectFalse(test_string.begin());
|
|
|
|
v8::base::SNPrintF(test_string, "typeof obj1.%s",
|
|
intrinsics_comparisons[i].object_property_name);
|
|
ExpectString(test_string.begin(), "number");
|
|
}
|
|
}
|
|
|
|
TEST(ObjectTemplatePerContextIntrinsics) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
Local<ObjectTemplate> object_template = v8::ObjectTemplate::New(isolate);
|
|
object_template->SetIntrinsicDataProperty(v8_str("values"),
|
|
v8::kArrayProto_values);
|
|
Local<Object> object =
|
|
object_template->NewInstance(env.local()).ToLocalChecked();
|
|
|
|
CHECK(env->Global()->Set(env.local(), v8_str("obj1"), object).FromJust());
|
|
ExpectString("typeof obj1.values", "function");
|
|
|
|
auto values = Local<Function>::Cast(
|
|
object->Get(env.local(), v8_str("values")).ToLocalChecked());
|
|
auto fn = i::Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*values));
|
|
auto ctx = v8::Utils::OpenHandle(*env.local());
|
|
CHECK_EQ(*(fn->GetCreationContext().ToHandleChecked()), *ctx);
|
|
|
|
{
|
|
LocalContext env2;
|
|
Local<Object> object2 =
|
|
object_template->NewInstance(env2.local()).ToLocalChecked();
|
|
CHECK(
|
|
env2->Global()->Set(env2.local(), v8_str("obj2"), object2).FromJust());
|
|
ExpectString("typeof obj2.values", "function");
|
|
CHECK_NE(*object->Get(env2.local(), v8_str("values")).ToLocalChecked(),
|
|
*object2->Get(env2.local(), v8_str("values")).ToLocalChecked());
|
|
|
|
auto values2 = Local<Function>::Cast(
|
|
object2->Get(env2.local(), v8_str("values")).ToLocalChecked());
|
|
auto fn2 = i::Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*values2));
|
|
auto ctx2 = v8::Utils::OpenHandle(*env2.local());
|
|
CHECK_EQ(*(fn2->GetCreationContext().ToHandleChecked()), *ctx2);
|
|
}
|
|
}
|
|
|
|
|
|
TEST(Proxy) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> target = CompileRun("({})").As<v8::Object>();
|
|
v8::Local<v8::Object> handler = CompileRun("({})").As<v8::Object>();
|
|
|
|
v8::Local<v8::Proxy> proxy =
|
|
v8::Proxy::New(context.local(), target, handler).ToLocalChecked();
|
|
CHECK(proxy->IsProxy());
|
|
CHECK(!target->IsProxy());
|
|
CHECK(!proxy->IsRevoked());
|
|
CHECK(proxy->GetTarget()->SameValue(target));
|
|
CHECK(proxy->GetHandler()->SameValue(handler));
|
|
|
|
proxy->Revoke();
|
|
CHECK(proxy->IsProxy());
|
|
CHECK(!target->IsProxy());
|
|
CHECK(proxy->IsRevoked());
|
|
CHECK(proxy->GetTarget()->IsNull());
|
|
CHECK(proxy->GetHandler()->IsNull());
|
|
}
|
|
|
|
WeakCallCounterAndPersistent<Value>* CreateGarbageWithWeakCallCounter(
|
|
v8::Isolate* isolate, WeakCallCounter* counter) {
|
|
v8::Locker locker(isolate);
|
|
LocalContext env;
|
|
HandleScope scope(isolate);
|
|
WeakCallCounterAndPersistent<Value>* val =
|
|
new WeakCallCounterAndPersistent<Value>(counter);
|
|
val->handle.Reset(isolate, Object::New(isolate));
|
|
val->handle.SetWeak(val, &WeakPointerCallback,
|
|
v8::WeakCallbackType::kParameter);
|
|
return val;
|
|
}
|
|
|
|
class MemoryPressureThread : public v8::base::Thread {
|
|
public:
|
|
explicit MemoryPressureThread(v8::Isolate* isolate,
|
|
v8::MemoryPressureLevel level)
|
|
: Thread(Options("MemoryPressureThread")),
|
|
isolate_(isolate),
|
|
level_(level) {}
|
|
|
|
void Run() override { isolate_->MemoryPressureNotification(level_); }
|
|
|
|
private:
|
|
v8::Isolate* isolate_;
|
|
v8::MemoryPressureLevel level_;
|
|
};
|
|
|
|
TEST(MemoryPressure) {
|
|
if (v8::internal::FLAG_optimize_for_size) return;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
WeakCallCounter counter(1234);
|
|
|
|
// Check that critical memory pressure notification sets GC interrupt.
|
|
auto garbage = CreateGarbageWithWeakCallCounter(isolate, &counter);
|
|
CHECK(!v8::Locker::IsLocked(isolate));
|
|
{
|
|
v8::Locker locker(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
MemoryPressureThread memory_pressure_thread(
|
|
isolate, v8::MemoryPressureLevel::kCritical);
|
|
CHECK(memory_pressure_thread.Start());
|
|
memory_pressure_thread.Join();
|
|
// This should trigger GC.
|
|
CHECK_EQ(0, counter.NumberOfWeakCalls());
|
|
CompileRun("(function noop() { return 0; })()");
|
|
CHECK_EQ(1, counter.NumberOfWeakCalls());
|
|
}
|
|
delete garbage;
|
|
// Check that critical memory pressure notification triggers GC.
|
|
garbage = CreateGarbageWithWeakCallCounter(isolate, &counter);
|
|
{
|
|
v8::Locker locker(isolate);
|
|
// If isolate is locked, memory pressure notification should trigger GC.
|
|
CHECK_EQ(1, counter.NumberOfWeakCalls());
|
|
isolate->MemoryPressureNotification(v8::MemoryPressureLevel::kCritical);
|
|
CHECK_EQ(2, counter.NumberOfWeakCalls());
|
|
}
|
|
delete garbage;
|
|
// Check that moderate memory pressure notification sets GC into memory
|
|
// optimizing mode.
|
|
isolate->MemoryPressureNotification(v8::MemoryPressureLevel::kModerate);
|
|
CHECK(CcTest::i_isolate()->heap()->ShouldOptimizeForMemoryUsage());
|
|
// Check that disabling memory pressure returns GC into normal mode.
|
|
isolate->MemoryPressureNotification(v8::MemoryPressureLevel::kNone);
|
|
CHECK(!CcTest::i_isolate()->heap()->ShouldOptimizeForMemoryUsage());
|
|
}
|
|
|
|
TEST(SetIntegrityLevel) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
|
CHECK(context->Global()->Set(context.local(), v8_str("o"), obj).FromJust());
|
|
|
|
v8::Local<v8::Value> is_frozen = CompileRun("Object.isFrozen(o)");
|
|
CHECK(!is_frozen->BooleanValue(isolate));
|
|
|
|
CHECK(obj->SetIntegrityLevel(context.local(), v8::IntegrityLevel::kFrozen)
|
|
.FromJust());
|
|
|
|
is_frozen = CompileRun("Object.isFrozen(o)");
|
|
CHECK(is_frozen->BooleanValue(isolate));
|
|
}
|
|
|
|
TEST(PrivateForApiIsNumber) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Shouldn't crash.
|
|
v8::Private::ForApi(isolate, v8_str("42"));
|
|
}
|
|
|
|
THREADED_TEST(ImmutableProto) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
templ->InstanceTemplate()->SetImmutableProto();
|
|
|
|
Local<v8::Object> object = templ->GetFunction(context.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(context.local())
|
|
.ToLocalChecked();
|
|
|
|
// Look up the prototype
|
|
Local<v8::Value> original_proto =
|
|
object->Get(context.local(), v8_str("__proto__")).ToLocalChecked();
|
|
|
|
// Setting the prototype (e.g., to null) throws
|
|
CHECK(object->SetPrototype(context.local(), v8::Null(isolate)).IsNothing());
|
|
|
|
// The original prototype is still there
|
|
Local<Value> new_proto =
|
|
object->Get(context.local(), v8_str("__proto__")).ToLocalChecked();
|
|
CHECK(new_proto->IsObject());
|
|
CHECK(new_proto.As<v8::Object>()
|
|
->Equals(context.local(), original_proto)
|
|
.FromJust());
|
|
}
|
|
|
|
Local<v8::Context> call_eval_context;
|
|
Local<v8::Function> call_eval_bound_function;
|
|
|
|
static void CallEval(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::Context::Scope scope(call_eval_context);
|
|
args.GetReturnValue().Set(
|
|
call_eval_bound_function
|
|
->Call(call_eval_context, call_eval_context->Global(), 0, nullptr)
|
|
.ToLocalChecked());
|
|
}
|
|
|
|
TEST(CrossActivationEval) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
{
|
|
call_eval_context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(call_eval_context);
|
|
call_eval_bound_function =
|
|
Local<Function>::Cast(CompileRun("eval.bind(this, '1')"));
|
|
}
|
|
env->Global()
|
|
->Set(env.local(), v8_str("CallEval"),
|
|
v8::FunctionTemplate::New(isolate, CallEval)
|
|
->GetFunction(env.local())
|
|
.ToLocalChecked())
|
|
.FromJust();
|
|
Local<Value> result = CompileRun("CallEval();");
|
|
CHECK(result->IsInt32());
|
|
CHECK_EQ(1, result->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
TEST(EvalInAccessCheckedContext) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New(isolate);
|
|
|
|
obj_template->SetAccessCheckCallback(AccessAlwaysAllowed);
|
|
|
|
v8::Local<Context> context0 = Context::New(isolate, nullptr, obj_template);
|
|
v8::Local<Context> context1 = Context::New(isolate, nullptr, obj_template);
|
|
|
|
Local<Value> foo = v8_str("foo");
|
|
Local<Value> bar = v8_str("bar");
|
|
|
|
// Set to different domains.
|
|
context0->SetSecurityToken(foo);
|
|
context1->SetSecurityToken(bar);
|
|
|
|
// Set up function in context0 that uses eval from context0.
|
|
context0->Enter();
|
|
v8::Local<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. Since the access check
|
|
// callback always returns true, the call succeeds even though the tokens
|
|
// are different.
|
|
context1->Enter();
|
|
context1->Global()->Set(context1, v8_str("fun"), fun).FromJust();
|
|
v8::Local<v8::Value> x_value = CompileRun("fun('x')");
|
|
CHECK_EQ(42, x_value->Int32Value(context1).FromJust());
|
|
context1->Exit();
|
|
}
|
|
|
|
THREADED_TEST(ImmutableProtoWithParent) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::FunctionTemplate> parent = v8::FunctionTemplate::New(isolate);
|
|
|
|
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
|
templ->Inherit(parent);
|
|
templ->PrototypeTemplate()->SetImmutableProto();
|
|
|
|
Local<v8::Function> function =
|
|
templ->GetFunction(context.local()).ToLocalChecked();
|
|
Local<v8::Object> instance =
|
|
function->NewInstance(context.local()).ToLocalChecked();
|
|
Local<v8::Object> prototype =
|
|
instance->Get(context.local(), v8_str("__proto__"))
|
|
.ToLocalChecked()
|
|
->ToObject(context.local())
|
|
.ToLocalChecked();
|
|
|
|
// Look up the prototype
|
|
Local<v8::Value> original_proto =
|
|
prototype->Get(context.local(), v8_str("__proto__")).ToLocalChecked();
|
|
|
|
// Setting the prototype (e.g., to null) throws
|
|
CHECK(
|
|
prototype->SetPrototype(context.local(), v8::Null(isolate)).IsNothing());
|
|
|
|
// The original prototype is still there
|
|
Local<Value> new_proto =
|
|
prototype->Get(context.local(), v8_str("__proto__")).ToLocalChecked();
|
|
CHECK(new_proto->IsObject());
|
|
CHECK(new_proto.As<v8::Object>()
|
|
->Equals(context.local(), original_proto)
|
|
.FromJust());
|
|
}
|
|
|
|
TEST(InternalFieldsOnGlobalProxy) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New(isolate);
|
|
obj_template->SetInternalFieldCount(1);
|
|
|
|
v8::Local<v8::Context> context = Context::New(isolate, nullptr, obj_template);
|
|
v8::Local<v8::Object> global = context->Global();
|
|
CHECK_EQ(1, global->InternalFieldCount());
|
|
}
|
|
|
|
THREADED_TEST(ImmutableProtoGlobal) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
|
|
global_template->SetImmutableProto();
|
|
v8::Local<Context> context = Context::New(isolate, nullptr, global_template);
|
|
Context::Scope context_scope(context);
|
|
v8::Local<Value> result = CompileRun(
|
|
"global = this;"
|
|
"(function() {"
|
|
" try {"
|
|
" global.__proto__ = {};"
|
|
" return 0;"
|
|
" } catch (e) {"
|
|
" return 1;"
|
|
" }"
|
|
"})()");
|
|
CHECK(result->Equals(context, v8::Integer::New(CcTest::isolate(), 1))
|
|
.FromJust());
|
|
}
|
|
|
|
THREADED_TEST(MutableProtoGlobal) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
|
|
v8::Local<Context> context = Context::New(isolate, nullptr, global_template);
|
|
Context::Scope context_scope(context);
|
|
v8::Local<Value> result = CompileRun(
|
|
"global = this;"
|
|
"(function() {"
|
|
" try {"
|
|
" global.__proto__ = {};"
|
|
" return 0;"
|
|
" } catch (e) {"
|
|
" return 1;"
|
|
" }"
|
|
"})()");
|
|
CHECK(result->Equals(context, v8::Integer::New(CcTest::isolate(), 0))
|
|
.FromJust());
|
|
}
|
|
|
|
TEST(SetPrototypeTemplate) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<FunctionTemplate> HTMLElementTemplate = FunctionTemplate::New(isolate);
|
|
Local<FunctionTemplate> HTMLImageElementTemplate =
|
|
FunctionTemplate::New(isolate);
|
|
HTMLImageElementTemplate->Inherit(HTMLElementTemplate);
|
|
|
|
Local<FunctionTemplate> ImageTemplate = FunctionTemplate::New(isolate);
|
|
ImageTemplate->SetPrototypeProviderTemplate(HTMLImageElementTemplate);
|
|
|
|
Local<Function> HTMLImageElement =
|
|
HTMLImageElementTemplate->GetFunction(env.local()).ToLocalChecked();
|
|
Local<Function> Image =
|
|
ImageTemplate->GetFunction(env.local()).ToLocalChecked();
|
|
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("HTMLImageElement"), HTMLImageElement)
|
|
.FromJust());
|
|
CHECK(env->Global()->Set(env.local(), v8_str("Image"), Image).FromJust());
|
|
|
|
ExpectTrue("Image.prototype === HTMLImageElement.prototype");
|
|
}
|
|
|
|
void ensure_receiver_is_global_proxy(
|
|
v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(v8::Utils::OpenHandle(*info.This())->IsJSGlobalProxy());
|
|
}
|
|
|
|
THREADED_TEST(GlobalAccessorInfo) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(isolate);
|
|
global_template->SetAccessor(
|
|
v8::String::NewFromUtf8Literal(isolate, "prop",
|
|
v8::NewStringType::kInternalized),
|
|
&ensure_receiver_is_global_proxy);
|
|
LocalContext env(nullptr, global_template);
|
|
CompileRun("for (var i = 0; i < 10; i++) this.prop");
|
|
CompileRun("for (var i = 0; i < 10; i++) prop");
|
|
}
|
|
|
|
TEST(DeterministicRandomNumberGeneration) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
|
|
int previous_seed = v8::internal::FLAG_random_seed;
|
|
v8::internal::FLAG_random_seed = 1234;
|
|
|
|
double first_value;
|
|
double second_value;
|
|
{
|
|
v8::Local<Context> context = Context::New(CcTest::isolate());
|
|
Context::Scope context_scope(context);
|
|
v8::Local<Value> result = CompileRun("Math.random();");
|
|
first_value = result->ToNumber(context).ToLocalChecked()->Value();
|
|
}
|
|
{
|
|
v8::Local<Context> context = Context::New(CcTest::isolate());
|
|
Context::Scope context_scope(context);
|
|
v8::Local<Value> result = CompileRun("Math.random();");
|
|
second_value = result->ToNumber(context).ToLocalChecked()->Value();
|
|
}
|
|
CHECK_EQ(first_value, second_value);
|
|
|
|
v8::internal::FLAG_random_seed = previous_seed;
|
|
}
|
|
|
|
UNINITIALIZED_TEST(AllowAtomicsWait) {
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.allow_atomics_wait = false;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
{
|
|
CHECK_EQ(false, i_isolate->allow_atomics_wait());
|
|
isolate->SetAllowAtomicsWait(true);
|
|
CHECK_EQ(true, i_isolate->allow_atomics_wait());
|
|
}
|
|
isolate->Dispose();
|
|
}
|
|
|
|
enum ContextId { EnteredContext, CurrentContext };
|
|
|
|
void CheckContexts(v8::Isolate* isolate) {
|
|
CHECK_EQ(CurrentContext, isolate->GetCurrentContext()
|
|
->GetEmbedderData(1)
|
|
.As<v8::Integer>()
|
|
->Value());
|
|
CHECK_EQ(EnteredContext, isolate->GetEnteredOrMicrotaskContext()
|
|
->GetEmbedderData(1)
|
|
.As<v8::Integer>()
|
|
->Value());
|
|
}
|
|
|
|
void ContextCheckGetter(Local<String> name,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CheckContexts(info.GetIsolate());
|
|
info.GetReturnValue().Set(true);
|
|
}
|
|
|
|
void ContextCheckSetter(Local<String> name, Local<Value>,
|
|
const v8::PropertyCallbackInfo<void>& info) {
|
|
CheckContexts(info.GetIsolate());
|
|
}
|
|
|
|
void ContextCheckToString(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
CheckContexts(info.GetIsolate());
|
|
info.GetReturnValue().Set(v8_str("foo"));
|
|
}
|
|
|
|
TEST(CorrectEnteredContext) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
|
|
LocalContext currentContext;
|
|
currentContext->SetEmbedderData(
|
|
1, v8::Integer::New(currentContext->GetIsolate(), CurrentContext));
|
|
LocalContext enteredContext;
|
|
enteredContext->SetEmbedderData(
|
|
1, v8::Integer::New(enteredContext->GetIsolate(), EnteredContext));
|
|
|
|
v8::Context::Scope contextScope(enteredContext.local());
|
|
|
|
v8::Local<v8::ObjectTemplate> object_template =
|
|
ObjectTemplate::New(currentContext->GetIsolate());
|
|
object_template->SetAccessor(v8_str("p"), &ContextCheckGetter,
|
|
&ContextCheckSetter);
|
|
|
|
v8::Local<v8::Object> object =
|
|
object_template->NewInstance(currentContext.local()).ToLocalChecked();
|
|
|
|
object->Get(currentContext.local(), v8_str("p")).ToLocalChecked();
|
|
object->Set(currentContext.local(), v8_str("p"), v8_int(0)).FromJust();
|
|
|
|
v8::Local<v8::Function> to_string =
|
|
v8::Function::New(currentContext.local(), ContextCheckToString)
|
|
.ToLocalChecked();
|
|
|
|
to_string->Call(currentContext.local(), object, 0, nullptr).ToLocalChecked();
|
|
|
|
object
|
|
->CreateDataProperty(currentContext.local(), v8_str("toString"),
|
|
to_string)
|
|
.FromJust();
|
|
|
|
object->ToString(currentContext.local()).ToLocalChecked();
|
|
}
|
|
|
|
// For testing only, the host-defined options are provided entirely by the host
|
|
// and have an abritrary length. Use this constant here for testing that we get
|
|
// the correct value during the tests.
|
|
const int kCustomHostDefinedOptionsLengthForTesting = 7;
|
|
|
|
v8::MaybeLocal<v8::Promise> HostImportModuleDynamicallyCallbackResolve(
|
|
Local<v8::Context> context, Local<v8::Data> host_defined_options,
|
|
Local<v8::Value> resource_name, Local<v8::String> specifier,
|
|
Local<v8::FixedArray> import_assertions) {
|
|
String::Utf8Value referrer_utf8(context->GetIsolate(),
|
|
resource_name.As<String>());
|
|
CHECK_EQ(0, strcmp("www.google.com", *referrer_utf8));
|
|
CHECK_EQ(host_defined_options.As<v8::FixedArray>()->Length(),
|
|
kCustomHostDefinedOptionsLengthForTesting);
|
|
CHECK(!specifier.IsEmpty());
|
|
String::Utf8Value specifier_utf8(context->GetIsolate(), specifier);
|
|
CHECK_EQ(0, strcmp("index.js", *specifier_utf8));
|
|
|
|
CHECK_EQ(0, import_assertions->Length());
|
|
|
|
Local<v8::Promise::Resolver> resolver =
|
|
v8::Promise::Resolver::New(context).ToLocalChecked();
|
|
auto result = v8_str("hello world");
|
|
resolver->Resolve(context, result).ToChecked();
|
|
return resolver->GetPromise();
|
|
}
|
|
|
|
TEST(DynamicImport) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
isolate->SetHostImportModuleDynamicallyCallback(
|
|
HostImportModuleDynamicallyCallbackResolve);
|
|
|
|
i::Handle<i::String> url(v8::Utils::OpenHandle(*v8_str("www.google.com")));
|
|
i::Handle<i::Object> specifier(v8::Utils::OpenHandle(*v8_str("index.js")));
|
|
i::Handle<i::String> result(v8::Utils::OpenHandle(*v8_str("hello world")));
|
|
i::Handle<i::String> source(v8::Utils::OpenHandle(*v8_str("foo")));
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
i::Handle<i::Script> referrer = i_isolate->factory()->NewScript(source);
|
|
referrer->set_name(*url);
|
|
i::Handle<i::FixedArray> options = i_isolate->factory()->NewFixedArray(
|
|
kCustomHostDefinedOptionsLengthForTesting);
|
|
referrer->set_host_defined_options(*options);
|
|
i::MaybeHandle<i::JSPromise> maybe_promise =
|
|
i_isolate->RunHostImportModuleDynamicallyCallback(
|
|
referrer, specifier, i::MaybeHandle<i::Object>());
|
|
i::Handle<i::JSPromise> promise = maybe_promise.ToHandleChecked();
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK(result->Equals(i::String::cast(promise->result())));
|
|
}
|
|
|
|
v8::MaybeLocal<v8::Promise>
|
|
HostImportModuleDynamicallyWithAssertionsCallbackResolve(
|
|
Local<v8::Context> context, Local<v8::Data> host_defined_options,
|
|
Local<v8::Value> resource_name, Local<v8::String> specifier,
|
|
Local<v8::FixedArray> import_assertions) {
|
|
String::Utf8Value referrer_utf8(context->GetIsolate(),
|
|
resource_name.As<String>());
|
|
CHECK_EQ(0, strcmp("www.google.com", *referrer_utf8));
|
|
CHECK_EQ(host_defined_options.As<v8::FixedArray>()->Length(),
|
|
kCustomHostDefinedOptionsLengthForTesting);
|
|
|
|
CHECK(!specifier.IsEmpty());
|
|
String::Utf8Value specifier_utf8(context->GetIsolate(), specifier);
|
|
CHECK_EQ(0, strcmp("index.js", *specifier_utf8));
|
|
|
|
CHECK_EQ(8, import_assertions->Length());
|
|
constexpr int kAssertionEntrySizeForDynamicImport = 2;
|
|
for (int i = 0;
|
|
i < import_assertions->Length() / kAssertionEntrySizeForDynamicImport;
|
|
++i) {
|
|
Local<String> assertion_key =
|
|
import_assertions
|
|
->Get(context, (i * kAssertionEntrySizeForDynamicImport))
|
|
.As<Value>()
|
|
.As<String>();
|
|
Local<String> assertion_value =
|
|
import_assertions
|
|
->Get(context, (i * kAssertionEntrySizeForDynamicImport) + 1)
|
|
.As<Value>()
|
|
.As<String>();
|
|
if (v8_str("a")->StrictEquals(assertion_key)) {
|
|
CHECK(v8_str("z")->StrictEquals(assertion_value));
|
|
} else if (v8_str("aa")->StrictEquals(assertion_key)) {
|
|
CHECK(v8_str("x")->StrictEquals(assertion_value));
|
|
} else if (v8_str("b")->StrictEquals(assertion_key)) {
|
|
CHECK(v8_str("w")->StrictEquals(assertion_value));
|
|
} else if (v8_str("c")->StrictEquals(assertion_key)) {
|
|
CHECK(v8_str("y")->StrictEquals(assertion_value));
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
Local<v8::Promise::Resolver> resolver =
|
|
v8::Promise::Resolver::New(context).ToLocalChecked();
|
|
auto result = v8_str("hello world");
|
|
resolver->Resolve(context, result).ToChecked();
|
|
return resolver->GetPromise();
|
|
}
|
|
|
|
TEST(DynamicImportWithAssertions) {
|
|
FLAG_SCOPE(harmony_import_assertions);
|
|
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
isolate->SetHostImportModuleDynamicallyCallback(
|
|
HostImportModuleDynamicallyWithAssertionsCallbackResolve);
|
|
|
|
i::Handle<i::String> url(v8::Utils::OpenHandle(*v8_str("www.google.com")));
|
|
i::Handle<i::Object> specifier(v8::Utils::OpenHandle(*v8_str("index.js")));
|
|
i::Handle<i::String> result(v8::Utils::OpenHandle(*v8_str("hello world")));
|
|
i::Handle<i::String> source(v8::Utils::OpenHandle(*v8_str("foo")));
|
|
v8::Local<v8::Object> import_assertions =
|
|
CompileRun(
|
|
"var arg = { assert: { 'b': 'w', aa: 'x', c: 'y', a: 'z'} };"
|
|
"arg;")
|
|
->ToObject(context.local())
|
|
.ToLocalChecked();
|
|
|
|
i::Handle<i::Object> i_import_assertions =
|
|
v8::Utils::OpenHandle(*import_assertions);
|
|
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
i::Handle<i::Script> referrer = i_isolate->factory()->NewScript(source);
|
|
referrer->set_name(*url);
|
|
i::Handle<i::FixedArray> options = i_isolate->factory()->NewFixedArray(
|
|
kCustomHostDefinedOptionsLengthForTesting);
|
|
referrer->set_host_defined_options(*options);
|
|
i::MaybeHandle<i::JSPromise> maybe_promise =
|
|
i_isolate->RunHostImportModuleDynamicallyCallback(referrer, specifier,
|
|
i_import_assertions);
|
|
i::Handle<i::JSPromise> promise = maybe_promise.ToHandleChecked();
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
CHECK(result->Equals(i::String::cast(promise->result())));
|
|
}
|
|
|
|
void HostInitializeImportMetaObjectCallbackStatic(Local<Context> context,
|
|
Local<Module> module,
|
|
Local<Object> meta) {
|
|
CHECK(!module.IsEmpty());
|
|
meta->CreateDataProperty(context, v8_str("foo"), v8_str("bar")).ToChecked();
|
|
}
|
|
|
|
TEST(ImportMeta) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
isolate->SetHostInitializeImportMetaObjectCallback(
|
|
HostInitializeImportMetaObjectCallbackStatic);
|
|
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
Local<String> url = v8_str("www.google.com");
|
|
Local<String> source_text = v8_str("globalThis.Result = import.meta;");
|
|
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
|
|
false, false, true);
|
|
v8::ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
|
|
i::Handle<i::JSObject> meta =
|
|
i::SourceTextModule::GetImportMeta(
|
|
i_isolate,
|
|
i::Handle<i::SourceTextModule>::cast(v8::Utils::OpenHandle(*module)))
|
|
.ToHandleChecked();
|
|
Local<Object> meta_obj = Local<Object>::Cast(v8::Utils::ToLocal(meta));
|
|
CHECK(meta_obj->Get(context.local(), v8_str("foo"))
|
|
.ToLocalChecked()
|
|
->IsString());
|
|
CHECK(meta_obj->Get(context.local(), v8_str("zapp"))
|
|
.ToLocalChecked()
|
|
->IsUndefined());
|
|
|
|
module->InstantiateModule(context.local(), UnexpectedModuleResolveCallback)
|
|
.ToChecked();
|
|
Local<Value> result = module->Evaluate(context.local()).ToLocalChecked();
|
|
Local<v8::Promise> promise(Local<v8::Promise>::Cast(result));
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
CHECK(promise->Result()->IsUndefined());
|
|
CHECK(context.local()
|
|
->Global()
|
|
->Get(context.local(), v8_str("Result"))
|
|
.ToLocalChecked()
|
|
->StrictEquals(Local<v8::Value>::Cast(v8::Utils::ToLocal(meta))));
|
|
}
|
|
|
|
void HostInitializeImportMetaObjectCallbackThrow(Local<Context> context,
|
|
Local<Module> module,
|
|
Local<Object> meta) {
|
|
CcTest::isolate()->ThrowException(v8_num(42));
|
|
}
|
|
|
|
TEST(ImportMetaThrowUnhandled) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
isolate->SetHostInitializeImportMetaObjectCallback(
|
|
HostInitializeImportMetaObjectCallbackThrow);
|
|
|
|
Local<String> url = v8_str("www.google.com");
|
|
Local<String> source_text =
|
|
v8_str("export default function() { return import.meta }");
|
|
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
|
|
false, false, true);
|
|
v8::ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
|
|
module->InstantiateModule(context.local(), UnexpectedModuleResolveCallback)
|
|
.ToChecked();
|
|
|
|
Local<Value> result = module->Evaluate(context.local()).ToLocalChecked();
|
|
auto promise = Local<v8::Promise>::Cast(result);
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
|
|
Local<Object> ns = module->GetModuleNamespace().As<Object>();
|
|
Local<Value> closure =
|
|
ns->Get(context.local(), v8_str("default")).ToLocalChecked();
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(Function::Cast(*closure)
|
|
->Call(context.local(), v8::Undefined(isolate), 0, nullptr)
|
|
.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()->StrictEquals(v8_num(42)));
|
|
}
|
|
|
|
TEST(ImportMetaThrowHandled) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
isolate->SetHostInitializeImportMetaObjectCallback(
|
|
HostInitializeImportMetaObjectCallbackThrow);
|
|
|
|
Local<String> url = v8_str("www.google.com");
|
|
Local<String> source_text = v8_str(R"javascript(
|
|
export default function() {
|
|
try {
|
|
import.meta;
|
|
} catch {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
)javascript");
|
|
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
|
|
false, false, true);
|
|
v8::ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
|
|
module->InstantiateModule(context.local(), UnexpectedModuleResolveCallback)
|
|
.ToChecked();
|
|
|
|
Local<Value> result = module->Evaluate(context.local()).ToLocalChecked();
|
|
auto promise = Local<v8::Promise>::Cast(result);
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
|
|
Local<Object> ns = module->GetModuleNamespace().As<Object>();
|
|
Local<Value> closure =
|
|
ns->Get(context.local(), v8_str("default")).ToLocalChecked();
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(Function::Cast(*closure)
|
|
->Call(context.local(), v8::Undefined(isolate), 0, nullptr)
|
|
.ToLocalChecked()
|
|
->IsTrue());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
TEST(GetModuleNamespace) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<String> url = v8_str("www.google.com");
|
|
Local<String> source_text = v8_str("export default 5; export const a = 10;");
|
|
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
|
|
false, false, true);
|
|
v8::ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
|
|
module->InstantiateModule(context.local(), UnexpectedModuleResolveCallback)
|
|
.ToChecked();
|
|
module->Evaluate(context.local()).ToLocalChecked();
|
|
|
|
Local<Value> ns_val = module->GetModuleNamespace();
|
|
CHECK(ns_val->IsModuleNamespaceObject());
|
|
Local<Object> ns = ns_val.As<Object>();
|
|
CHECK(ns->Get(context.local(), v8_str("default"))
|
|
.ToLocalChecked()
|
|
->StrictEquals(v8::Number::New(isolate, 5)));
|
|
CHECK(ns->Get(context.local(), v8_str("a"))
|
|
.ToLocalChecked()
|
|
->StrictEquals(v8::Number::New(isolate, 10)));
|
|
}
|
|
|
|
TEST(ModuleGetUnboundModuleScript) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<String> url = v8_str("www.google.com");
|
|
Local<String> source_text = v8_str("export default 5; export const a = 10;");
|
|
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
|
|
false, false, true);
|
|
v8::ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
|
|
Local<v8::UnboundModuleScript> sfi_before_instantiation =
|
|
module->GetUnboundModuleScript();
|
|
module->InstantiateModule(context.local(), UnexpectedModuleResolveCallback)
|
|
.ToChecked();
|
|
Local<v8::UnboundModuleScript> sfi_after_instantiation =
|
|
module->GetUnboundModuleScript();
|
|
|
|
// Check object identity.
|
|
{
|
|
i::Handle<i::Object> s1 = v8::Utils::OpenHandle(*sfi_before_instantiation);
|
|
i::Handle<i::Object> s2 = v8::Utils::OpenHandle(*sfi_after_instantiation);
|
|
CHECK_EQ(*s1, *s2);
|
|
}
|
|
}
|
|
|
|
TEST(ModuleScriptId) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<String> url = v8_str("www.google.com");
|
|
Local<String> source_text = v8_str("export default 5; export const a = 10;");
|
|
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
|
|
false, false, true);
|
|
v8::ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
|
|
int id_before_instantiation = module->ScriptId();
|
|
module->InstantiateModule(context.local(), UnexpectedModuleResolveCallback)
|
|
.ToChecked();
|
|
int id_after_instantiation = module->ScriptId();
|
|
|
|
CHECK_EQ(id_before_instantiation, id_after_instantiation);
|
|
CHECK_NE(id_before_instantiation, v8::UnboundScript::kNoScriptId);
|
|
}
|
|
|
|
TEST(ModuleIsSourceTextModule) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
Local<String> url = v8_str("www.google.com");
|
|
Local<String> source_text = v8_str("export default 5; export const a = 10;");
|
|
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
|
|
false, false, true);
|
|
v8::ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> module =
|
|
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
|
|
CHECK(module->IsSourceTextModule());
|
|
CHECK(!module->IsSyntheticModule());
|
|
}
|
|
|
|
TEST(GlobalTemplateWithDoubleProperty) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
|
|
global->Set(isolate, "double", v8_num(3.14));
|
|
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
|
|
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
Local<Value> result = CompileRun("double");
|
|
CHECK(result->IsNumber());
|
|
CheckDoubleEquals(3.14, result->NumberValue(context).ToChecked());
|
|
}
|
|
|
|
TEST(PrimitiveArray) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
int length = 5;
|
|
Local<v8::PrimitiveArray> array(v8::PrimitiveArray::New(isolate, 5));
|
|
CHECK_EQ(length, array->Length());
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
Local<v8::Primitive> item = array->Get(isolate, i);
|
|
CHECK(item->IsUndefined());
|
|
}
|
|
|
|
Local<v8::Symbol> symbol(v8::Symbol::New(isolate));
|
|
array->Set(isolate, 0, symbol);
|
|
CHECK(array->Get(isolate, 0)->IsSymbol());
|
|
|
|
Local<v8::String> string = v8::String::NewFromUtf8Literal(
|
|
isolate, "test", v8::NewStringType::kInternalized);
|
|
array->Set(isolate, 1, string);
|
|
CHECK(array->Get(isolate, 0)->IsSymbol());
|
|
CHECK(array->Get(isolate, 1)->IsString());
|
|
|
|
Local<v8::Number> num = v8::Number::New(env->GetIsolate(), 3.1415926);
|
|
array->Set(isolate, 2, num);
|
|
CHECK(array->Get(isolate, 0)->IsSymbol());
|
|
CHECK(array->Get(isolate, 1)->IsString());
|
|
CHECK(array->Get(isolate, 2)->IsNumber());
|
|
|
|
v8::Local<v8::Boolean> f = v8::False(isolate);
|
|
array->Set(isolate, 3, f);
|
|
CHECK(array->Get(isolate, 0)->IsSymbol());
|
|
CHECK(array->Get(isolate, 1)->IsString());
|
|
CHECK(array->Get(isolate, 2)->IsNumber());
|
|
CHECK(array->Get(isolate, 3)->IsBoolean());
|
|
|
|
v8::Local<v8::Primitive> n = v8::Null(isolate);
|
|
array->Set(isolate, 4, n);
|
|
CHECK(array->Get(isolate, 0)->IsSymbol());
|
|
CHECK(array->Get(isolate, 1)->IsString());
|
|
CHECK(array->Get(isolate, 2)->IsNumber());
|
|
CHECK(array->Get(isolate, 3)->IsBoolean());
|
|
CHECK(array->Get(isolate, 4)->IsNull());
|
|
}
|
|
|
|
TEST(PersistentValueMap) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
v8::PersistentValueMap<
|
|
std::string, v8::Value,
|
|
v8::DefaultPersistentValueMapTraits<std::string, v8::Value>>
|
|
map(isolate);
|
|
v8::Local<v8::Value> value = v8::String::NewFromUtf8Literal(
|
|
isolate, "value", v8::NewStringType::kInternalized);
|
|
map.Set("key", value);
|
|
}
|
|
|
|
enum class AtomicsWaitCallbackAction {
|
|
Interrupt,
|
|
StopAndThrowInFirstCall,
|
|
StopAndThrowInSecondCall,
|
|
StopFromThreadAndThrow,
|
|
KeepWaiting
|
|
};
|
|
|
|
class StopAtomicsWaitThread;
|
|
|
|
struct AtomicsWaitCallbackInfo {
|
|
v8::Isolate* isolate;
|
|
v8::Isolate::AtomicsWaitWakeHandle* wake_handle;
|
|
std::unique_ptr<StopAtomicsWaitThread> stop_thread;
|
|
AtomicsWaitCallbackAction action;
|
|
|
|
Local<v8::SharedArrayBuffer> expected_sab;
|
|
v8::Isolate::AtomicsWaitEvent expected_event;
|
|
double expected_timeout;
|
|
int64_t expected_value;
|
|
size_t expected_offset;
|
|
|
|
size_t ncalls = 0;
|
|
};
|
|
|
|
class StopAtomicsWaitThread : public v8::base::Thread {
|
|
public:
|
|
explicit StopAtomicsWaitThread(AtomicsWaitCallbackInfo* info)
|
|
: Thread(Options("StopAtomicsWaitThread")), info_(info) {}
|
|
|
|
void Run() override {
|
|
CHECK_NOT_NULL(info_->wake_handle);
|
|
info_->wake_handle->Wake();
|
|
}
|
|
|
|
private:
|
|
AtomicsWaitCallbackInfo* info_;
|
|
};
|
|
|
|
void AtomicsWaitCallbackForTesting(
|
|
v8::Isolate::AtomicsWaitEvent event, Local<v8::SharedArrayBuffer> sab,
|
|
size_t offset_in_bytes, int64_t value, double timeout_in_ms,
|
|
v8::Isolate::AtomicsWaitWakeHandle* wake_handle, void* data) {
|
|
AtomicsWaitCallbackInfo* info = static_cast<AtomicsWaitCallbackInfo*>(data);
|
|
info->ncalls++;
|
|
info->wake_handle = wake_handle;
|
|
CHECK(sab->StrictEquals(info->expected_sab));
|
|
CHECK_EQ(timeout_in_ms, info->expected_timeout);
|
|
CHECK_EQ(value, info->expected_value);
|
|
CHECK_EQ(offset_in_bytes, info->expected_offset);
|
|
CHECK_EQ(v8::StateTag::ATOMICS_WAIT,
|
|
reinterpret_cast<i::Isolate*>(info->isolate)->current_vm_state());
|
|
|
|
auto ThrowSomething = [&]() {
|
|
info->isolate->ThrowException(v8::Integer::New(info->isolate, 42));
|
|
};
|
|
|
|
if (event == v8::Isolate::AtomicsWaitEvent::kStartWait) {
|
|
CHECK_NOT_NULL(wake_handle);
|
|
switch (info->action) {
|
|
case AtomicsWaitCallbackAction::Interrupt:
|
|
info->isolate->TerminateExecution();
|
|
break;
|
|
case AtomicsWaitCallbackAction::StopAndThrowInFirstCall:
|
|
ThrowSomething();
|
|
V8_FALLTHROUGH;
|
|
case AtomicsWaitCallbackAction::StopAndThrowInSecondCall:
|
|
wake_handle->Wake();
|
|
break;
|
|
case AtomicsWaitCallbackAction::StopFromThreadAndThrow:
|
|
info->stop_thread = std::make_unique<StopAtomicsWaitThread>(info);
|
|
CHECK(info->stop_thread->Start());
|
|
break;
|
|
case AtomicsWaitCallbackAction::KeepWaiting:
|
|
break;
|
|
}
|
|
} else {
|
|
CHECK_EQ(event, info->expected_event);
|
|
CHECK_NULL(wake_handle);
|
|
|
|
if (info->stop_thread) {
|
|
info->stop_thread->Join();
|
|
info->stop_thread.reset();
|
|
}
|
|
|
|
if (info->action == AtomicsWaitCallbackAction::StopAndThrowInSecondCall ||
|
|
info->action == AtomicsWaitCallbackAction::StopFromThreadAndThrow) {
|
|
ThrowSomething();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Must be called from within HandleScope
|
|
void AtomicsWaitCallbackCommon(v8::Isolate* isolate, Local<Value> sab,
|
|
size_t initial_offset,
|
|
size_t offset_multiplier) {
|
|
CHECK(sab->IsSharedArrayBuffer());
|
|
|
|
AtomicsWaitCallbackInfo info;
|
|
info.isolate = isolate;
|
|
info.expected_sab = sab.As<v8::SharedArrayBuffer>();
|
|
isolate->SetAtomicsWaitCallback(AtomicsWaitCallbackForTesting, &info);
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
info.expected_offset = initial_offset;
|
|
info.expected_timeout = std::numeric_limits<double>::infinity();
|
|
info.expected_value = 0;
|
|
info.expected_event = v8::Isolate::AtomicsWaitEvent::kTerminatedExecution;
|
|
info.action = AtomicsWaitCallbackAction::Interrupt;
|
|
info.ncalls = 0;
|
|
CompileRun("wait(0, 0);");
|
|
CHECK_EQ(info.ncalls, 2);
|
|
CHECK(try_catch.HasTerminated());
|
|
}
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
info.expected_offset = initial_offset + offset_multiplier;
|
|
info.expected_timeout = std::numeric_limits<double>::infinity();
|
|
info.expected_value = 1;
|
|
info.expected_event = v8::Isolate::AtomicsWaitEvent::kNotEqual;
|
|
info.action = AtomicsWaitCallbackAction::KeepWaiting;
|
|
info.ncalls = 0;
|
|
CompileRun("wait(1, 1);"); // real value is 0 != 1
|
|
CHECK_EQ(info.ncalls, 2);
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
info.expected_offset = initial_offset + offset_multiplier;
|
|
info.expected_timeout = 0.125;
|
|
info.expected_value = 0;
|
|
info.expected_event = v8::Isolate::AtomicsWaitEvent::kTimedOut;
|
|
info.action = AtomicsWaitCallbackAction::KeepWaiting;
|
|
info.ncalls = 0;
|
|
CompileRun("wait(1, 0, 0.125);"); // timeout
|
|
CHECK_EQ(info.ncalls, 2);
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
info.expected_offset = initial_offset + offset_multiplier;
|
|
info.expected_timeout = std::numeric_limits<double>::infinity();
|
|
info.expected_value = 0;
|
|
info.expected_event = v8::Isolate::AtomicsWaitEvent::kAPIStopped;
|
|
info.action = AtomicsWaitCallbackAction::StopAndThrowInFirstCall;
|
|
info.ncalls = 0;
|
|
CompileRun("wait(1, 0);");
|
|
CHECK_EQ(info.ncalls, 1); // Only one extra call
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()->IsInt32());
|
|
CHECK_EQ(try_catch.Exception().As<v8::Int32>()->Value(), 42);
|
|
}
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
info.expected_offset = initial_offset + offset_multiplier;
|
|
info.expected_timeout = std::numeric_limits<double>::infinity();
|
|
info.expected_value = 0;
|
|
info.expected_event = v8::Isolate::AtomicsWaitEvent::kAPIStopped;
|
|
info.action = AtomicsWaitCallbackAction::StopAndThrowInSecondCall;
|
|
info.ncalls = 0;
|
|
CompileRun("wait(1, 0);");
|
|
CHECK_EQ(info.ncalls, 2);
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()->IsInt32());
|
|
CHECK_EQ(try_catch.Exception().As<v8::Int32>()->Value(), 42);
|
|
}
|
|
|
|
{
|
|
// Same test as before, but with a different `expected_value`.
|
|
v8::TryCatch try_catch(isolate);
|
|
info.expected_offset = initial_offset + offset_multiplier;
|
|
info.expected_timeout = std::numeric_limits<double>::infinity();
|
|
info.expected_value = 200;
|
|
info.expected_event = v8::Isolate::AtomicsWaitEvent::kAPIStopped;
|
|
info.action = AtomicsWaitCallbackAction::StopAndThrowInSecondCall;
|
|
info.ncalls = 0;
|
|
CompileRun(
|
|
"setArrayElemAs(1, 200);"
|
|
"wait(1, 200);");
|
|
CHECK_EQ(info.ncalls, 2);
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()->IsInt32());
|
|
CHECK_EQ(try_catch.Exception().As<v8::Int32>()->Value(), 42);
|
|
}
|
|
|
|
{
|
|
// Wake the `Atomics.wait()` call from a thread.
|
|
v8::TryCatch try_catch(isolate);
|
|
info.expected_offset = initial_offset;
|
|
info.expected_timeout = std::numeric_limits<double>::infinity();
|
|
info.expected_value = 0;
|
|
info.expected_event = v8::Isolate::AtomicsWaitEvent::kAPIStopped;
|
|
info.action = AtomicsWaitCallbackAction::StopFromThreadAndThrow;
|
|
info.ncalls = 0;
|
|
CompileRun(
|
|
"setArrayElemAs(1, 0);"
|
|
"wait(0, 0);");
|
|
CHECK_EQ(info.ncalls, 2);
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()->IsInt32());
|
|
CHECK_EQ(try_catch.Exception().As<v8::Int32>()->Value(), 42);
|
|
}
|
|
}
|
|
|
|
TEST(AtomicsWaitCallback) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
const char* init = R"(
|
|
let sab = new SharedArrayBuffer(16);
|
|
let int32arr = new Int32Array(sab, 4);
|
|
let setArrayElemAs = function(id, val) {
|
|
int32arr[id] = val;
|
|
};
|
|
let wait = function(id, val, timeout) {
|
|
if(arguments.length == 2) return Atomics.wait(int32arr, id, val);
|
|
return Atomics.wait(int32arr, id, val, timeout);
|
|
};
|
|
sab;)";
|
|
AtomicsWaitCallbackCommon(isolate, CompileRun(init), 4, 4);
|
|
}
|
|
|
|
#if V8_ENABLE_WEBASSEMBLY
|
|
namespace v8 {
|
|
namespace internal {
|
|
namespace wasm {
|
|
TEST(WasmI32AtomicWaitCallback) {
|
|
FlagScope<bool> wasm_threads_flag(&i::FLAG_experimental_wasm_threads, true);
|
|
WasmRunner<int32_t, int32_t, int32_t, double> r(TestExecutionTier::kTurbofan);
|
|
r.builder().AddMemory(kWasmPageSize, SharedFlag::kShared);
|
|
r.builder().SetHasSharedMemory();
|
|
BUILD(r, WASM_ATOMICS_WAIT(kExprI32AtomicWait, WASM_LOCAL_GET(0),
|
|
WASM_LOCAL_GET(1),
|
|
WASM_I64_SCONVERT_F64(WASM_LOCAL_GET(2)), 4));
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
Handle<JSFunction> func = r.builder().WrapCode(0);
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("func"), v8::Utils::ToLocal(func))
|
|
.FromJust());
|
|
Handle<JSArrayBuffer> memory(
|
|
r.builder().instance_object()->memory_object().array_buffer(), i_isolate);
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("sab"), v8::Utils::ToLocal(memory))
|
|
.FromJust());
|
|
|
|
const char* init = R"(
|
|
let int32arr = new Int32Array(sab, 4);
|
|
let setArrayElemAs = function(id, val) {
|
|
int32arr[id] = val;
|
|
};
|
|
let wait = function(id, val, timeout) {
|
|
if(arguments.length === 2)
|
|
return func(id << 2, val, -1);
|
|
return func(id << 2, val, timeout*1000000);
|
|
};
|
|
sab;)";
|
|
AtomicsWaitCallbackCommon(isolate, CompileRun(init), 4, 4);
|
|
}
|
|
|
|
TEST(WasmI64AtomicWaitCallback) {
|
|
FlagScope<bool> wasm_threads_flag(&i::FLAG_experimental_wasm_threads, true);
|
|
WasmRunner<int32_t, int32_t, double, double> r(TestExecutionTier::kTurbofan);
|
|
r.builder().AddMemory(kWasmPageSize, SharedFlag::kShared);
|
|
r.builder().SetHasSharedMemory();
|
|
BUILD(r, WASM_ATOMICS_WAIT(kExprI64AtomicWait, WASM_LOCAL_GET(0),
|
|
WASM_I64_SCONVERT_F64(WASM_LOCAL_GET(1)),
|
|
WASM_I64_SCONVERT_F64(WASM_LOCAL_GET(2)), 8));
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
Handle<JSFunction> func = r.builder().WrapCode(0);
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("func"), v8::Utils::ToLocal(func))
|
|
.FromJust());
|
|
Handle<JSArrayBuffer> memory(
|
|
r.builder().instance_object()->memory_object().array_buffer(), i_isolate);
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("sab"), v8::Utils::ToLocal(memory))
|
|
.FromJust());
|
|
|
|
const char* init = R"(
|
|
let int64arr = new BigInt64Array(sab, 8);
|
|
let setArrayElemAs = function(id, val) {
|
|
int64arr[id] = BigInt(val);
|
|
};
|
|
let wait = function(id, val, timeout) {
|
|
if(arguments.length === 2)
|
|
return func(id << 3, val, -1);
|
|
return func(id << 3, val, timeout*1000000);
|
|
};
|
|
sab;)";
|
|
AtomicsWaitCallbackCommon(isolate, CompileRun(init), 8, 8);
|
|
}
|
|
|
|
} // namespace wasm
|
|
} // namespace internal
|
|
} // namespace v8
|
|
#endif // V8_ENABLE_WEBASSEMBLY
|
|
|
|
TEST(BigIntAPI) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
bool lossless;
|
|
uint64_t words1[10];
|
|
uint64_t words2[10];
|
|
|
|
{
|
|
Local<Value> bi = CompileRun("12n");
|
|
CHECK(bi->IsBigInt());
|
|
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Uint64Value(), 12);
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Uint64Value(&lossless), 12);
|
|
CHECK_EQ(lossless, true);
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Int64Value(), 12);
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Int64Value(&lossless), 12);
|
|
CHECK_EQ(lossless, true);
|
|
}
|
|
|
|
{
|
|
Local<Value> bi = CompileRun("-12n");
|
|
CHECK(bi->IsBigInt());
|
|
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Uint64Value(), static_cast<uint64_t>(-12));
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Uint64Value(&lossless),
|
|
static_cast<uint64_t>(-12));
|
|
CHECK_EQ(lossless, false);
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Int64Value(), -12);
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Int64Value(&lossless), -12);
|
|
CHECK_EQ(lossless, true);
|
|
}
|
|
|
|
{
|
|
Local<Value> bi = CompileRun("123456789012345678901234567890n");
|
|
CHECK(bi->IsBigInt());
|
|
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Uint64Value(), 14083847773837265618ULL);
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Uint64Value(&lossless),
|
|
14083847773837265618ULL);
|
|
CHECK_EQ(lossless, false);
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Int64Value(), -4362896299872285998LL);
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Int64Value(&lossless),
|
|
-4362896299872285998LL);
|
|
CHECK_EQ(lossless, false);
|
|
}
|
|
|
|
{
|
|
Local<Value> bi = CompileRun("-123456789012345678901234567890n");
|
|
CHECK(bi->IsBigInt());
|
|
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Uint64Value(), 4362896299872285998LL);
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Uint64Value(&lossless),
|
|
4362896299872285998LL);
|
|
CHECK_EQ(lossless, false);
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Int64Value(), 4362896299872285998LL);
|
|
CHECK_EQ(bi.As<v8::BigInt>()->Int64Value(&lossless), 4362896299872285998LL);
|
|
CHECK_EQ(lossless, false);
|
|
}
|
|
|
|
{
|
|
Local<v8::BigInt> bi =
|
|
v8::BigInt::NewFromWords(env.local(), 0, 0, words1).ToLocalChecked();
|
|
CHECK_EQ(bi->Uint64Value(), 0);
|
|
CHECK_EQ(bi->WordCount(), 0);
|
|
}
|
|
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
v8::MaybeLocal<v8::BigInt> bi = v8::BigInt::NewFromWords(
|
|
env.local(), 0, std::numeric_limits<int>::max(), words1);
|
|
CHECK(bi.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
v8::MaybeLocal<v8::BigInt> bi =
|
|
v8::BigInt::NewFromWords(env.local(), 0, -1, words1);
|
|
CHECK(bi.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
{
|
|
TryCatch try_catch(isolate);
|
|
v8::MaybeLocal<v8::BigInt> bi =
|
|
v8::BigInt::NewFromWords(env.local(), 0, 1 << 30, words1);
|
|
CHECK(bi.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
}
|
|
|
|
for (int sign_bit = 0; sign_bit <= 1; sign_bit++) {
|
|
words1[0] = 0xffffffff00000000ULL;
|
|
words1[1] = 0x00000000ffffffffULL;
|
|
v8::Local<v8::BigInt> bi =
|
|
v8::BigInt::NewFromWords(env.local(), sign_bit, 2, words1)
|
|
.ToLocalChecked();
|
|
CHECK_EQ(bi->Uint64Value(&lossless),
|
|
sign_bit ? static_cast<uint64_t>(-static_cast<int64_t>(words1[0]))
|
|
: words1[0]);
|
|
CHECK_EQ(lossless, false);
|
|
CHECK_EQ(bi->Int64Value(&lossless), sign_bit
|
|
? -static_cast<int64_t>(words1[0])
|
|
: static_cast<int64_t>(words1[0]));
|
|
CHECK_EQ(lossless, false);
|
|
CHECK_EQ(bi->WordCount(), 2);
|
|
int real_sign_bit;
|
|
int word_count = arraysize(words2);
|
|
bi->ToWordsArray(&real_sign_bit, &word_count, words2);
|
|
CHECK_EQ(real_sign_bit, sign_bit);
|
|
CHECK_EQ(word_count, 2);
|
|
}
|
|
}
|
|
|
|
TEST(GetJSEntryStubs) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
|
|
v8::JSEntryStubs entry_stubs = isolate->GetJSEntryStubs();
|
|
|
|
v8::JSEntryStub entry_stub = entry_stubs.js_entry_stub;
|
|
CHECK_EQ(i_isolate->builtins()->code(i::Builtin::kJSEntry).InstructionStart(),
|
|
reinterpret_cast<i::Address>(entry_stub.code.start));
|
|
|
|
v8::JSEntryStub construct_stub = entry_stubs.js_construct_entry_stub;
|
|
CHECK_EQ(i_isolate->builtins()
|
|
->code(i::Builtin::kJSConstructEntry)
|
|
.InstructionStart(),
|
|
reinterpret_cast<i::Address>(construct_stub.code.start));
|
|
|
|
v8::JSEntryStub microtask_stub = entry_stubs.js_run_microtasks_entry_stub;
|
|
CHECK_EQ(i_isolate->builtins()
|
|
->code(i::Builtin::kJSRunMicrotasksEntry)
|
|
.InstructionStart(),
|
|
reinterpret_cast<i::Address>(microtask_stub.code.start));
|
|
}
|
|
|
|
TEST(MicrotaskContextShouldBeNativeContext) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
auto callback = [](const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
i::Handle<i::Context> context =
|
|
v8::Utils::OpenHandle(*isolate->GetEnteredOrMicrotaskContext());
|
|
|
|
CHECK(context->IsNativeContext());
|
|
info.GetReturnValue().SetUndefined();
|
|
};
|
|
|
|
Local<v8::FunctionTemplate> desc = v8::FunctionTemplate::New(isolate);
|
|
desc->InstanceTemplate()->SetCallAsFunctionHandler(callback);
|
|
Local<v8::Object> obj = desc->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked();
|
|
|
|
CHECK(env->Global()->Set(env.local(), v8_str("callback"), obj).FromJust());
|
|
CompileRun(
|
|
"with({}){(async ()=>{"
|
|
" await 42;"
|
|
"})().then(callback);}");
|
|
|
|
isolate->PerformMicrotaskCheckpoint();
|
|
}
|
|
|
|
TEST(PreviewSetKeysIteratorEntriesWithDeleted) {
|
|
LocalContext env;
|
|
v8::HandleScope handle_scope(env->GetIsolate());
|
|
v8::Local<v8::Context> context = env.local();
|
|
|
|
{
|
|
// Create set, delete entry, create iterator, preview.
|
|
v8::Local<v8::Object> iterator =
|
|
CompileRun("var set = new Set([1,2,3]); set.delete(1); set.keys()")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(2, entries->Length());
|
|
CHECK_EQ(2, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
CHECK_EQ(3, entries->Get(context, 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create set, create iterator, delete entry, preview.
|
|
v8::Local<v8::Object> iterator =
|
|
CompileRun("var set = new Set([1,2,3]); set.keys()")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("set.delete(1);");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(2, entries->Length());
|
|
CHECK_EQ(2, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
CHECK_EQ(3, entries->Get(context, 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create set, create iterator, delete entry, iterate, preview.
|
|
v8::Local<v8::Object> iterator =
|
|
CompileRun("var set = new Set([1,2,3]); var it = set.keys(); it")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("set.delete(1); it.next();");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(1, entries->Length());
|
|
CHECK_EQ(3, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create set, create iterator, delete entry, iterate until empty, preview.
|
|
v8::Local<v8::Object> iterator =
|
|
CompileRun("var set = new Set([1,2,3]); var it = set.keys(); it")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("set.delete(1); it.next(); it.next();");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(0, entries->Length());
|
|
}
|
|
{
|
|
// Create set, create iterator, delete entry, iterate, trigger rehash,
|
|
// preview.
|
|
v8::Local<v8::Object> iterator =
|
|
CompileRun("var set = new Set([1,2,3]); var it = set.keys(); it")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("set.delete(1); it.next();");
|
|
CompileRun("for (var i = 4; i < 20; i++) set.add(i);");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(17, entries->Length());
|
|
for (uint32_t i = 0; i < 17; i++) {
|
|
CHECK_EQ(i + 3, entries->Get(context, i)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(PreviewSetValuesIteratorEntriesWithDeleted) {
|
|
LocalContext env;
|
|
v8::HandleScope handle_scope(env->GetIsolate());
|
|
v8::Local<v8::Context> context = env.local();
|
|
|
|
{
|
|
// Create set, delete entry, create iterator, preview.
|
|
v8::Local<v8::Object> iterator =
|
|
CompileRun("var set = new Set([1,2,3]); set.delete(1); set.values()")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(2, entries->Length());
|
|
CHECK_EQ(2, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
CHECK_EQ(3, entries->Get(context, 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create set, create iterator, delete entry, preview.
|
|
v8::Local<v8::Object> iterator =
|
|
CompileRun("var set = new Set([1,2,3]); set.values()")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("set.delete(1);");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(2, entries->Length());
|
|
CHECK_EQ(2, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
CHECK_EQ(3, entries->Get(context, 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create set, create iterator, delete entry, iterate, preview.
|
|
v8::Local<v8::Object> iterator =
|
|
CompileRun("var set = new Set([1,2,3]); var it = set.values(); it")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("set.delete(1); it.next();");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(1, entries->Length());
|
|
CHECK_EQ(3, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create set, create iterator, delete entry, iterate until empty, preview.
|
|
v8::Local<v8::Object> iterator =
|
|
CompileRun("var set = new Set([1,2,3]); var it = set.values(); it")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("set.delete(1); it.next(); it.next();");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(0, entries->Length());
|
|
}
|
|
{
|
|
// Create set, create iterator, delete entry, iterate, trigger rehash,
|
|
// preview.
|
|
v8::Local<v8::Object> iterator =
|
|
CompileRun("var set = new Set([1,2,3]); var it = set.values(); it")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("set.delete(1); it.next();");
|
|
CompileRun("for (var i = 4; i < 20; i++) set.add(i);");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(17, entries->Length());
|
|
for (uint32_t i = 0; i < 17; i++) {
|
|
CHECK_EQ(i + 3, entries->Get(context, i)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(PreviewMapEntriesIteratorEntries) {
|
|
LocalContext env;
|
|
v8::HandleScope handle_scope(env->GetIsolate());
|
|
v8::Local<v8::Context> context = env.local();
|
|
{
|
|
// Create set, delete entry, create entries iterator, preview.
|
|
v8::Local<v8::Object> iterator =
|
|
CompileRun("var set = new Set([1,2,3]); set.delete(2); set.entries()")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(is_key);
|
|
CHECK_EQ(4, entries->Length());
|
|
uint32_t first = entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust();
|
|
uint32_t second = entries->Get(context, 2)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust();
|
|
CHECK_EQ(1, first);
|
|
CHECK_EQ(3, second);
|
|
CHECK_EQ(first, entries->Get(context, 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
CHECK_EQ(second, entries->Get(context, 3)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
TEST(PreviewMapValuesIteratorEntriesWithDeleted) {
|
|
LocalContext env;
|
|
v8::HandleScope handle_scope(env->GetIsolate());
|
|
v8::Local<v8::Context> context = env.local();
|
|
|
|
{
|
|
// Create map, delete entry, create iterator, preview.
|
|
v8::Local<v8::Object> iterator = CompileRun(
|
|
"var map = new Map();"
|
|
"var key = {}; map.set(key, 1);"
|
|
"map.set({}, 2); map.set({}, 3);"
|
|
"map.delete(key);"
|
|
"map.values()")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(2, entries->Length());
|
|
CHECK_EQ(2, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
CHECK_EQ(3, entries->Get(context, 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create map, create iterator, delete entry, preview.
|
|
v8::Local<v8::Object> iterator = CompileRun(
|
|
"var map = new Map();"
|
|
"var key = {}; map.set(key, 1);"
|
|
"map.set({}, 2); map.set({}, 3);"
|
|
"map.values()")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("map.delete(key);");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(2, entries->Length());
|
|
CHECK_EQ(2, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
CHECK_EQ(3, entries->Get(context, 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create map, create iterator, delete entry, iterate, preview.
|
|
v8::Local<v8::Object> iterator = CompileRun(
|
|
"var map = new Map();"
|
|
"var key = {}; map.set(key, 1);"
|
|
"map.set({}, 2); map.set({}, 3);"
|
|
"var it = map.values(); it")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("map.delete(key); it.next();");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(1, entries->Length());
|
|
CHECK_EQ(3, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create map, create iterator, delete entry, iterate until empty, preview.
|
|
v8::Local<v8::Object> iterator = CompileRun(
|
|
"var map = new Map();"
|
|
"var key = {}; map.set(key, 1);"
|
|
"map.set({}, 2); map.set({}, 3);"
|
|
"var it = map.values(); it")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("map.delete(key); it.next(); it.next();");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(0, entries->Length());
|
|
}
|
|
{
|
|
// Create map, create iterator, delete entry, iterate, trigger rehash,
|
|
// preview.
|
|
v8::Local<v8::Object> iterator = CompileRun(
|
|
"var map = new Map();"
|
|
"var key = {}; map.set(key, 1);"
|
|
"map.set({}, 2); map.set({}, 3);"
|
|
"var it = map.values(); it")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("map.delete(key); it.next();");
|
|
CompileRun("for (var i = 4; i < 20; i++) map.set({}, i);");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(17, entries->Length());
|
|
for (uint32_t i = 0; i < 17; i++) {
|
|
CHECK_EQ(i + 3, entries->Get(context, i)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(PreviewMapKeysIteratorEntriesWithDeleted) {
|
|
LocalContext env;
|
|
v8::HandleScope handle_scope(env->GetIsolate());
|
|
v8::Local<v8::Context> context = env.local();
|
|
|
|
{
|
|
// Create map, delete entry, create iterator, preview.
|
|
v8::Local<v8::Object> iterator = CompileRun(
|
|
"var map = new Map();"
|
|
"var key = 1; map.set(key, {});"
|
|
"map.set(2, {}); map.set(3, {});"
|
|
"map.delete(key);"
|
|
"map.keys()")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(2, entries->Length());
|
|
CHECK_EQ(2, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
CHECK_EQ(3, entries->Get(context, 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create map, create iterator, delete entry, preview.
|
|
v8::Local<v8::Object> iterator = CompileRun(
|
|
"var map = new Map();"
|
|
"var key = 1; map.set(key, {});"
|
|
"map.set(2, {}); map.set(3, {});"
|
|
"map.keys()")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("map.delete(key);");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(2, entries->Length());
|
|
CHECK_EQ(2, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
CHECK_EQ(3, entries->Get(context, 1)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create map, create iterator, delete entry, iterate, preview.
|
|
v8::Local<v8::Object> iterator = CompileRun(
|
|
"var map = new Map();"
|
|
"var key = 1; map.set(key, {});"
|
|
"map.set(2, {}); map.set(3, {});"
|
|
"var it = map.keys(); it")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("map.delete(key); it.next();");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(1, entries->Length());
|
|
CHECK_EQ(3, entries->Get(context, 0)
|
|
.ToLocalChecked()
|
|
->Int32Value(context)
|
|
.FromJust());
|
|
}
|
|
{
|
|
// Create map, create iterator, delete entry, iterate until empty, preview.
|
|
v8::Local<v8::Object> iterator = CompileRun(
|
|
"var map = new Map();"
|
|
"var key = 1; map.set(key, {});"
|
|
"map.set(2, {}); map.set(3, {});"
|
|
"var it = map.keys(); it")
|
|
->ToObject(context)
|
|
.ToLocalChecked();
|
|
CompileRun("map.delete(key); it.next(); it.next();");
|
|
bool is_key;
|
|
v8::Local<v8::Array> entries =
|
|
iterator->PreviewEntries(&is_key).ToLocalChecked();
|
|
CHECK(!is_key);
|
|
CHECK_EQ(0, entries->Length());
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
static v8::Isolate* isolate_1;
|
|
static v8::Isolate* isolate_2;
|
|
v8::Persistent<v8::Context> context_1;
|
|
v8::Persistent<v8::Context> context_2;
|
|
|
|
static void CallIsolate1(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::Isolate::Scope isolate_scope(isolate_1);
|
|
v8::HandleScope handle_scope(isolate_1);
|
|
v8::Local<v8::Context> context =
|
|
v8::Local<v8::Context>::New(isolate_1, context_1);
|
|
v8::Context::Scope context_scope(context);
|
|
CompileRun("f1() //# sourceURL=isolate1b");
|
|
}
|
|
|
|
static void CallIsolate2(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::Isolate::Scope isolate_scope(isolate_2);
|
|
v8::HandleScope handle_scope(isolate_2);
|
|
v8::Local<v8::Context> context =
|
|
v8::Local<v8::Context>::New(isolate_2, context_2);
|
|
v8::Context::Scope context_scope(context);
|
|
reinterpret_cast<i::Isolate*>(isolate_2)->heap()->CollectAllGarbage(
|
|
i::Heap::kForcedGC, i::GarbageCollectionReason::kTesting,
|
|
v8::kNoGCCallbackFlags);
|
|
CompileRun("f2() //# sourceURL=isolate2b");
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
UNINITIALIZED_TEST(NestedIsolates) {
|
|
#ifdef VERIFY_HEAP
|
|
i::FLAG_verify_heap = true;
|
|
#endif // VERIFY_HEAP
|
|
// Create two isolates and set up C++ functions via function templates that
|
|
// call into the other isolate. Recurse a few times, trigger GC along the way,
|
|
// and finally capture a stack trace. Check that the stack trace only includes
|
|
// frames from its own isolate.
|
|
i::FLAG_stack_trace_limit = 20;
|
|
i::FLAG_experimental_stack_trace_frames = true;
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
isolate_1 = v8::Isolate::New(create_params);
|
|
isolate_2 = v8::Isolate::New(create_params);
|
|
|
|
{
|
|
v8::Isolate::Scope isolate_scope(isolate_1);
|
|
v8::HandleScope handle_scope(isolate_1);
|
|
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate_1);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
v8::Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(isolate_1, CallIsolate2);
|
|
fun_templ->SetClassName(v8_str(isolate_1, "call_isolate_2"));
|
|
Local<Function> fun = fun_templ->GetFunction(context).ToLocalChecked();
|
|
CHECK(context->Global()
|
|
->Set(context, v8_str(isolate_1, "call_isolate_2"), fun)
|
|
.FromJust());
|
|
CompileRun(
|
|
"let c = 0;"
|
|
"function f1() {"
|
|
" c++;"
|
|
" return call_isolate_2();"
|
|
"} //# sourceURL=isolate1a");
|
|
context_1.Reset(isolate_1, context);
|
|
}
|
|
|
|
{
|
|
v8::Isolate::Scope isolate_scope(isolate_2);
|
|
v8::HandleScope handle_scope(isolate_2);
|
|
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate_2);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
v8::Local<v8::FunctionTemplate> fun_templ =
|
|
v8::FunctionTemplate::New(isolate_2, CallIsolate1);
|
|
fun_templ->SetClassName(v8_str(isolate_2, "call_isolate_1"));
|
|
Local<Function> fun = fun_templ->GetFunction(context).ToLocalChecked();
|
|
|
|
CHECK(context->Global()
|
|
->Set(context, v8_str(isolate_2, "call_isolate_1"), fun)
|
|
.FromJust());
|
|
CompileRun(
|
|
"let c = 4;"
|
|
"let result = undefined;"
|
|
"function f2() {"
|
|
" if (c-- > 0) return call_isolate_1();"
|
|
" else result = new Error().stack;"
|
|
"} //# sourceURL=isolate2a");
|
|
context_2.Reset(isolate_2, context);
|
|
|
|
v8::Local<v8::String> result =
|
|
CompileRun("f2(); result //# sourceURL=isolate2c")
|
|
->ToString(context)
|
|
.ToLocalChecked();
|
|
v8::Local<v8::String> expectation =
|
|
v8_str(isolate_2,
|
|
"Error\n"
|
|
" at f2 (isolate2a:1:104)\n"
|
|
" at isolate2b:1:1\n"
|
|
" at call_isolate_1 (<anonymous>)\n"
|
|
" at f2 (isolate2a:1:71)\n"
|
|
" at isolate2b:1:1\n"
|
|
" at call_isolate_1 (<anonymous>)\n"
|
|
" at f2 (isolate2a:1:71)\n"
|
|
" at isolate2b:1:1\n"
|
|
" at call_isolate_1 (<anonymous>)\n"
|
|
" at f2 (isolate2a:1:71)\n"
|
|
" at isolate2b:1:1\n"
|
|
" at call_isolate_1 (<anonymous>)\n"
|
|
" at f2 (isolate2a:1:71)\n"
|
|
" at isolate2c:1:1");
|
|
CHECK(result->StrictEquals(expectation));
|
|
}
|
|
|
|
{
|
|
v8::Isolate::Scope isolate_scope(isolate_1);
|
|
v8::HandleScope handle_scope(isolate_1);
|
|
v8::Local<v8::Context> context =
|
|
v8::Local<v8::Context>::New(isolate_1, context_1);
|
|
v8::Context::Scope context_scope(context);
|
|
ExpectInt32("c", 4);
|
|
}
|
|
|
|
isolate_1->Dispose();
|
|
isolate_2->Dispose();
|
|
}
|
|
|
|
#undef THREADED_PROFILED_TEST
|
|
|
|
#ifndef V8_LITE_MODE
|
|
namespace {
|
|
|
|
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
|
|
template <typename Value>
|
|
Value PrimitiveFromMixedType(v8::AnyCType argument);
|
|
|
|
template <>
|
|
bool PrimitiveFromMixedType(v8::AnyCType argument) {
|
|
return argument.bool_value;
|
|
}
|
|
template <>
|
|
int32_t PrimitiveFromMixedType(v8::AnyCType argument) {
|
|
return argument.int32_value;
|
|
}
|
|
template <>
|
|
uint32_t PrimitiveFromMixedType(v8::AnyCType argument) {
|
|
return argument.uint32_value;
|
|
}
|
|
template <>
|
|
int64_t PrimitiveFromMixedType(v8::AnyCType argument) {
|
|
return argument.int64_value;
|
|
}
|
|
template <>
|
|
uint64_t PrimitiveFromMixedType(v8::AnyCType argument) {
|
|
return argument.uint64_value;
|
|
}
|
|
template <>
|
|
float PrimitiveFromMixedType(v8::AnyCType argument) {
|
|
return argument.float_value;
|
|
}
|
|
template <>
|
|
double PrimitiveFromMixedType(v8::AnyCType argument) {
|
|
return argument.double_value;
|
|
}
|
|
template <>
|
|
v8::Local<v8::Value> PrimitiveFromMixedType(v8::AnyCType argument) {
|
|
return argument.object_value;
|
|
}
|
|
|
|
template <typename T>
|
|
v8::AnyCType PrimitiveToMixedType(T value) {
|
|
return v8::AnyCType();
|
|
}
|
|
|
|
template <>
|
|
v8::AnyCType PrimitiveToMixedType(bool value) {
|
|
v8::AnyCType ret;
|
|
ret.bool_value = value;
|
|
return ret;
|
|
}
|
|
template <>
|
|
v8::AnyCType PrimitiveToMixedType(int32_t value) {
|
|
v8::AnyCType ret;
|
|
ret.int32_value = value;
|
|
return ret;
|
|
}
|
|
template <>
|
|
v8::AnyCType PrimitiveToMixedType(uint32_t value) {
|
|
v8::AnyCType ret;
|
|
ret.uint32_value = value;
|
|
return ret;
|
|
}
|
|
template <>
|
|
v8::AnyCType PrimitiveToMixedType(float value) {
|
|
v8::AnyCType ret;
|
|
ret.float_value = value;
|
|
return ret;
|
|
}
|
|
template <>
|
|
v8::AnyCType PrimitiveToMixedType(double value) {
|
|
v8::AnyCType ret;
|
|
ret.double_value = value;
|
|
return ret;
|
|
}
|
|
|
|
#endif // V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
|
|
|
|
template <typename Value, typename Impl, typename Ret>
|
|
struct BasicApiChecker {
|
|
static Ret FastCallback(v8::Local<v8::Object> receiver, Value argument,
|
|
v8::FastApiCallbackOptions& options) {
|
|
// TODO(mslekova): Refactor the data checking.
|
|
v8::Value* data = &(options.data);
|
|
CHECK(data->IsNumber());
|
|
CHECK_EQ(v8::Number::Cast(data)->Value(), 42.0);
|
|
return Impl::FastCallback(receiver, argument, options);
|
|
}
|
|
static Ret FastCallbackNoFallback(v8::Local<v8::Object> receiver,
|
|
Value argument) {
|
|
v8::FastApiCallbackOptions options = {false, {0}};
|
|
return Impl::FastCallback(receiver, argument, options);
|
|
}
|
|
|
|
static void SlowCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
Impl::SlowCallback(info);
|
|
}
|
|
|
|
bool DidCallFast() const { return (result_ & ApiCheckerResult::kFastCalled); }
|
|
bool DidCallSlow() const { return (result_ & ApiCheckerResult::kSlowCalled); }
|
|
|
|
void SetCallFast() { result_ |= ApiCheckerResult::kFastCalled; }
|
|
void SetCallSlow() { result_ |= ApiCheckerResult::kSlowCalled; }
|
|
|
|
void Reset() { result_ = ApiCheckerResult::kNotCalled; }
|
|
|
|
private:
|
|
ApiCheckerResultFlags result_ = ApiCheckerResult::kNotCalled;
|
|
};
|
|
|
|
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
|
|
template <typename Value, typename Impl, typename Ret,
|
|
typename = std::enable_if_t<!std::is_void<Ret>::value>>
|
|
static v8::AnyCType FastCallbackPatch(v8::AnyCType receiver,
|
|
v8::AnyCType argument,
|
|
v8::AnyCType options) {
|
|
v8::AnyCType ret = PrimitiveToMixedType<Ret>(Impl::FastCallback(
|
|
receiver.object_value, PrimitiveFromMixedType<Value>(argument),
|
|
*(options.options_value)));
|
|
return ret;
|
|
}
|
|
template <typename Value, typename Impl, typename Ret,
|
|
typename = std::enable_if_t<!std::is_void<Ret>::value>>
|
|
static v8::AnyCType FastCallbackNoFallbackWrapper(v8::AnyCType receiver,
|
|
v8::AnyCType argument) {
|
|
v8::FastApiCallbackOptions options = {false, {0}};
|
|
v8::AnyCType ret = PrimitiveToMixedType<Ret>(Impl::FastCallback(
|
|
receiver.object_value, PrimitiveFromMixedType<Value>(argument), options));
|
|
return ret;
|
|
}
|
|
template <typename Value, typename Impl, typename Ret,
|
|
typename = std::enable_if_t<std::is_void<Ret>::value>>
|
|
static void FastCallbackPatch(v8::AnyCType receiver, v8::AnyCType argument,
|
|
v8::AnyCType options) {
|
|
return Impl::FastCallback(receiver.object_value,
|
|
PrimitiveFromMixedType<Value>(argument),
|
|
*(options.options_value));
|
|
}
|
|
template <typename Value, typename Impl, typename Ret,
|
|
typename = std::enable_if_t<std::is_void<Ret>::value>>
|
|
static void FastCallbackNoFallbackWrapper(v8::AnyCType receiver,
|
|
v8::AnyCType argument) {
|
|
v8::FastApiCallbackOptions options = {false, {0}};
|
|
return Impl::FastCallback(receiver.object_value,
|
|
PrimitiveFromMixedType<Value>(argument), options);
|
|
}
|
|
#endif // V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
|
|
|
|
enum class Behavior {
|
|
kNoException,
|
|
kException, // An exception should be thrown by the callback function.
|
|
};
|
|
|
|
enum class FallbackPolicy {
|
|
kDontRequestFallback,
|
|
kRequestFallback, // The callback function should write a non-zero value
|
|
// to the fallback variable.
|
|
};
|
|
|
|
template <typename T>
|
|
struct ApiNumberChecker : BasicApiChecker<T, ApiNumberChecker<T>, void> {
|
|
explicit ApiNumberChecker(
|
|
T value, Behavior raise_exception = Behavior::kNoException,
|
|
FallbackPolicy write_to_fallback = FallbackPolicy::kDontRequestFallback,
|
|
int args_count = 1)
|
|
: raise_exception_(raise_exception),
|
|
write_to_fallback_(write_to_fallback),
|
|
args_count_(args_count) {}
|
|
|
|
static void FastCallback(v8::Local<v8::Object> receiver, T argument,
|
|
v8::FastApiCallbackOptions& options) {
|
|
v8::Object* receiver_obj = *receiver;
|
|
if (!IsValidUnwrapObject(receiver_obj)) {
|
|
options.fallback = 1;
|
|
return;
|
|
}
|
|
ApiNumberChecker<T>* receiver_ptr =
|
|
GetInternalField<ApiNumberChecker<T>>(receiver_obj);
|
|
receiver_ptr->SetCallFast();
|
|
receiver_ptr->fast_value_ = argument;
|
|
if (receiver_ptr->write_to_fallback_ == FallbackPolicy::kRequestFallback) {
|
|
// Anything != 0 has the same effect here, but we're writing 1 to match
|
|
// the default behavior expected from the embedder. The value is checked
|
|
// against after loading it from a stack slot, as defined in
|
|
// EffectControlLinearizer::LowerFastApiCall.
|
|
CHECK_EQ(options.fallback, 0);
|
|
options.fallback = 1;
|
|
}
|
|
}
|
|
|
|
static void SlowCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
v8::Object* receiver = v8::Object::Cast(*info.Holder());
|
|
if (!IsValidUnwrapObject(receiver)) {
|
|
info.GetIsolate()->ThrowException(v8_str("Called with a non-object."));
|
|
return;
|
|
}
|
|
ApiNumberChecker<T>* checker =
|
|
GetInternalField<ApiNumberChecker<T>>(receiver);
|
|
CHECK_EQ(info.Length(), checker->args_count_);
|
|
|
|
checker->SetCallSlow();
|
|
|
|
LocalContext env;
|
|
checker->slow_value_ = ConvertJSValue<T>::Get(info[0], env.local());
|
|
|
|
if (checker->raise_exception_ == Behavior::kException) {
|
|
CHECK(checker->write_to_fallback_ == FallbackPolicy::kRequestFallback);
|
|
info.GetIsolate()->ThrowException(v8_str("Callback error"));
|
|
}
|
|
}
|
|
|
|
T fast_value_ = T();
|
|
Maybe<T> slow_value_ = v8::Nothing<T>();
|
|
Behavior raise_exception_ = Behavior::kNoException;
|
|
FallbackPolicy write_to_fallback_ = FallbackPolicy::kDontRequestFallback;
|
|
int args_count_ = 1;
|
|
};
|
|
|
|
struct UnexpectedObjectChecker
|
|
: BasicApiChecker<v8::Local<v8::Value>, UnexpectedObjectChecker, void> {
|
|
static void FastCallback(v8::Local<v8::Object> receiver,
|
|
v8::Local<v8::Value> argument,
|
|
v8::FastApiCallbackOptions& options) {
|
|
UnexpectedObjectChecker* receiver_ptr =
|
|
GetInternalField<UnexpectedObjectChecker>(*receiver);
|
|
receiver_ptr->SetCallFast();
|
|
if (argument->IsObject()) {
|
|
v8::Object* argument_obj = v8::Object::Cast(*argument);
|
|
CHECK(!IsValidUnwrapObject(argument_obj));
|
|
}
|
|
}
|
|
|
|
static void SlowCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
v8::Object* receiver_obj = v8::Object::Cast(*info.Holder());
|
|
UnexpectedObjectChecker* receiver_ptr =
|
|
GetInternalField<UnexpectedObjectChecker>(receiver_obj);
|
|
receiver_ptr->SetCallSlow();
|
|
if (info[0]->IsObject()) {
|
|
v8::Object* argument_obj = v8::Object::Cast(*info[0]);
|
|
CHECK(!IsValidUnwrapObject(argument_obj));
|
|
}
|
|
}
|
|
};
|
|
|
|
struct EmbedderType {
|
|
int data;
|
|
};
|
|
|
|
struct ApiObjectChecker
|
|
: BasicApiChecker<v8::Local<v8::Value>, ApiObjectChecker, void> {
|
|
ApiObjectChecker(v8::FunctionTemplate* ctor, int data)
|
|
: ctor_(ctor), initial_data_(data) {}
|
|
|
|
static void FastCallback(v8::Local<v8::Object> receiver,
|
|
v8::Local<v8::Value> argument,
|
|
v8::FastApiCallbackOptions& options) {
|
|
ApiObjectChecker* receiver_ptr =
|
|
GetInternalField<ApiObjectChecker>(*receiver);
|
|
receiver_ptr->SetCallFast();
|
|
|
|
v8::Object* argument_obj = v8::Object::Cast(*argument);
|
|
EmbedderType* argument_ptr = GetInternalField<EmbedderType>(argument_obj);
|
|
CHECK(receiver_ptr->ctor_->IsLeafTemplateForApiObject(argument));
|
|
|
|
argument_ptr->data = receiver_ptr->initial_data_;
|
|
}
|
|
static void SlowCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
v8::Object* receiver_obj = v8::Object::Cast(*info.Holder());
|
|
ApiObjectChecker* receiver_ptr =
|
|
GetInternalField<ApiObjectChecker>(receiver_obj);
|
|
receiver_ptr->SetCallSlow();
|
|
|
|
CHECK(info[0]->IsObject());
|
|
v8::Local<v8::Object> argument_obj = info[0].As<v8::Object>();
|
|
CHECK(receiver_ptr->ctor_->IsLeafTemplateForApiObject(argument_obj));
|
|
}
|
|
|
|
v8::FunctionTemplate* ctor_;
|
|
int fast_value_ = 0;
|
|
int initial_data_;
|
|
};
|
|
|
|
template <typename Value, typename Impl, typename Ret>
|
|
bool SetupTest(v8::Local<v8::Value> initial_value, LocalContext* env,
|
|
BasicApiChecker<Value, Impl, Ret>* checker,
|
|
const char* source_code, bool supports_fallback = true,
|
|
bool accept_any_receiver = true) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
v8::CFunction c_func;
|
|
if (supports_fallback) {
|
|
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
|
|
c_func =
|
|
v8::CFunction::Make(BasicApiChecker<Value, Impl, Ret>::FastCallback,
|
|
FastCallbackPatch<Value, Impl, Ret>);
|
|
#else // V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
|
|
c_func =
|
|
v8::CFunction::Make(BasicApiChecker<Value, Impl, Ret>::FastCallback);
|
|
#endif // V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
|
|
} else {
|
|
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
|
|
c_func = v8::CFunction::Make(
|
|
BasicApiChecker<Value, Impl, Ret>::FastCallbackNoFallback,
|
|
FastCallbackNoFallbackWrapper<Value, Impl, Ret>);
|
|
#else // V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
|
|
c_func = v8::CFunction::Make(
|
|
BasicApiChecker<Value, Impl, Ret>::FastCallbackNoFallback);
|
|
#endif // V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
|
|
}
|
|
CHECK_EQ(c_func.ArgumentInfo(0).GetType(), v8::CTypeInfo::Type::kV8Value);
|
|
|
|
Local<v8::FunctionTemplate> checker_templ = v8::FunctionTemplate::New(
|
|
isolate, BasicApiChecker<Value, Impl, Ret>::SlowCallback,
|
|
v8::Number::New(isolate, 42), v8::Local<v8::Signature>(), 1,
|
|
v8::ConstructorBehavior::kThrow, v8::SideEffectType::kHasSideEffect,
|
|
&c_func);
|
|
if (!accept_any_receiver) {
|
|
checker_templ->SetAcceptAnyReceiver(false);
|
|
}
|
|
|
|
v8::Local<v8::ObjectTemplate> object_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
object_template->SetInternalFieldCount(kV8WrapperObjectIndex + 1);
|
|
object_template->Set(isolate, "api_func", checker_templ);
|
|
|
|
v8::Local<v8::Object> object =
|
|
object_template->NewInstance(env->local()).ToLocalChecked();
|
|
object->SetAlignedPointerInInternalField(kV8WrapperObjectIndex,
|
|
reinterpret_cast<void*>(checker));
|
|
|
|
CHECK((*env)
|
|
->Global()
|
|
->Set(env->local(), v8_str("receiver"), object)
|
|
.FromJust());
|
|
CHECK((*env)
|
|
->Global()
|
|
->Set(env->local(), v8_str("value"), initial_value)
|
|
.FromJust());
|
|
USE(CompileRun(source_code));
|
|
return try_catch.HasCaught();
|
|
}
|
|
|
|
template <typename T>
|
|
void CheckEqual(T actual, T expected) {
|
|
CHECK_EQ(actual, expected);
|
|
}
|
|
|
|
template <>
|
|
void CheckEqual<float>(float actual, float expected) {
|
|
if (std::isnan(expected)) {
|
|
CHECK(std::isnan(actual));
|
|
} else {
|
|
// This differentiates between -0 and +0.
|
|
CHECK_EQ(std::signbit(actual), std::signbit(expected));
|
|
CHECK_EQ(actual, expected);
|
|
}
|
|
}
|
|
|
|
template <>
|
|
void CheckEqual<double>(double actual, double expected) {
|
|
if (std::isnan(expected)) {
|
|
CHECK(std::isnan(actual));
|
|
} else {
|
|
// This differentiates between -0 and +0.
|
|
CHECK_EQ(std::signbit(actual), std::signbit(expected));
|
|
CHECK_EQ(actual, expected);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void CallAndCheck(
|
|
T expected_value, Behavior expected_behavior,
|
|
ApiCheckerResultFlags expected_path, v8::Local<v8::Value> initial_value,
|
|
Behavior raise_exception = Behavior::kNoException,
|
|
FallbackPolicy write_to_fallback = FallbackPolicy::kDontRequestFallback) {
|
|
LocalContext env;
|
|
ApiNumberChecker<T> checker(expected_value, raise_exception,
|
|
write_to_fallback);
|
|
|
|
bool has_caught = SetupTest<T, ApiNumberChecker<T>, void>(
|
|
initial_value, &env, &checker,
|
|
"function func(arg) { return receiver.api_func(arg); }"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"func(value);");
|
|
checker.Reset();
|
|
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::TryCatch try_catch(isolate);
|
|
v8::Local<v8::Value> result = CompileRun(
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func(value);");
|
|
if (!try_catch.HasCaught()) {
|
|
CHECK(result->IsUndefined());
|
|
}
|
|
|
|
CHECK_EQ(expected_behavior == Behavior::kException, has_caught);
|
|
CHECK_EQ(expected_path == ApiCheckerResult::kSlowCalled,
|
|
!checker.DidCallFast());
|
|
CHECK_EQ(expected_path == ApiCheckerResult::kFastCalled,
|
|
!checker.DidCallSlow());
|
|
|
|
if (expected_path & ApiCheckerResult::kSlowCalled) {
|
|
CHECK(checker.DidCallSlow());
|
|
if (expected_behavior != Behavior::kException) {
|
|
CheckEqual(checker.slow_value_.ToChecked(), expected_value);
|
|
}
|
|
}
|
|
if (expected_path & ApiCheckerResult::kFastCalled) {
|
|
CHECK(checker.DidCallFast());
|
|
CheckEqual(checker.fast_value_, expected_value);
|
|
}
|
|
}
|
|
|
|
void CheckApiObjectArg() {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
Local<v8::FunctionTemplate> api_obj_ctor = v8::FunctionTemplate::New(isolate);
|
|
v8::Local<v8::ObjectTemplate> api_obj_template =
|
|
api_obj_ctor->InstanceTemplate();
|
|
api_obj_template->SetInternalFieldCount(kV8WrapperObjectIndex + 1);
|
|
|
|
EmbedderType embedder_obj;
|
|
v8::Local<v8::Object> api_obj =
|
|
api_obj_template->NewInstance(env.local()).ToLocalChecked();
|
|
api_obj->SetAlignedPointerInInternalField(
|
|
kV8WrapperObjectIndex, reinterpret_cast<void*>(&embedder_obj));
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("api_object"), api_obj)
|
|
.FromJust());
|
|
|
|
const int data = 42;
|
|
ApiObjectChecker checker(*api_obj_ctor, data);
|
|
bool has_caught =
|
|
SetupTest(v8_num(data), &env, &checker,
|
|
"function func() { return receiver.api_func(api_object); }"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"func();");
|
|
checker.Reset();
|
|
CHECK(!has_caught);
|
|
|
|
CompileRun(
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func();");
|
|
|
|
CHECK(checker.DidCallFast());
|
|
CHECK_EQ(embedder_obj.data, data);
|
|
CHECK(!checker.DidCallSlow());
|
|
}
|
|
|
|
static const char* fast_calls_error_message = nullptr;
|
|
static const char* fast_calls_error_location = nullptr;
|
|
void FastCallsErrorCallback(const char* location, const char* message) {
|
|
fast_calls_error_message = message;
|
|
fast_calls_error_location = location;
|
|
}
|
|
|
|
void CheckFastCallsWithConstructor() {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
CcTest::isolate()->SetFatalErrorHandler(FastCallsErrorCallback);
|
|
|
|
CHECK_NULL(fast_calls_error_message);
|
|
|
|
v8::CFunction c_func_ctor =
|
|
v8::CFunction::Make(ApiObjectChecker::FastCallback);
|
|
v8::FunctionTemplate::New(isolate, ApiObjectChecker::SlowCallback,
|
|
Local<v8::Value>(), v8::Local<v8::Signature>(), 1,
|
|
v8::ConstructorBehavior::kAllow,
|
|
v8::SideEffectType::kHasSideEffect, &c_func_ctor);
|
|
CHECK_NOT_NULL(fast_calls_error_message);
|
|
CHECK_EQ(
|
|
0, strcmp(fast_calls_error_message,
|
|
"Fast API calls are not supported for constructor functions."));
|
|
CHECK_NOT_NULL(fast_calls_error_location);
|
|
CHECK_EQ(0, strcmp(fast_calls_error_location, "FunctionTemplate::New"));
|
|
|
|
fast_calls_error_message = nullptr;
|
|
const v8::CFunction c_func_ctor_overloads[] = {c_func_ctor};
|
|
v8::FunctionTemplate::NewWithCFunctionOverloads(
|
|
isolate, ApiObjectChecker::SlowCallback, Local<v8::Value>(),
|
|
v8::Local<v8::Signature>(), 1, v8::ConstructorBehavior::kAllow,
|
|
v8::SideEffectType::kHasSideEffect, {c_func_ctor_overloads, 1});
|
|
CHECK_NOT_NULL(fast_calls_error_message);
|
|
CHECK_EQ(
|
|
0, strcmp(fast_calls_error_message,
|
|
"Fast API calls are not supported for constructor functions."));
|
|
CHECK_NOT_NULL(fast_calls_error_location);
|
|
CHECK_EQ(0, strcmp(fast_calls_error_location,
|
|
"FunctionTemplate::NewWithCFunctionOverloads"));
|
|
}
|
|
|
|
template <typename T>
|
|
struct ReturnValueChecker : BasicApiChecker<T, ReturnValueChecker<T>, T> {
|
|
static T FastCallback(v8::Local<v8::Object> receiver, T arg,
|
|
v8::FastApiCallbackOptions& options) {
|
|
ReturnValueChecker<T>* receiver_ptr =
|
|
GetInternalField<ReturnValueChecker<T>>(*receiver);
|
|
receiver_ptr->SetCallFast();
|
|
return arg;
|
|
}
|
|
|
|
static void SlowCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
v8::Object* receiver_obj = v8::Object::Cast(*info.Holder());
|
|
ReturnValueChecker<T>* receiver_ptr =
|
|
GetInternalField<ReturnValueChecker<T>>(receiver_obj);
|
|
receiver_ptr->SetCallSlow();
|
|
info.GetReturnValue().Set(info[0]);
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
void CheckFastReturnValue(v8::Local<v8::Value> expected_value,
|
|
ApiCheckerResultFlags expected_path) {
|
|
LocalContext env;
|
|
ReturnValueChecker<T> checker{};
|
|
|
|
bool has_caught = SetupTest<T, ReturnValueChecker<T>, T>(
|
|
expected_value, &env, &checker,
|
|
"function func(arg) { return receiver.api_func(arg); }"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"func(value);");
|
|
CHECK(!has_caught);
|
|
checker.Reset();
|
|
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::TryCatch try_catch(isolate);
|
|
v8::Local<v8::Value> result = CompileRun(
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func(value);");
|
|
|
|
CHECK_EQ(expected_path == ApiCheckerResult::kSlowCalled,
|
|
!checker.DidCallFast());
|
|
CHECK_EQ(expected_path == ApiCheckerResult::kFastCalled,
|
|
!checker.DidCallSlow());
|
|
CHECK(checker.DidCallFast() || checker.DidCallSlow());
|
|
|
|
CHECK(result->SameValue(expected_value));
|
|
}
|
|
|
|
void CallAndDeopt() {
|
|
LocalContext env;
|
|
v8::Local<v8::Value> initial_value(v8_num(42));
|
|
ApiNumberChecker<int32_t> checker(42);
|
|
SetupTest(initial_value, &env, &checker,
|
|
"function func(arg) { return receiver.api_func(arg); }"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"func(value);"
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func(value);");
|
|
|
|
v8::Local<v8::Value> function = CompileRun(
|
|
"try { func(BigInt(42)); } catch(e) {}"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func(value);"
|
|
"func;");
|
|
CHECK(function->IsFunction());
|
|
i::Handle<i::JSFunction> ifunction =
|
|
i::Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*function));
|
|
CHECK(ifunction->HasAttachedOptimizedCode());
|
|
}
|
|
|
|
void CallNoFallback(int32_t expected_value) {
|
|
LocalContext env;
|
|
v8::Local<v8::Value> initial_value(v8_num(42));
|
|
ApiNumberChecker<int32_t> checker(expected_value, Behavior::kNoException);
|
|
SetupTest(initial_value, &env, &checker,
|
|
"function func(arg) { return receiver.api_func(arg); }"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"func(value);"
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func(value);",
|
|
false);
|
|
|
|
CHECK(checker.DidCallFast());
|
|
CHECK_EQ(checker.fast_value_, expected_value);
|
|
}
|
|
|
|
void CallNoConvertReceiver(int32_t expected_value) {
|
|
LocalContext env;
|
|
v8::Local<v8::Value> initial_value(v8_num(42));
|
|
ApiNumberChecker<int32_t> checker(expected_value, Behavior::kNoException);
|
|
SetupTest(initial_value, &env, &checker,
|
|
"function func(arg) { return receiver.api_func(arg); }"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"func(value);"
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func(value);",
|
|
true, false);
|
|
|
|
CHECK(checker.DidCallFast());
|
|
CHECK_EQ(checker.fast_value_, expected_value);
|
|
}
|
|
|
|
void CallWithLessArguments() {
|
|
LocalContext env;
|
|
v8::Local<v8::Value> initial_value(v8_num(42));
|
|
ApiNumberChecker<int32_t> checker(42, Behavior::kNoException,
|
|
FallbackPolicy::kDontRequestFallback, 0);
|
|
SetupTest(initial_value, &env, &checker,
|
|
"function func() { return receiver.api_func(); }"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"func();"
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func();");
|
|
|
|
// Passing not enough arguments should not go through the fast path.
|
|
CHECK(checker.DidCallSlow());
|
|
}
|
|
|
|
void CallWithMoreArguments() {
|
|
LocalContext env;
|
|
v8::Local<v8::Value> initial_value(v8_num(42));
|
|
ApiNumberChecker<int32_t> checker(42, Behavior::kNoException,
|
|
FallbackPolicy::kDontRequestFallback, 2);
|
|
SetupTest(initial_value, &env, &checker,
|
|
"function func(arg) { receiver.api_func(arg, arg); }"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"func(value);"
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func(value);");
|
|
|
|
// Passing too many arguments should just ignore the extra ones.
|
|
CHECK(checker.DidCallFast());
|
|
}
|
|
|
|
void CallWithUnexpectedReceiverType(v8::Local<v8::Value> receiver) {
|
|
LocalContext env;
|
|
ApiNumberChecker<int32_t> checker(42);
|
|
bool has_caught =
|
|
SetupTest(receiver, &env, &checker,
|
|
"function func(arg) { receiver.api_func.apply(value, arg); }"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"func(value);"
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func(value);");
|
|
CHECK(has_caught);
|
|
// The slow and fast callbacks were called actually, but aborted early.
|
|
CHECK(!checker.DidCallSlow());
|
|
CHECK(!checker.DidCallFast());
|
|
}
|
|
|
|
void CallWithUnexpectedObjectType(v8::Local<v8::Value> receiver) {
|
|
LocalContext env;
|
|
UnexpectedObjectChecker checker;
|
|
SetupTest(receiver, &env, &checker,
|
|
"function func(arg) { receiver.api_func(arg); }"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"func(value);"
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func(value);");
|
|
CHECK(checker.DidCallFast());
|
|
CHECK(checker.DidCallSlow());
|
|
}
|
|
|
|
class TestCFunctionInfo : public v8::CFunctionInfo {
|
|
static constexpr unsigned int kArgCount = 2u;
|
|
|
|
public:
|
|
TestCFunctionInfo()
|
|
: v8::CFunctionInfo(v8::CTypeInfo(v8::CTypeInfo::Type::kVoid), kArgCount,
|
|
arg_info_storage_),
|
|
arg_info_storage_{
|
|
v8::CTypeInfo(v8::CTypeInfo::Type::kV8Value),
|
|
v8::CTypeInfo(v8::CTypeInfo::Type::kBool),
|
|
} {}
|
|
|
|
private:
|
|
const v8::CTypeInfo arg_info_storage_[kArgCount];
|
|
};
|
|
|
|
void CheckDynamicTypeInfo() {
|
|
LocalContext env;
|
|
|
|
static TestCFunctionInfo type_info;
|
|
v8::CFunction c_func = v8::CFunction(
|
|
reinterpret_cast<const void*>(ApiNumberChecker<bool>::FastCallback),
|
|
&type_info);
|
|
CHECK_EQ(c_func.ArgumentCount(), 2);
|
|
CHECK_EQ(c_func.ArgumentInfo(0).GetType(), v8::CTypeInfo::Type::kV8Value);
|
|
CHECK_EQ(c_func.ArgumentInfo(1).GetType(), v8::CTypeInfo::Type::kBool);
|
|
CHECK_EQ(c_func.ReturnInfo().GetType(), v8::CTypeInfo::Type::kVoid);
|
|
}
|
|
} // namespace
|
|
#endif // V8_LITE_MODE
|
|
|
|
TEST(FastApiStackSlot) {
|
|
#ifndef V8_LITE_MODE
|
|
if (i::FLAG_jitless) return;
|
|
|
|
v8::internal::FLAG_opt = true;
|
|
v8::internal::FLAG_turbo_fast_api_calls = true;
|
|
v8::internal::FLAG_allow_natives_syntax = true;
|
|
// Disable --always_opt, otherwise we haven't generated the necessary
|
|
// feedback to go down the "best optimization" path for the fast call.
|
|
v8::internal::FLAG_always_opt = false;
|
|
v8::internal::FlagList::EnforceFlagImplications();
|
|
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
i_isolate->set_embedder_wrapper_type_index(kV8WrapperTypeIndex);
|
|
i_isolate->set_embedder_wrapper_object_index(kV8WrapperObjectIndex);
|
|
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
int test_value = 42;
|
|
ApiNumberChecker<int32_t> checker(test_value, Behavior::kNoException,
|
|
FallbackPolicy::kRequestFallback);
|
|
|
|
bool has_caught = SetupTest<int32_t, ApiNumberChecker<int32_t>>(
|
|
v8_num(test_value), &env, &checker,
|
|
"function func(arg) {"
|
|
" let foo = 128;"
|
|
" for (let i = 0; i < 100; ++i) {"
|
|
" let bar = true;"
|
|
" if (i == 10) %OptimizeOsr();"
|
|
" try { receiver.api_func(arg) } catch(_) {};"
|
|
" try { receiver.api_func(arg) } catch(_) {};"
|
|
" };"
|
|
" return foo;"
|
|
"};");
|
|
checker.Reset();
|
|
|
|
v8::TryCatch try_catch(isolate);
|
|
v8::Local<v8::Value> foo =
|
|
CompileRun("%PrepareFunctionForOptimization(func); func(value);");
|
|
CHECK(foo->IsNumber());
|
|
CHECK_EQ(128, foo->ToInt32(env.local()).ToLocalChecked()->Value());
|
|
|
|
CHECK(checker.DidCallFast() && checker.DidCallSlow());
|
|
CHECK_EQ(false, has_caught);
|
|
int32_t slow_value_typed = checker.slow_value_.ToChecked();
|
|
CHECK_EQ(slow_value_typed, test_value);
|
|
CHECK_EQ(checker.fast_value_, test_value);
|
|
#endif
|
|
}
|
|
|
|
TEST(FastApiCalls) {
|
|
#ifndef V8_LITE_MODE
|
|
if (i::FLAG_jitless) return;
|
|
|
|
v8::internal::FLAG_opt = true;
|
|
v8::internal::FLAG_turbo_fast_api_calls = true;
|
|
v8::internal::FLAG_allow_natives_syntax = true;
|
|
// Disable --always_opt, otherwise we haven't generated the necessary
|
|
// feedback to go down the "best optimization" path for the fast call.
|
|
v8::internal::FLAG_always_opt = false;
|
|
v8::internal::FlagList::EnforceFlagImplications();
|
|
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
i_isolate->set_embedder_wrapper_type_index(kV8WrapperTypeIndex);
|
|
i_isolate->set_embedder_wrapper_object_index(kV8WrapperObjectIndex);
|
|
|
|
v8::HandleScope scope(isolate);
|
|
LocalContext env;
|
|
|
|
// Main cases (the value fits in the type)
|
|
CallAndCheck<int32_t>(-42, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(-42));
|
|
CallAndCheck<uint32_t>(i::Smi::kMaxValue, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(i::Smi::kMaxValue));
|
|
CallAndCheck<bool>(false, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8::Boolean::New(isolate, false));
|
|
CallAndCheck<bool>(true, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8::Boolean::New(isolate, true));
|
|
|
|
#ifdef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
|
|
CallAndCheck<float>(3.14f, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(3.14));
|
|
CallAndCheck<double>(3.14, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(3.14));
|
|
#else
|
|
CallAndCheck<float>(3.14f, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled, v8_num(3.14));
|
|
CallAndCheck<double>(3.14, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled, v8_num(3.14));
|
|
#endif
|
|
|
|
// Corner cases (the value is out of bounds or of different type) - int32_t
|
|
CallAndCheck<int32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(-0.0));
|
|
CallAndCheck<int32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<double>::quiet_NaN()));
|
|
CallAndCheck<int32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<double>::infinity()));
|
|
CallAndCheck<int32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled, v8_str("some_string"));
|
|
CallAndCheck<int32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
CompileRun("new Proxy({}, {});"));
|
|
CallAndCheck<int32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8::Object::New(isolate));
|
|
CallAndCheck<int32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled, v8::Array::New(isolate));
|
|
CallAndCheck<int32_t>(0, Behavior::kException, ApiCheckerResult::kSlowCalled,
|
|
v8::BigInt::New(isolate, 42));
|
|
CallAndCheck<int32_t>(std::numeric_limits<int32_t>::min(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<int32_t>::min()));
|
|
CallAndCheck<int32_t>(
|
|
std::numeric_limits<int32_t>::min(), Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(static_cast<double>(std::numeric_limits<int32_t>::max()) + 1));
|
|
|
|
CallAndCheck<int32_t>(3, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(3.14));
|
|
|
|
// Corner cases - uint32_t
|
|
CallAndCheck<uint32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(-0.0));
|
|
CallAndCheck<uint32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<double>::quiet_NaN()));
|
|
CallAndCheck<uint32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<double>::infinity()));
|
|
CallAndCheck<uint32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled, v8_str("some_string"));
|
|
CallAndCheck<uint32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8::Object::New(isolate));
|
|
CallAndCheck<uint32_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8::Array::New(isolate));
|
|
CallAndCheck<uint32_t>(0, Behavior::kException, ApiCheckerResult::kSlowCalled,
|
|
v8::BigInt::New(isolate, 42));
|
|
CallAndCheck<uint32_t>(std::numeric_limits<uint32_t>::min(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<uint32_t>::max() + 1));
|
|
CallAndCheck<uint32_t>(3, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(3.14));
|
|
|
|
// Both 32- and 64-bit platforms should execute the following tests
|
|
// through the slow path.
|
|
// Corner cases - int64
|
|
CallAndCheck<int64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled, v8_num(std::pow(2, 65)));
|
|
CallAndCheck<int64_t>(8192, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(std::pow(2, 65) + 8192));
|
|
CallAndCheck<int64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(std::pow(2, 1023)));
|
|
CallAndCheck<int64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(std::numeric_limits<double>::quiet_NaN()));
|
|
CallAndCheck<int64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(std::numeric_limits<double>::infinity()));
|
|
CallAndCheck<int64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled, v8_str("some_string"));
|
|
CallAndCheck<int64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
CompileRun("new Proxy({}, {});"));
|
|
CallAndCheck<int64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8::Object::New(isolate));
|
|
CallAndCheck<int64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled, v8::Array::New(isolate));
|
|
CallAndCheck<int64_t>(0, Behavior::kException, ApiCheckerResult::kSlowCalled,
|
|
v8::BigInt::New(isolate, 42));
|
|
CallAndCheck<int64_t>(3, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled, v8_num(3.14));
|
|
CallAndCheck<int64_t>(
|
|
0, Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8_num(static_cast<double>(std::numeric_limits<int64_t>::max()) * 2 +
|
|
3.14));
|
|
CallAndCheck<int64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(static_cast<double>(1ull << 63) * 2));
|
|
CallAndCheck<int64_t>(4096, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(static_cast<double>(1ull << 63) * 2 + 4096));
|
|
CallAndCheck<int64_t>(std::numeric_limits<int64_t>::min() + 4096,
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8_num(static_cast<double>(1ull << 63) * 3 + 4096));
|
|
|
|
// Corner cases - uint64_t
|
|
CallAndCheck<uint64_t>(static_cast<double>(1ull << 63) * 2 - 2048,
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8_num(static_cast<double>(1ull << 63) * 2 - 2048));
|
|
// TODO(mslekova): We deopt for unsafe integers, but ultimately we want to
|
|
// stay on the fast path.
|
|
CallAndCheck<uint64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(static_cast<double>(1ull << 63) * 2));
|
|
CallAndCheck<uint64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(std::numeric_limits<double>::quiet_NaN()));
|
|
CallAndCheck<uint64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(std::numeric_limits<double>::infinity()));
|
|
CallAndCheck<uint64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled, v8_str("some_string"));
|
|
CallAndCheck<uint64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
CompileRun("new Proxy({}, {});"));
|
|
CallAndCheck<uint64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8::Object::New(isolate));
|
|
CallAndCheck<uint64_t>(0, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8::Array::New(isolate));
|
|
CallAndCheck<uint64_t>(0, Behavior::kException, ApiCheckerResult::kSlowCalled,
|
|
v8::BigInt::New(isolate, 42));
|
|
CallAndCheck<uint64_t>(3, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled, v8_num(3.14));
|
|
CallAndCheck<uint64_t>(4096, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(static_cast<double>(1ull << 63) * 2 + 4096));
|
|
CallAndCheck<uint64_t>(static_cast<double>(1ull << 63) + 4096,
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8_num(static_cast<double>(1ull << 63) * 3 + 4096));
|
|
|
|
// The following int64/uint64 tests are platform-dependent, because Turbofan
|
|
// currently doesn't support 64-bit integers on 32-bit architectures. So if
|
|
// we attempt to follow the fast path on them, this leads to unsupported
|
|
// situations, e.g. attempting to call IA32OperandConverter::ToImmediate
|
|
// for a 64-bit operand.
|
|
#ifdef V8_TARGET_ARCH_64_BIT
|
|
ApiCheckerResult expected_path_for_64bit_test = ApiCheckerResult::kFastCalled;
|
|
#else
|
|
ApiCheckerResult expected_path_for_64bit_test = ApiCheckerResult::kSlowCalled;
|
|
#endif
|
|
// Corner cases - int64
|
|
CallAndCheck<int64_t>(static_cast<int64_t>(i::Smi::kMaxValue) + 1,
|
|
Behavior::kNoException, expected_path_for_64bit_test,
|
|
v8_num(static_cast<int64_t>(i::Smi::kMaxValue) + 1));
|
|
CallAndCheck<int64_t>(std::numeric_limits<int64_t>::min(),
|
|
Behavior::kNoException, expected_path_for_64bit_test,
|
|
v8_num(std::numeric_limits<int64_t>::min()));
|
|
CallAndCheck<int64_t>(1ll << 62, Behavior::kNoException,
|
|
expected_path_for_64bit_test, v8_num(1ll << 62));
|
|
CallAndCheck<int64_t>(i::kMaxSafeInteger, Behavior::kNoException,
|
|
expected_path_for_64bit_test,
|
|
v8_num(i::kMaxSafeInteger));
|
|
CallAndCheck<int64_t>(-i::kMaxSafeInteger, Behavior::kNoException,
|
|
expected_path_for_64bit_test,
|
|
v8_num(-i::kMaxSafeInteger));
|
|
CallAndCheck<int64_t>((1ull << 63) - 1024, Behavior::kNoException,
|
|
expected_path_for_64bit_test,
|
|
v8_num((1ull << 63) - 1024));
|
|
CallAndCheck<int64_t>(0, Behavior::kNoException, expected_path_for_64bit_test,
|
|
v8_num(-0.0));
|
|
|
|
// Corner cases - uint64_t
|
|
CallAndCheck<uint64_t>(static_cast<uint64_t>(i::Smi::kMaxValue) + 1,
|
|
Behavior::kNoException, expected_path_for_64bit_test,
|
|
v8_num(static_cast<uint64_t>(i::Smi::kMaxValue) + 1));
|
|
CallAndCheck<uint64_t>(std::numeric_limits<uint64_t>::min(),
|
|
Behavior::kNoException, expected_path_for_64bit_test,
|
|
v8_num(std::numeric_limits<uint64_t>::min()));
|
|
CallAndCheck<uint64_t>(1ll << 62, Behavior::kNoException,
|
|
expected_path_for_64bit_test, v8_num(1ll << 62));
|
|
CallAndCheck<uint64_t>(
|
|
std::numeric_limits<uint64_t>::max() - ((1ll << 62) - 1),
|
|
Behavior::kNoException, expected_path_for_64bit_test,
|
|
v8_num(-(1ll << 62)));
|
|
CallAndCheck<uint64_t>(i::kMaxSafeIntegerUint64, Behavior::kNoException,
|
|
expected_path_for_64bit_test,
|
|
v8_num(i::kMaxSafeInteger));
|
|
CallAndCheck<uint64_t>(
|
|
std::numeric_limits<uint64_t>::max() - (i::kMaxSafeIntegerUint64 - 1),
|
|
Behavior::kNoException, expected_path_for_64bit_test,
|
|
v8_num(-i::kMaxSafeInteger));
|
|
CallAndCheck<uint64_t>(0, Behavior::kNoException,
|
|
expected_path_for_64bit_test, v8_num(-0.0));
|
|
|
|
// TODO(mslekova): We deopt for unsafe integers, but ultimately we want to
|
|
// stay on the fast path.
|
|
CallAndCheck<int64_t>(std::numeric_limits<int64_t>::min(),
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8_num(1ull << 63));
|
|
CallAndCheck<int64_t>(
|
|
std::numeric_limits<int64_t>::min(), Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(static_cast<double>(std::numeric_limits<int64_t>::max()) + 3.14));
|
|
CallAndCheck<int64_t>(std::numeric_limits<int64_t>::min(),
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8_num(static_cast<double>(1ull << 63)));
|
|
CallAndCheck<int64_t>(std::numeric_limits<int64_t>::min(),
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8_num(-static_cast<double>(1ll << 63)));
|
|
CallAndCheck<uint64_t>(1ull << 63, Behavior::kNoException,
|
|
ApiCheckerResult::kSlowCalled,
|
|
v8_num(static_cast<double>(1ull << 63)));
|
|
|
|
// Corner cases - float and double
|
|
#ifdef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
|
|
// Source:
|
|
// https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limitations_on_integer_values
|
|
constexpr float kMaxSafeFloat = 16777215; // 2^24-1
|
|
CallAndCheck<float>(std::numeric_limits<float>::min(), Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<float>::min()));
|
|
CallAndCheck<float>(-kMaxSafeFloat, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(-kMaxSafeFloat));
|
|
CallAndCheck<float>(-0.0f, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(-0.0));
|
|
CallAndCheck<float>(0.0f, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(0.0));
|
|
CallAndCheck<float>(kMaxSafeFloat, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(kMaxSafeFloat));
|
|
CallAndCheck<float>(std::numeric_limits<float>::max(), Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<float>::max()));
|
|
CallAndCheck<float>(std::numeric_limits<float>::quiet_NaN(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<float>::quiet_NaN()));
|
|
CallAndCheck<float>(std::numeric_limits<float>::infinity(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<float>::infinity()));
|
|
CallAndCheck<float>(std::numeric_limits<float>::quiet_NaN(),
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8_str("some_string"));
|
|
CallAndCheck<float>(std::numeric_limits<float>::quiet_NaN(),
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
CompileRun("new Proxy({}, {});"));
|
|
CallAndCheck<float>(std::numeric_limits<float>::quiet_NaN(),
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8::Object::New(isolate));
|
|
CallAndCheck<float>(0, Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8::Array::New(isolate));
|
|
CallAndCheck<float>(std::numeric_limits<float>::quiet_NaN(),
|
|
Behavior::kException, ApiCheckerResult::kSlowCalled,
|
|
v8::BigInt::New(isolate, 42));
|
|
CallAndCheck<float>(-std::numeric_limits<float>::infinity(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(-std::numeric_limits<double>::max()));
|
|
CallAndCheck<float>(std::numeric_limits<float>::infinity(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<double>::max()));
|
|
CallAndCheck<float>(kMaxSafeFloat + 1.0f, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(static_cast<double>(kMaxSafeFloat) + 2.0));
|
|
|
|
CallAndCheck<double>(std::numeric_limits<double>::min(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<double>::min()));
|
|
CallAndCheck<double>(-i::kMaxSafeInteger, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(-i::kMaxSafeInteger));
|
|
CallAndCheck<double>(std::numeric_limits<float>::min(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<float>::min()));
|
|
CallAndCheck<double>(-0.0, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(-0.0));
|
|
CallAndCheck<double>(0.0, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(0.0));
|
|
CallAndCheck<double>(std::numeric_limits<float>::max(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<float>::max()));
|
|
CallAndCheck<double>(i::kMaxSafeInteger, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(i::kMaxSafeInteger));
|
|
CallAndCheck<double>(i::kMaxSafeInteger + 1, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8_num(i::kMaxSafeInteger + 1));
|
|
CallAndCheck<double>(std::numeric_limits<double>::max(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<double>::max()));
|
|
CallAndCheck<double>(std::numeric_limits<double>::quiet_NaN(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<double>::quiet_NaN()));
|
|
CallAndCheck<double>(std::numeric_limits<double>::infinity(),
|
|
Behavior::kNoException, ApiCheckerResult::kFastCalled,
|
|
v8_num(std::numeric_limits<double>::infinity()));
|
|
CallAndCheck<double>(std::numeric_limits<double>::quiet_NaN(),
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8_str("some_string"));
|
|
CallAndCheck<double>(std::numeric_limits<double>::quiet_NaN(),
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
CompileRun("new Proxy({}, {});"));
|
|
CallAndCheck<double>(std::numeric_limits<double>::quiet_NaN(),
|
|
Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8::Object::New(isolate));
|
|
CallAndCheck<double>(0, Behavior::kNoException, ApiCheckerResult::kSlowCalled,
|
|
v8::Array::New(isolate));
|
|
CallAndCheck<double>(std::numeric_limits<double>::quiet_NaN(),
|
|
Behavior::kException, ApiCheckerResult::kSlowCalled,
|
|
v8::BigInt::New(isolate, 42));
|
|
#endif
|
|
|
|
// Corner cases - bool
|
|
CallAndCheck<bool>(false, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8::Undefined(isolate));
|
|
CallAndCheck<bool>(false, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8::Null(isolate));
|
|
CallAndCheck<bool>(false, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(0));
|
|
CallAndCheck<bool>(true, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_num(42));
|
|
CallAndCheck<bool>(false, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_str(""));
|
|
CallAndCheck<bool>(true, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8_str("some_string"));
|
|
CallAndCheck<bool>(true, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8::Symbol::New(isolate));
|
|
CallAndCheck<bool>(false, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8::BigInt::New(isolate, 0));
|
|
CallAndCheck<bool>(true, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled,
|
|
v8::BigInt::New(isolate, 42));
|
|
CallAndCheck<bool>(true, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled, v8::Object::New(isolate));
|
|
|
|
// Test return values
|
|
CheckFastReturnValue<bool>(v8::Boolean::New(isolate, true),
|
|
ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<bool>(v8::Boolean::New(isolate, false),
|
|
ApiCheckerResult::kFastCalled);
|
|
|
|
CheckFastReturnValue<int32_t>(v8_num(0), ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<int32_t>(v8_num(std::numeric_limits<int32_t>::min()),
|
|
ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<int32_t>(v8_num(std::numeric_limits<int32_t>::max()),
|
|
ApiCheckerResult::kFastCalled);
|
|
|
|
CheckFastReturnValue<uint32_t>(v8_num(0), ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<uint32_t>(v8_num(std::numeric_limits<uint32_t>::min()),
|
|
ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<uint32_t>(v8_num(std::numeric_limits<uint32_t>::max()),
|
|
ApiCheckerResult::kFastCalled);
|
|
|
|
#ifdef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
|
|
CheckFastReturnValue<float>(v8_num(0), ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<float>(v8_num(-0.0), ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<float>(v8_num(std::numeric_limits<float>::quiet_NaN()),
|
|
ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<float>(v8_num(std::numeric_limits<float>::infinity()),
|
|
ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<float>(v8_num(std::numeric_limits<float>::min()),
|
|
ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<float>(v8_num(std::numeric_limits<float>::max()),
|
|
ApiCheckerResult::kFastCalled);
|
|
|
|
CheckFastReturnValue<double>(v8_num(0), ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<double>(v8_num(-0.0), ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<double>(v8_num(std::numeric_limits<double>::quiet_NaN()),
|
|
ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<double>(v8_num(std::numeric_limits<double>::infinity()),
|
|
ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<double>(v8_num(std::numeric_limits<double>::min()),
|
|
ApiCheckerResult::kFastCalled);
|
|
CheckFastReturnValue<double>(v8_num(std::numeric_limits<double>::max()),
|
|
ApiCheckerResult::kFastCalled);
|
|
#endif // V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
|
|
|
|
// Check for the deopt loop protection
|
|
CallAndDeopt();
|
|
|
|
// Test callbacks without a fallback support
|
|
CallNoFallback(42);
|
|
|
|
// Test callback requesting access checks
|
|
CallNoConvertReceiver(42);
|
|
|
|
CheckDynamicTypeInfo();
|
|
|
|
// Fallback to slow call and throw an exception.
|
|
CallAndCheck<int32_t>(
|
|
42, Behavior::kException,
|
|
ApiCheckerResult::kFastCalled | ApiCheckerResult::kSlowCalled, v8_num(42),
|
|
Behavior::kException, FallbackPolicy::kRequestFallback);
|
|
|
|
// Fallback to slow call and don't throw an exception.
|
|
CallAndCheck<int32_t>(
|
|
42, Behavior::kNoException,
|
|
ApiCheckerResult::kFastCalled | ApiCheckerResult::kSlowCalled, v8_num(42),
|
|
Behavior::kNoException, FallbackPolicy::kRequestFallback);
|
|
|
|
// Doesn't fallback to slow call, so don't throw an exception.
|
|
CallAndCheck<int32_t>(
|
|
42, Behavior::kNoException, ApiCheckerResult::kFastCalled, v8_num(42),
|
|
Behavior::kNoException, FallbackPolicy::kDontRequestFallback);
|
|
|
|
// Wrong number of arguments
|
|
CallWithLessArguments();
|
|
CallWithMoreArguments();
|
|
|
|
// Wrong types of receiver
|
|
CallWithUnexpectedReceiverType(v8_num(123));
|
|
CallWithUnexpectedReceiverType(v8_str("str"));
|
|
CallWithUnexpectedReceiverType(CompileRun("new Proxy({}, {});"));
|
|
|
|
// Wrong types for argument of type object
|
|
CallWithUnexpectedObjectType(v8_num(123));
|
|
CallWithUnexpectedObjectType(v8_str("str"));
|
|
CallWithUnexpectedObjectType(CompileRun("new Proxy({}, {});"));
|
|
|
|
CheckApiObjectArg();
|
|
CheckFastCallsWithConstructor();
|
|
|
|
// TODO(mslekova): Restructure the tests so that the fast optimized calls
|
|
// are compared against the slow optimized calls.
|
|
// TODO(mslekova): Add tests for FTI that requires access check.
|
|
#endif // V8_LITE_MODE
|
|
}
|
|
|
|
#ifndef V8_LITE_MODE
|
|
namespace {
|
|
static Trivial* UnwrapTrivialObject(Local<Object> object) {
|
|
i::Address addr = *reinterpret_cast<i::Address*>(*object);
|
|
auto instance_type = i::Internals::GetInstanceType(addr);
|
|
bool is_valid =
|
|
(v8::base::IsInRange(instance_type, i::Internals::kFirstJSApiObjectType,
|
|
i::Internals::kLastJSApiObjectType) ||
|
|
instance_type == i::Internals::kJSSpecialApiObjectType);
|
|
if (!is_valid) {
|
|
return nullptr;
|
|
}
|
|
Trivial* wrapped = static_cast<Trivial*>(
|
|
object->GetAlignedPointerFromInternalField(kV8WrapperObjectIndex));
|
|
CHECK_NOT_NULL(wrapped);
|
|
return wrapped;
|
|
}
|
|
|
|
void FastCallback1TypedArray(v8::Local<v8::Object> receiver, int arg0,
|
|
const v8::FastApiTypedArray<int32_t>& arg1) {
|
|
Trivial* self = UnwrapTrivialObject(receiver);
|
|
CHECK_NOT_NULL(self);
|
|
CHECK_EQ(arg0, arg1.length());
|
|
self->set_x(arg0);
|
|
}
|
|
|
|
void FastCallback2JSArray(v8::Local<v8::Object> receiver, int arg0,
|
|
v8::Local<v8::Array> arg1) {
|
|
Trivial* self = UnwrapTrivialObject(receiver);
|
|
CHECK_NOT_NULL(self);
|
|
CHECK_EQ(arg0, arg1->Length());
|
|
self->set_x(arg0);
|
|
}
|
|
|
|
void FastCallback3SwappedParams(v8::Local<v8::Object> receiver,
|
|
v8::Local<v8::Array> arg0, int arg1) {}
|
|
|
|
void FastCallback4Scalar(v8::Local<v8::Object> receiver, int arg0, float arg1) {
|
|
}
|
|
|
|
void FastCallback5DifferentArity(v8::Local<v8::Object> receiver, int arg0,
|
|
v8::Local<v8::Array> arg1, float arg2) {}
|
|
|
|
void SequenceSlowCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::Isolate* isolate = args.GetIsolate();
|
|
Trivial* self = UnwrapTrivialObject(args.This());
|
|
if (!self) {
|
|
isolate->ThrowError("This method is not defined on the given receiver.");
|
|
return;
|
|
}
|
|
self->set_x(1337);
|
|
|
|
HandleScope handle_scope(isolate);
|
|
|
|
if (args.Length() < 2 || !args[0]->IsNumber()) {
|
|
isolate->ThrowError(
|
|
"This method expects at least 2 arguments,"
|
|
" first one a number.");
|
|
return;
|
|
}
|
|
int64_t len = args[0]->IntegerValue(isolate->GetCurrentContext()).FromJust();
|
|
if (args[1]->IsTypedArray()) {
|
|
v8::Local<v8::TypedArray> typed_array_arg = args[1].As<v8::TypedArray>();
|
|
size_t length = typed_array_arg->Length();
|
|
CHECK_EQ(len, length);
|
|
return;
|
|
}
|
|
if (!args[1]->IsArray()) {
|
|
isolate->ThrowError("This method expects an array as a second argument.");
|
|
return;
|
|
}
|
|
v8::Local<v8::Array> seq_arg = args[1].As<v8::Array>();
|
|
uint32_t length = seq_arg->Length();
|
|
CHECK_EQ(len, length);
|
|
return;
|
|
}
|
|
} // namespace
|
|
#endif // V8_LITE_MODE
|
|
|
|
TEST(FastApiSequenceOverloads) {
|
|
#ifndef V8_LITE_MODE
|
|
if (i::FLAG_jitless) return;
|
|
|
|
v8::internal::FLAG_opt = true;
|
|
v8::internal::FLAG_turbo_fast_api_calls = true;
|
|
v8::internal::FLAG_allow_natives_syntax = true;
|
|
// Disable --always_opt, otherwise we haven't generated the necessary
|
|
// feedback to go down the "best optimization" path for the fast call.
|
|
v8::internal::FLAG_always_opt = false;
|
|
v8::internal::FlagList::EnforceFlagImplications();
|
|
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
HandleScope handle_scope(isolate);
|
|
LocalContext env;
|
|
|
|
v8::CFunction typed_array_callback =
|
|
v8::CFunctionBuilder().Fn(FastCallback1TypedArray).Build();
|
|
v8::CFunction js_array_callback =
|
|
v8::CFunctionBuilder().Fn(FastCallback2JSArray).Build();
|
|
const v8::CFunction sequece_overloads[] = {
|
|
typed_array_callback,
|
|
js_array_callback,
|
|
};
|
|
|
|
Local<v8::FunctionTemplate> sequence_callback_templ =
|
|
v8::FunctionTemplate::NewWithCFunctionOverloads(
|
|
isolate, SequenceSlowCallback, v8::Number::New(isolate, 42),
|
|
v8::Local<v8::Signature>(), 1, v8::ConstructorBehavior::kThrow,
|
|
v8::SideEffectType::kHasSideEffect, {sequece_overloads, 2});
|
|
|
|
v8::Local<v8::ObjectTemplate> object_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
object_template->SetInternalFieldCount(kV8WrapperObjectIndex + 1);
|
|
object_template->Set(isolate, "api_func", sequence_callback_templ);
|
|
|
|
std::unique_ptr<Trivial> rcv(new Trivial(42));
|
|
v8::Local<v8::Object> object =
|
|
object_template->NewInstance(env.local()).ToLocalChecked();
|
|
object->SetAlignedPointerInInternalField(kV8WrapperObjectIndex, rcv.get());
|
|
|
|
CHECK(
|
|
(env)->Global()->Set(env.local(), v8_str("receiver"), object).FromJust());
|
|
USE(CompileRun(
|
|
"function func(num, arr) { return receiver.api_func(num, arr); }"
|
|
"%PrepareFunctionForOptimization(func);"
|
|
"func(3, [1,2,3]);"
|
|
"%OptimizeFunctionOnNextCall(func);"
|
|
"func(3, [1,2,3]);"));
|
|
CHECK_EQ(3, rcv->x());
|
|
|
|
USE(
|
|
CompileRun("const ta = new Int32Array([1, 2, 3, 4]);"
|
|
"func(4, ta);"));
|
|
CHECK_EQ(4, rcv->x());
|
|
#endif // V8_LITE_MODE
|
|
}
|
|
|
|
TEST(FastApiOverloadResolution) {
|
|
#ifndef V8_LITE_MODE
|
|
if (i::FLAG_jitless) return;
|
|
|
|
v8::internal::FLAG_opt = true;
|
|
v8::internal::FLAG_turbo_fast_api_calls = true;
|
|
v8::internal::FLAG_allow_natives_syntax = true;
|
|
// Disable --always_opt, otherwise we haven't generated the necessary
|
|
// feedback to go down the "best optimization" path for the fast call.
|
|
v8::internal::FLAG_always_opt = false;
|
|
v8::internal::FlagList::EnforceFlagImplications();
|
|
|
|
v8::CFunction typed_array_callback =
|
|
v8::CFunctionBuilder().Fn(FastCallback1TypedArray).Build();
|
|
v8::CFunction js_array_callback =
|
|
v8::CFunctionBuilder().Fn(FastCallback2JSArray).Build();
|
|
|
|
// Check that a general runtime overload resolution is possible.
|
|
CHECK_EQ(v8::CFunction::OverloadResolution::kAtRuntime,
|
|
typed_array_callback.GetOverloadResolution(&js_array_callback));
|
|
|
|
v8::CFunction swapped_params_callback =
|
|
v8::CFunctionBuilder().Fn(FastCallback3SwappedParams).Build();
|
|
|
|
// Check that difference in > 1 position is not possible.
|
|
CHECK_EQ(
|
|
v8::CFunction::OverloadResolution::kImpossible,
|
|
typed_array_callback.GetOverloadResolution(&swapped_params_callback));
|
|
|
|
v8::CFunction scalar_callback =
|
|
v8::CFunctionBuilder().Fn(FastCallback4Scalar).Build();
|
|
|
|
// Check that resolving when there is a scalar at the difference position
|
|
// is not possible.
|
|
CHECK_EQ(v8::CFunction::OverloadResolution::kImpossible,
|
|
typed_array_callback.GetOverloadResolution(&scalar_callback));
|
|
|
|
v8::CFunction diff_arity_callback =
|
|
v8::CFunctionBuilder().Fn(FastCallback5DifferentArity).Build();
|
|
|
|
// Check that overload resolution between different number of arguments
|
|
// is possible.
|
|
CHECK_EQ(v8::CFunction::OverloadResolution::kAtCompileTime,
|
|
typed_array_callback.GetOverloadResolution(&diff_arity_callback));
|
|
|
|
#endif // V8_LITE_MODE
|
|
}
|
|
|
|
THREADED_TEST(Recorder_GetContext) {
|
|
using v8::Context;
|
|
using v8::Local;
|
|
using v8::MaybeLocal;
|
|
|
|
// Set up isolate and context.
|
|
v8::Isolate* iso = CcTest::isolate();
|
|
v8::metrics::Recorder::ContextId original_id;
|
|
std::vector<v8::metrics::Recorder::ContextId> ids;
|
|
{
|
|
v8::HandleScope scope(iso);
|
|
Local<Context> context = Context::New(iso);
|
|
|
|
// Ensure that we get a valid context id.
|
|
original_id = v8::metrics::Recorder::GetContextId(context);
|
|
CHECK(!original_id.IsEmpty());
|
|
|
|
// Request many context ids to ensure correct growth behavior.
|
|
for (size_t count = 0; count < 50; ++count) {
|
|
Local<Context> temp_context = Context::New(iso);
|
|
ids.push_back(v8::metrics::Recorder::GetContextId(temp_context));
|
|
}
|
|
for (const v8::metrics::Recorder::ContextId& id : ids) {
|
|
CHECK(!v8::metrics::Recorder::GetContext(iso, id).IsEmpty());
|
|
}
|
|
|
|
// Ensure that we can get the context from the context id.
|
|
MaybeLocal<Context> retrieved_context =
|
|
v8::metrics::Recorder::GetContext(iso, original_id);
|
|
CHECK_EQ(context, retrieved_context.ToLocalChecked());
|
|
|
|
// Ensure that an empty context id returns an empty handle.
|
|
retrieved_context = v8::metrics::Recorder::GetContext(
|
|
iso, v8::metrics::Recorder::ContextId::Empty());
|
|
CHECK(retrieved_context.IsEmpty());
|
|
|
|
// Ensure that repeated context id accesses return the same context id.
|
|
v8::metrics::Recorder::ContextId new_id =
|
|
v8::metrics::Recorder::GetContextId(context);
|
|
CHECK_EQ(original_id, new_id);
|
|
}
|
|
|
|
// Invalidate the context and therefore the context id.
|
|
CcTest::PreciseCollectAllGarbage();
|
|
|
|
// Ensure that a stale context id returns an empty handle.
|
|
{
|
|
v8::HandleScope scope(iso);
|
|
MaybeLocal<Context> retrieved_context =
|
|
v8::metrics::Recorder::GetContext(iso, original_id);
|
|
CHECK(retrieved_context.IsEmpty());
|
|
|
|
for (const v8::metrics::Recorder::ContextId& id : ids) {
|
|
CHECK(v8::metrics::Recorder::GetContext(iso, id).IsEmpty());
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
class MetricsRecorder : public v8::metrics::Recorder {
|
|
public:
|
|
v8::Isolate* isolate_;
|
|
size_t count_ = 0;
|
|
size_t module_count_ = 0;
|
|
int64_t time_in_us_ = -1;
|
|
|
|
explicit MetricsRecorder(v8::Isolate* isolate) : isolate_(isolate) {}
|
|
|
|
void AddMainThreadEvent(const v8::metrics::WasmModuleDecoded& event,
|
|
v8::metrics::Recorder::ContextId id) override {
|
|
if (v8::metrics::Recorder::GetContext(isolate_, id).IsEmpty()) return;
|
|
++count_;
|
|
time_in_us_ = event.wall_clock_duration_in_us;
|
|
}
|
|
|
|
void AddThreadSafeEvent(
|
|
const v8::metrics::WasmModulesPerIsolate& event) override {
|
|
++count_;
|
|
module_count_ = event.count;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(TriggerMainThreadMetricsEvent) {
|
|
using v8::Context;
|
|
using v8::Local;
|
|
using v8::MaybeLocal;
|
|
|
|
// Set up isolate and context.
|
|
v8::Isolate* iso = CcTest::isolate();
|
|
i::Isolate* i_iso = reinterpret_cast<i::Isolate*>(iso);
|
|
CHECK(i_iso->metrics_recorder());
|
|
v8::metrics::WasmModuleDecoded event;
|
|
v8::metrics::Recorder::ContextId context_id;
|
|
std::shared_ptr<MetricsRecorder> recorder =
|
|
std::make_shared<MetricsRecorder>(iso);
|
|
iso->SetMetricsRecorder(recorder);
|
|
{
|
|
v8::HandleScope scope(iso);
|
|
Local<Context> context = Context::New(iso);
|
|
context_id = v8::metrics::Recorder::GetContextId(context);
|
|
|
|
// Check that event submission works.
|
|
{
|
|
i::metrics::TimedScope<v8::metrics::WasmModuleDecoded> timed_scope(
|
|
&event);
|
|
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(100));
|
|
}
|
|
i_iso->metrics_recorder()->AddMainThreadEvent(event, context_id);
|
|
CHECK_EQ(recorder->count_, 1); // Increased.
|
|
CHECK_GT(recorder->time_in_us_, 100);
|
|
}
|
|
|
|
CcTest::PreciseCollectAllGarbage();
|
|
|
|
// Check that event submission doesn't break even if the context id is
|
|
// invalid.
|
|
i_iso->metrics_recorder()->AddMainThreadEvent(event, context_id);
|
|
CHECK_EQ(recorder->count_, 1); // Unchanged.
|
|
}
|
|
|
|
TEST(TriggerDelayedMainThreadMetricsEvent) {
|
|
using v8::Context;
|
|
using v8::Local;
|
|
using v8::MaybeLocal;
|
|
i::FLAG_stress_concurrent_allocation = false;
|
|
|
|
// Set up isolate and context.
|
|
v8::Isolate* iso = CcTest::isolate();
|
|
i::Isolate* i_iso = reinterpret_cast<i::Isolate*>(iso);
|
|
CHECK(i_iso->metrics_recorder());
|
|
v8::metrics::WasmModuleDecoded event;
|
|
v8::metrics::Recorder::ContextId context_id;
|
|
std::shared_ptr<MetricsRecorder> recorder =
|
|
std::make_shared<MetricsRecorder>(iso);
|
|
iso->SetMetricsRecorder(recorder);
|
|
{
|
|
v8::HandleScope scope(iso);
|
|
Local<Context> context = Context::New(iso);
|
|
context_id = v8::metrics::Recorder::GetContextId(context);
|
|
|
|
// Check that event submission works.
|
|
{
|
|
i::metrics::TimedScope<v8::metrics::WasmModuleDecoded> timed_scope(
|
|
&event);
|
|
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(100));
|
|
}
|
|
i_iso->metrics_recorder()->DelayMainThreadEvent(event, context_id);
|
|
CHECK_EQ(recorder->count_, 0); // Unchanged.
|
|
CHECK_EQ(recorder->time_in_us_, -1); // Unchanged.
|
|
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(1100));
|
|
while (v8::platform::PumpMessageLoop(v8::internal::V8::GetCurrentPlatform(),
|
|
iso)) {
|
|
}
|
|
CHECK_EQ(recorder->count_, 1); // Increased.
|
|
CHECK_GT(recorder->time_in_us_, 100);
|
|
}
|
|
|
|
CcTest::PreciseCollectAllGarbage();
|
|
|
|
// Check that event submission doesn't break even if the context id is
|
|
// invalid.
|
|
i_iso->metrics_recorder()->DelayMainThreadEvent(event, context_id);
|
|
v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(1100));
|
|
while (v8::platform::PumpMessageLoop(v8::internal::V8::GetCurrentPlatform(),
|
|
iso)) {
|
|
}
|
|
CHECK_EQ(recorder->count_, 1); // Unchanged.
|
|
}
|
|
|
|
TEST(TriggerThreadSafeMetricsEvent) {
|
|
// Set up isolate and context.
|
|
v8::Isolate* iso = CcTest::isolate();
|
|
i::Isolate* i_iso = reinterpret_cast<i::Isolate*>(iso);
|
|
CHECK(i_iso->metrics_recorder());
|
|
v8::metrics::WasmModulesPerIsolate event;
|
|
std::shared_ptr<MetricsRecorder> recorder =
|
|
std::make_shared<MetricsRecorder>(iso);
|
|
iso->SetMetricsRecorder(recorder);
|
|
|
|
// Check that event submission works.
|
|
event.count = 42;
|
|
i_iso->metrics_recorder()->AddThreadSafeEvent(event);
|
|
CHECK_EQ(recorder->count_, 1); // Increased.
|
|
CHECK_EQ(recorder->module_count_, 42);
|
|
}
|
|
|
|
void SetupCodeLike(LocalContext* env, const char* name,
|
|
v8::Local<v8::FunctionTemplate> to_string,
|
|
bool is_code_like) {
|
|
// Setup a JS constructor + object template for testing IsCodeLike.
|
|
v8::Local<FunctionTemplate> constructor =
|
|
v8::FunctionTemplate::New((*env)->GetIsolate());
|
|
constructor->SetClassName(v8_str(name));
|
|
constructor->InstanceTemplate()->Set((*env)->GetIsolate(), "toString",
|
|
to_string);
|
|
if (is_code_like) {
|
|
constructor->InstanceTemplate()->SetCodeLike();
|
|
}
|
|
CHECK_EQ(is_code_like, constructor->InstanceTemplate()->IsCodeLike());
|
|
CHECK((*env)
|
|
->Global()
|
|
->Set(env->local(), v8_str(name),
|
|
constructor->GetFunction(env->local()).ToLocalChecked())
|
|
.FromJust());
|
|
}
|
|
|
|
TEST(CodeLikeEval) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// Setup two object templates with an eval-able string representation.
|
|
// One code-like, one not, and otherwise identical.
|
|
auto string_fn = v8::FunctionTemplate::New(
|
|
isolate, [](const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(v8_str("2+2"));
|
|
});
|
|
SetupCodeLike(&env, "CodeLike", string_fn, true);
|
|
SetupCodeLike(&env, "Other", string_fn, false);
|
|
|
|
// Check v8::Object::IsCodeLike.
|
|
CHECK(CompileRun("new CodeLike()").As<v8::Object>()->IsCodeLike(isolate));
|
|
CHECK(!CompileRun("new Other()").As<v8::Object>()->IsCodeLike(isolate));
|
|
|
|
// Expected behaviour for normal objects:
|
|
// - eval returns them as-is
|
|
// - when pre-stringified, the string gets evaluated (of course)
|
|
ExpectString("eval(new Other()) + \"\"", "2+2");
|
|
ExpectInt32("eval(\"\" + new Other())", 4);
|
|
|
|
// Expected behaviour for 'code like': Is always evaluated.
|
|
ExpectInt32("eval(new CodeLike())", 4);
|
|
ExpectInt32("eval(\"\" + new CodeLike())", 4);
|
|
|
|
// Modify callback will always returns a replacement string:
|
|
// Expected behaviour: Always execute the replacement string.
|
|
isolate->SetModifyCodeGenerationFromStringsCallback(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Value> source,
|
|
bool is_code_like) -> v8::ModifyCodeGenerationFromStringsResult {
|
|
return {true, v8_str("3+3")};
|
|
});
|
|
ExpectInt32("eval(new Other())", 6);
|
|
ExpectInt32("eval(new CodeLike())", 6);
|
|
|
|
// Modify callback always disallows:
|
|
// Expected behaviour: Always fail to execute.
|
|
isolate->SetModifyCodeGenerationFromStringsCallback(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Value> source,
|
|
bool is_code_like) -> v8::ModifyCodeGenerationFromStringsResult {
|
|
return {false, v8::Local<v8::String>()};
|
|
});
|
|
CHECK(CompileRun("eval(new Other())").IsEmpty());
|
|
CHECK(CompileRun("eval(new CodeLike())").IsEmpty());
|
|
|
|
// Modify callback allows only "code like":
|
|
// Expected behaviour: Only code-like executed, with replacement string.
|
|
isolate->SetModifyCodeGenerationFromStringsCallback(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Value> source,
|
|
bool is_code_like) -> v8::ModifyCodeGenerationFromStringsResult {
|
|
bool ok = is_code_like ||
|
|
(source->IsObject() &&
|
|
source.As<v8::Object>()->IsCodeLike(context->GetIsolate()));
|
|
return {ok, v8_str("5+7")};
|
|
});
|
|
CHECK(CompileRun("eval(new Other())").IsEmpty());
|
|
ExpectInt32("eval(new CodeLike())", 12);
|
|
}
|
|
|
|
TEST(CodeLikeFunction) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// These follow the pattern of the CodeLikeEval test above, but with
|
|
// "new Function" instead of eval.
|
|
|
|
// Setup two object templates with an eval-able string representation.
|
|
// One code kind, one not, and otherwise identical.
|
|
auto string_fn = v8::FunctionTemplate::New(
|
|
isolate, [](const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(v8_str("return 2+2"));
|
|
});
|
|
SetupCodeLike(&env, "CodeLike", string_fn, true);
|
|
SetupCodeLike(&env, "Other", string_fn, false);
|
|
|
|
ExpectInt32("new Function(new Other())()", 4);
|
|
ExpectInt32("new Function(new CodeLike())()", 4);
|
|
|
|
// Modify callback will always return a replacement string:
|
|
env.local()->AllowCodeGenerationFromStrings(false);
|
|
isolate->SetModifyCodeGenerationFromStringsCallback(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Value> source,
|
|
bool is_code_like) -> v8::ModifyCodeGenerationFromStringsResult {
|
|
return {true, v8_str("(function anonymous(\n) {\nreturn 7;\n})\n")};
|
|
});
|
|
ExpectInt32("new Function(new Other())()", 7);
|
|
ExpectInt32("new Function(new CodeLike())()", 7);
|
|
|
|
// Modify callback always disallows:
|
|
isolate->SetModifyCodeGenerationFromStringsCallback(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Value> source,
|
|
bool is_code_like) -> v8::ModifyCodeGenerationFromStringsResult {
|
|
return {false, v8::Local<v8::String>()};
|
|
});
|
|
CHECK(CompileRun("new Function(new Other())()").IsEmpty());
|
|
CHECK(CompileRun("new Function(new CodeLike())()").IsEmpty());
|
|
|
|
// Modify callback allows only "code kind":
|
|
isolate->SetModifyCodeGenerationFromStringsCallback(
|
|
[](v8::Local<v8::Context> context, v8::Local<v8::Value> source,
|
|
bool is_code_like) -> v8::ModifyCodeGenerationFromStringsResult {
|
|
bool ok = is_code_like ||
|
|
(source->IsObject() &&
|
|
source.As<v8::Object>()->IsCodeLike(context->GetIsolate()));
|
|
return {ok, v8_str("(function anonymous(\n) {\nreturn 7;\n})\n")};
|
|
});
|
|
CHECK(CompileRun("new Function(new Other())()").IsEmpty());
|
|
ExpectInt32("new Function(new CodeLike())()", 7);
|
|
}
|
|
|
|
UNINITIALIZED_TEST(SingleThreadedDefaultPlatform) {
|
|
v8::V8::SetFlagsFromString("--single-threaded");
|
|
auto old_platform = i::V8::GetCurrentPlatform();
|
|
std::unique_ptr<v8::Platform> new_platform(
|
|
v8::platform::NewSingleThreadedDefaultPlatform());
|
|
i::V8::SetPlatformForTesting(new_platform.get());
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
isolate->Enter();
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
{
|
|
i::HandleScope scope(i_isolate);
|
|
v8::Local<Context> env = Context::New(isolate);
|
|
env->Enter();
|
|
|
|
CompileRunChecked(isolate,
|
|
"function f() {"
|
|
" for (let i = 0; i < 10; i++)"
|
|
" (new Array(10)).fill(0);"
|
|
" return 0;"
|
|
"}"
|
|
"f();");
|
|
env->Exit();
|
|
}
|
|
CcTest::CollectGarbage(i::NEW_SPACE, i_isolate);
|
|
CcTest::CollectAllAvailableGarbage(i_isolate);
|
|
isolate->Exit();
|
|
isolate->Dispose();
|
|
i::V8::SetPlatformForTesting(old_platform);
|
|
}
|
|
|
|
THREADED_TEST(MicrotaskQueueOfContext) {
|
|
auto microtask_queue = v8::MicrotaskQueue::New(CcTest::isolate());
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
v8::Local<Context> context = Context::New(
|
|
CcTest::isolate(), nullptr, v8::MaybeLocal<ObjectTemplate>(),
|
|
v8::MaybeLocal<Value>(), v8::DeserializeInternalFieldsCallback(),
|
|
microtask_queue.get());
|
|
CHECK_EQ(context->GetMicrotaskQueue(), microtask_queue.get());
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool sab_constructor_enabled_value = false;
|
|
|
|
bool MockSabConstructorEnabledCallback(v8::Local<v8::Context>) {
|
|
return sab_constructor_enabled_value;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(TestSetSabConstructorEnabledCallback) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate());
|
|
i::Handle<i::Context> i_context = v8::Utils::OpenHandle(*context);
|
|
|
|
// {Isolate::IsSharedArrayBufferConstructorEnabled} calls the callback set by
|
|
// the embedder if such a callback exists. Otherwise it returns
|
|
// {FLAG_harmony_sharedarraybuffer}. First we test that the flag is returned
|
|
// correctly if no callback is set. Then we test that the flag is ignored if
|
|
// the callback is set.
|
|
|
|
i::FLAG_harmony_sharedarraybuffer = false;
|
|
i::FLAG_enable_sharedarraybuffer_per_context = false;
|
|
CHECK(!i_isolate->IsSharedArrayBufferConstructorEnabled(i_context));
|
|
|
|
i::FLAG_harmony_sharedarraybuffer = false;
|
|
i::FLAG_enable_sharedarraybuffer_per_context = false;
|
|
CHECK(!i_isolate->IsSharedArrayBufferConstructorEnabled(i_context));
|
|
|
|
i::FLAG_harmony_sharedarraybuffer = true;
|
|
i::FLAG_enable_sharedarraybuffer_per_context = false;
|
|
CHECK(i_isolate->IsSharedArrayBufferConstructorEnabled(i_context));
|
|
|
|
i::FLAG_harmony_sharedarraybuffer = true;
|
|
i::FLAG_enable_sharedarraybuffer_per_context = true;
|
|
CHECK(!i_isolate->IsSharedArrayBufferConstructorEnabled(i_context));
|
|
|
|
isolate->SetSharedArrayBufferConstructorEnabledCallback(
|
|
MockSabConstructorEnabledCallback);
|
|
sab_constructor_enabled_value = false;
|
|
CHECK(!i_isolate->IsSharedArrayBufferConstructorEnabled(i_context));
|
|
|
|
sab_constructor_enabled_value = true;
|
|
CHECK(i_isolate->IsSharedArrayBufferConstructorEnabled(i_context));
|
|
}
|
|
|
|
namespace {
|
|
void NodeTypeCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::Isolate* isolate = args.GetIsolate();
|
|
args.GetReturnValue().Set(v8::Number::New(isolate, 1));
|
|
}
|
|
} // namespace
|
|
|
|
TEST(EmbedderInstanceTypes) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
i::FLAG_embedder_instance_types = true;
|
|
Local<FunctionTemplate> node = FunctionTemplate::New(isolate);
|
|
Local<ObjectTemplate> proto_template = node->PrototypeTemplate();
|
|
Local<FunctionTemplate> nodeType = v8::FunctionTemplate::New(
|
|
isolate, NodeTypeCallback, Local<Value>(),
|
|
v8::Signature::New(isolate, node), 0, v8::ConstructorBehavior::kThrow,
|
|
v8::SideEffectType::kHasSideEffect, nullptr, 0, 1, 3);
|
|
proto_template->SetAccessorProperty(
|
|
String::NewFromUtf8Literal(isolate, "nodeType"), nodeType);
|
|
|
|
Local<FunctionTemplate> element = FunctionTemplate::New(
|
|
isolate, nullptr, Local<Value>(), Local<v8::Signature>(), 0,
|
|
v8::ConstructorBehavior::kAllow, v8::SideEffectType::kHasSideEffect,
|
|
nullptr, 1);
|
|
element->Inherit(node);
|
|
|
|
Local<FunctionTemplate> html_element = FunctionTemplate::New(
|
|
isolate, nullptr, Local<Value>(), Local<v8::Signature>(), 0,
|
|
v8::ConstructorBehavior::kAllow, v8::SideEffectType::kHasSideEffect,
|
|
nullptr, 2);
|
|
html_element->Inherit(element);
|
|
|
|
Local<FunctionTemplate> div_element = FunctionTemplate::New(
|
|
isolate, nullptr, Local<Value>(), Local<v8::Signature>(), 0,
|
|
v8::ConstructorBehavior::kAllow, v8::SideEffectType::kHasSideEffect,
|
|
nullptr, 3);
|
|
div_element->Inherit(html_element);
|
|
|
|
CHECK(env->Global()
|
|
->Set(env.local(), v8_str("div"),
|
|
div_element->GetFunction(env.local())
|
|
.ToLocalChecked()
|
|
->NewInstance(env.local())
|
|
.ToLocalChecked())
|
|
.FromJust());
|
|
|
|
CompileRun("var x = div.nodeType;");
|
|
|
|
Local<Value> res =
|
|
env->Global()->Get(env.local(), v8_str("x")).ToLocalChecked();
|
|
CHECK_EQ(1, res->ToInt32(env.local()).ToLocalChecked()->Value());
|
|
}
|