summaryrefslogtreecommitdiff
path: root/deps/v8/test/cctest/test-api.cc
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/test/cctest/test-api.cc')
-rw-r--r--deps/v8/test/cctest/test-api.cc1100
1 files changed, 818 insertions, 282 deletions
diff --git a/deps/v8/test/cctest/test-api.cc b/deps/v8/test/cctest/test-api.cc
index 2fd28f0689..af7dfbf03c 100644
--- a/deps/v8/test/cctest/test-api.cc
+++ b/deps/v8/test/cctest/test-api.cc
@@ -80,6 +80,7 @@
#include "test/common/flag-utils.h"
#if V8_ENABLE_WEBASSEMBLY
+#include "src/wasm/wasm-engine.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
@@ -531,6 +532,9 @@ class TestOneByteResource : public String::ExternalOneByteStringResource {
THREADED_TEST(ScriptUsingStringResource) {
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
+
int dispose_count = 0;
const char* c_source = "1 + 2 * 3";
uint16_t* two_byte_source = AsciiToTwoByteString(c_source);
@@ -563,6 +567,9 @@ THREADED_TEST(ScriptUsingStringResource) {
THREADED_TEST(ScriptUsingOneByteStringResource) {
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
+
int dispose_count = 0;
const char* c_source = "1 + 2 * 3";
{
@@ -596,6 +603,9 @@ THREADED_TEST(ScriptUsingOneByteStringResource) {
THREADED_TEST(ScriptMakingExternalString) {
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
+
int dispose_count = 0;
uint16_t* two_byte_source = AsciiToTwoByteString("1 + 2 * 3");
{
@@ -630,6 +640,9 @@ THREADED_TEST(ScriptMakingExternalString) {
THREADED_TEST(ScriptMakingExternalOneByteString) {
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
+
int dispose_count = 0;
const char* c_source = "1 + 2 * 3";
{
@@ -665,31 +678,29 @@ TEST(MakingExternalStringConditions) {
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);
+ Local<String> tiny_local_string = v8_str("\xCF\x80");
+ Local<String> local_string = v8_str("s1234\xCF\x80");
- two_byte_string = AsciiToTwoByteString("s1234");
- Local<String> local_string =
- String::NewFromTwoByte(env->GetIsolate(), two_byte_string)
- .ToLocalChecked();
- i::DeleteArray(two_byte_string);
+ CHECK(!tiny_local_string->IsOneByte());
+ CHECK(!local_string->IsOneByte());
if (!i::v8_flags.single_generation) {
// We should refuse to externalize new space strings.
- CHECK(!local_string->CanMakeExternal());
+ CHECK(!local_string->CanMakeExternal(String::Encoding::TWO_BYTE_ENCODING));
// Trigger full GC so that the newly allocated string moves to old gen.
CcTest::CollectGarbage(i::OLD_SPACE);
}
// Old space strings should be accepted.
- CHECK(local_string->CanMakeExternal());
+ CHECK(local_string->CanMakeExternal(String::Encoding::TWO_BYTE_ENCODING));
// Tiny strings are not in-place externalizable when pointer compression is
// enabled, but they are if the sandbox is enabled.
- CHECK_EQ(V8_ENABLE_SANDBOX_BOOL || i::kTaggedSize == i::kSystemPointerSize,
- tiny_local_string->CanMakeExternal());
+ CHECK_EQ(
+ V8_ENABLE_SANDBOX_BOOL || i::kTaggedSize == i::kSystemPointerSize,
+ tiny_local_string->CanMakeExternal(String::Encoding::TWO_BYTE_ENCODING));
+
+ // Change of representation is not allowed.
+ CHECK(!local_string->CanMakeExternal(String::Encoding::ONE_BYTE_ENCODING));
}
@@ -706,18 +717,26 @@ TEST(MakingExternalOneByteStringConditions) {
Local<String> tiny_local_string = v8_str("s");
Local<String> local_string = v8_str("s1234");
+ CHECK(tiny_local_string->IsOneByte());
+ CHECK(local_string->IsOneByte());
+
// Single-character strings should not be externalized because they
// are always in the RO-space.
- CHECK(!tiny_local_string->CanMakeExternal());
+ CHECK(
+ !tiny_local_string->CanMakeExternal(String::Encoding::ONE_BYTE_ENCODING));
if (!i::v8_flags.single_generation) {
// We should refuse to externalize new space strings.
- CHECK(!local_string->CanMakeExternal());
+ CHECK(!local_string->CanMakeExternal(String::Encoding::ONE_BYTE_ENCODING));
// Trigger full GC so that the newly allocated string moves to old gen.
CcTest::CollectGarbage(i::OLD_SPACE);
- CHECK(!tiny_local_string->CanMakeExternal());
+ CHECK(!tiny_local_string->CanMakeExternal(
+ String::Encoding::ONE_BYTE_ENCODING));
}
// Old space strings should be accepted.
- CHECK(local_string->CanMakeExternal());
+ CHECK(local_string->CanMakeExternal(String::Encoding::ONE_BYTE_ENCODING));
+
+ // Change of representation is not allowed.
+ CHECK(!local_string->CanMakeExternal(String::Encoding::TWO_BYTE_ENCODING));
}
@@ -752,8 +771,6 @@ TEST(MakingExternalUnalignedOneByteString) {
// Trigger GCs and force evacuation.
CcTest::CollectAllGarbage();
- i::ScanStackModeScopeForTesting no_stack_scanning(
- CcTest::heap(), i::Heap::ScanStackMode::kNone);
CcTest::heap()->CollectAllGarbage(i::Heap::kReduceMemoryFootprintMask,
i::GarbageCollectionReason::kTesting);
}
@@ -855,6 +872,9 @@ TEST(ScavengeExternalString) {
ManualGCScope manual_gc_scope;
i::v8_flags.stress_compaction = false;
i::v8_flags.gc_global = false;
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
+
int dispose_count = 0;
bool in_young_generation = false;
{
@@ -880,6 +900,9 @@ TEST(ScavengeExternalOneByteString) {
ManualGCScope manual_gc_scope;
i::v8_flags.stress_compaction = false;
i::v8_flags.gc_global = false;
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
+
int dispose_count = 0;
bool in_young_generation = false;
{
@@ -925,6 +948,9 @@ int TestOneByteResourceWithDisposeControl::dispose_calls = 0;
TEST(ExternalStringWithDisposeHandling) {
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
+
const char* c_source = "1 + 2 * 3";
// Use a stack allocated external string resource allocated object.
@@ -2431,7 +2457,6 @@ THREADED_TEST(TestObjectTemplateReflectConstruct) {
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.
@@ -3091,15 +3116,17 @@ THREADED_TEST(SetAlignedPointerInInternalFields) {
delete[] heap_allocated_2;
}
-static void CheckAlignedPointerInEmbedderData(LocalContext* env, int index,
- void* value) {
+static void CheckAlignedPointerInEmbedderData(LocalContext* env,
+ v8::Local<v8::Object> some_obj,
+ 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));
+ CHECK_EQ(value,
+ some_obj->GetAlignedPointerFromEmbedderDataInCreationContext(index));
}
-
static void* AlignedTestPointer(int i) {
return reinterpret_cast<void*>(i * 1234);
}
@@ -3107,24 +3134,27 @@ static void* AlignedTestPointer(int i) {
THREADED_TEST(EmbedderDataAlignedPointers) {
LocalContext env;
- v8::HandleScope scope(env->GetIsolate());
+ v8::Isolate* isolate = env->GetIsolate();
+ v8::HandleScope scope(isolate);
- CheckAlignedPointerInEmbedderData(&env, 0, nullptr);
+ v8::Local<v8::Object> obj = v8::Object::New(isolate);
+
+ CheckAlignedPointerInEmbedderData(&env, obj, 0, nullptr);
CHECK_EQ(1, (*env)->GetNumberOfEmbedderDataFields());
int* heap_allocated = new int[100];
- CheckAlignedPointerInEmbedderData(&env, 1, heap_allocated);
+ CheckAlignedPointerInEmbedderData(&env, obj, 1, heap_allocated);
CHECK_EQ(2, (*env)->GetNumberOfEmbedderDataFields());
delete[] heap_allocated;
int stack_allocated[100];
- CheckAlignedPointerInEmbedderData(&env, 2, stack_allocated);
+ CheckAlignedPointerInEmbedderData(&env, obj, 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);
+ CheckAlignedPointerInEmbedderData(&env, obj, 3, huge);
CHECK_EQ(4, (*env)->GetNumberOfEmbedderDataFields());
// Test growing of the embedder data's backing store.
@@ -4199,6 +4229,8 @@ void FirstPassCallback(const v8::WeakCallbackInfo<TwoPassCallbackData>& data) {
TEST(TwoPassPhantomCallbacks) {
auto isolate = CcTest::isolate();
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
GCCallbackMetadata metadata;
const size_t kLength = 20;
for (size_t i = 0; i < kLength; ++i) {
@@ -4213,6 +4245,8 @@ TEST(TwoPassPhantomCallbacks) {
TEST(TwoPassPhantomCallbacksNestedGc) {
auto isolate = CcTest::isolate();
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
GCCallbackMetadata metadata;
const size_t kLength = 20;
TwoPassCallbackData* array[kLength];
@@ -4233,6 +4267,8 @@ TEST(TwoPassPhantomCallbacksNestedGc) {
// the second pass callback can still execute JS as per its API contract.
TEST(TwoPassPhantomCallbacksTriggeredByStringAlloc) {
auto isolate = CcTest::isolate();
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
GCCallbackMetadata metadata;
auto data = new TwoPassCallbackData(isolate, &metadata);
data->SetWeak();
@@ -7600,8 +7636,11 @@ static void SetFlag(const v8::WeakCallbackInfo<FlagAndPersistent>& data) {
static void IndependentWeakHandle(bool global_gc, bool interlinked) {
ManualGCScope manual_gc_scope;
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
// Parallel scavenge introduces too much fragmentation.
i::v8_flags.parallel_scavenge = false;
+
v8::Isolate* iso = CcTest::isolate();
v8::HandleScope scope(iso);
v8::Local<Context> context = Context::New(iso);
@@ -7706,6 +7745,8 @@ void InternalFieldCallback(bool global_gc) {
// which prevents it from being reclaimed and the callbacks from being
// executed.
ManualGCScope manual_gc_scope;
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
@@ -7741,6 +7782,7 @@ void InternalFieldCallback(bool global_gc) {
handle.SetWeak<v8::Persistent<v8::Object>>(
&handle, CheckInternalFields, v8::WeakCallbackType::kInternalFields);
}
+
if (i::v8_flags.single_generation || global_gc) {
CcTest::CollectAllGarbage();
} else {
@@ -7847,6 +7889,8 @@ THREADED_TEST(GCFromWeakCallbacks) {
v8::HandleScope scope(isolate);
v8::Local<Context> context = Context::New(isolate);
Context::Scope context_scope(context);
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
if (i::v8_flags.single_generation) {
FlagAndPersistent object;
@@ -13009,12 +13053,13 @@ bool ApiTestFuzzer::NextThread() {
void ApiTestFuzzer::Run() {
- // When it is our turn...
+ // Wait until it is our turn.
gate_.Wait();
{
- // ... get the V8 lock
+ // Get the V8 lock.
v8::Locker locker(CcTest::isolate());
- // ... and start running the test.
+ // Start running the test, which will enter the isolate and exit it when it
+ // finishes.
CallTest();
}
// This test finished.
@@ -13055,11 +13100,18 @@ static void CallTestNumber(int test_number) {
void ApiTestFuzzer::RunAllTests() {
+ // This method is called when running each THREADING_TEST, which is an
+ // initialized test and has entered the isolate at this point. We need to exit
+ // the isolate, so that the fuzzer threads can enter it in turn, while running
+ // their tests.
+ CcTest::isolate()->Exit();
// Set off the first test.
current_ = -1;
NextThread();
// Wait till they are all done.
all_tests_done_.Wait();
+ // We enter the isolate again, to prepare for teardown.
+ CcTest::isolate()->Enter();
}
@@ -13077,10 +13129,16 @@ int ApiTestFuzzer::GetNextTestNumber() {
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();
+ // Exit the isolate from this thread.
+ CcTest::i_isolate()->Exit();
+ {
+ // Now the new thread can start.
+ v8::Unlocker unlocker(CcTest::isolate());
+ // Wait till someone starts us again.
+ gate_.Wait();
+ }
+ // Enter the isolate from this thread again.
+ CcTest::i_isolate()->Enter();
// And we're off.
}
}
@@ -13295,6 +13353,8 @@ static void CheckSurvivingGlobalObjectsCount(int expected) {
// 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.
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
CcTest::CollectAllGarbage();
CcTest::CollectAllGarbage();
int count = GetGlobalObjectsCount();
@@ -13346,6 +13406,8 @@ static void WeakApiCallback(
TEST(WeakCallbackApi) {
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
LocalContext context;
v8::Isolate* isolate = context->GetIsolate();
i::GlobalHandles* globals =
@@ -13521,7 +13583,7 @@ THREADED_TEST(NestedHandleScopeAndContexts) {
env->Exit();
}
-static v8::base::HashMap* code_map = nullptr;
+static v8::base::HashMap* instruction_stream_map = nullptr;
static v8::base::HashMap* jitcode_line_info = nullptr;
static int saw_bar = 0;
static int move_events = 0;
@@ -13564,7 +13626,7 @@ static bool FunctionNameIs(const char* expected,
static void event_handler(const v8::JitCodeEvent* event) {
CHECK_NOT_NULL(event);
- CHECK_NOT_NULL(code_map);
+ CHECK_NOT_NULL(instruction_stream_map);
CHECK_NOT_NULL(jitcode_line_info);
class DummyJitCodeLineInfo {
@@ -13575,7 +13637,7 @@ static void event_handler(const v8::JitCodeEvent* event) {
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(
+ v8::base::HashMap::Entry* entry = instruction_stream_map->LookupOrInsert(
event->code_start, i::ComputePointerHash(event->code_start));
entry->value = reinterpret_cast<void*>(event->code_len);
@@ -13595,14 +13657,14 @@ static void event_handler(const v8::JitCodeEvent* event) {
// 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);
+ instruction_stream_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);
+ instruction_stream_map->Remove(event->code_start, hash);
- entry = code_map->LookupOrInsert(
+ entry = instruction_stream_map->LookupOrInsert(
event->new_code_start,
i::ComputePointerHash(event->new_code_start));
entry->value = reinterpret_cast<void*>(event->code_len);
@@ -13663,6 +13725,7 @@ UNINITIALIZED_TEST(SetJitCodeEventHandler) {
i::v8_flags.baseline_batch_compilation = false;
#endif
if (!i::v8_flags.compact) return;
+ i::FlagList::EnforceFlagImplications();
const char* script =
"function bar() {"
" var sum = 0;"
@@ -13687,7 +13750,7 @@ UNINITIALIZED_TEST(SetJitCodeEventHandler) {
{
v8::HandleScope scope(isolate);
v8::base::HashMap code;
- code_map = &code;
+ instruction_stream_map = &code;
v8::base::HashMap lineinfo;
jitcode_line_info = &lineinfo;
@@ -13734,7 +13797,7 @@ UNINITIALIZED_TEST(SetJitCodeEventHandler) {
CHECK_LE(kIterations, saw_bar);
CHECK_LT(0, move_events);
- code_map = nullptr;
+ instruction_stream_map = nullptr;
jitcode_line_info = nullptr;
}
@@ -13754,7 +13817,7 @@ UNINITIALIZED_TEST(SetJitCodeEventHandler) {
// Now get code through initial iteration.
v8::base::HashMap code;
- code_map = &code;
+ instruction_stream_map = &code;
v8::base::HashMap lineinfo;
jitcode_line_info = &lineinfo;
@@ -13770,7 +13833,7 @@ UNINITIALIZED_TEST(SetJitCodeEventHandler) {
// with EnumExisting.
CHECK_LT(0u, code.occupancy());
- code_map = nullptr;
+ instruction_stream_map = nullptr;
}
isolate->Exit();
@@ -13805,12 +13868,10 @@ static void wasm_event_handler(const v8::JitCodeEvent* event) {
}
}
-namespace v8 {
-namespace internal {
-namespace wasm {
+namespace v8::internal::wasm {
TEST(WasmSetJitCodeEventHandler) {
v8::base::HashMap code;
- code_map = &code;
+ instruction_stream_map = &code;
v8::base::HashMap lineinfo;
jitcode_line_info = &lineinfo;
@@ -13822,15 +13883,21 @@ TEST(WasmSetJitCodeEventHandler) {
v8_isolate->SetJitCodeEventHandler(v8::kJitCodeEventDefault,
wasm_event_handler);
+ // Add (unreached) endless recursion to prevent fully inling "f". Otherwise we
+ // won't have source positions and will miss the
+ // {CODE_END_LINE_INFO_RECORDING} event.
TestSignatures sigs;
auto& f = r.NewFunction(sigs.i_i(), "f");
- BUILD(f, WASM_I32_ADD(WASM_LOCAL_GET(0), WASM_LOCAL_GET(0)));
+ f.Build({WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
+ WASM_LOCAL_SET(0, WASM_CALL_FUNCTION(f.function_index(),
+ WASM_LOCAL_GET(0)))),
+ 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))));
+ r.Build(
+ {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()
@@ -13841,11 +13908,8 @@ TEST(WasmSetJitCodeEventHandler) {
)";
CompileRun(script);
CHECK(saw_wasm_main);
- saw_wasm_main = false;
}
-} // namespace wasm
-} // namespace internal
-} // namespace v8
+} // namespace v8::internal::wasm
#endif // V8_ENABLE_WEBASSEMBLY
TEST(ExternalAllocatedMemory) {
@@ -16604,13 +16668,16 @@ TEST(TestIdleNotification) {
bool finished = false;
for (int i = 0; i < 200 && !finished; i++) {
if (i < 10 && CcTest::heap()->incremental_marking()->IsStopped()) {
- CcTest::heap()->StartIdleIncrementalMarking(
+ CcTest::heap()->StartIncrementalMarking(
+ i::Heap::kReduceMemoryFootprintMask,
i::GarbageCollectionReason::kTesting);
}
+ START_ALLOW_USE_DEPRECATED();
finished = env->GetIsolate()->IdleNotificationDeadline(
(v8::base::TimeTicks::Now().ToInternalValue() /
static_cast<double>(v8::base::Time::kMicrosecondsPerSecond)) +
IdlePauseInSeconds);
+ END_ALLOW_USE_DEPRECATED();
if (CcTest::heap()->sweeping_in_progress()) {
CcTest::heap()->EnsureSweepingCompleted(
i::Heap::SweepingForcedFinalizationMode::kV8Only);
@@ -16625,11 +16692,13 @@ TEST(TestMemorySavingsMode) {
LocalContext context;
v8::Isolate* isolate = context->GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
+ START_ALLOW_USE_DEPRECATED();
CHECK(!i_isolate->IsMemorySavingsModeActive());
isolate->EnableMemorySavingsMode();
CHECK(i_isolate->IsMemorySavingsModeActive());
isolate->DisableMemorySavingsMode();
CHECK(!i_isolate->IsMemorySavingsModeActive());
+ END_ALLOW_USE_DEPRECATED();
}
TEST(Regress2333) {
@@ -16776,6 +16845,8 @@ TEST(GetHeapSpaceStatistics) {
}
TEST(NumberOfNativeContexts) {
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
static const size_t kNumTestContexts = 10;
i::Isolate* isolate = CcTest::i_isolate();
i::HandleScope scope(isolate);
@@ -16800,6 +16871,8 @@ TEST(NumberOfNativeContexts) {
}
TEST(NumberOfDetachedContexts) {
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
static const size_t kNumTestContexts = 10;
i::Isolate* isolate = CcTest::i_isolate();
i::HandleScope scope(isolate);
@@ -17027,6 +17100,8 @@ TEST(ExternalInternalizedStringCollectedAtTearDown) {
TEST(ExternalInternalizedStringCollectedAtGC) {
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
int destroyed = 0;
{ LocalContext env;
v8::HandleScope handle_scope(env->GetIsolate());
@@ -17221,6 +17296,9 @@ TEST(Regress528) {
v8::Local<Context> other_context;
int gc_count;
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
+
// Create a context used to keep the code from aging in the compilation
// cache.
other_context = Context::New(isolate);
@@ -19608,6 +19686,8 @@ static int CountLiveMapsInMapCache(i::Context context) {
THREADED_TEST(Regress1516) {
LocalContext context;
v8::HandleScope scope(context->GetIsolate());
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
// Object with 20 properties is not a common case, so it should be removed
// from the cache after GC.
@@ -21088,198 +21168,6 @@ TEST(AccessCheckThrows) {
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::ScanStackModeScopeForTesting no_stack_scanning(
- CcTest::heap(), i::Heap::ScanStackMode::kNone);
- 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::v8_flags.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::v8_flags.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::v8_flags.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::v8_flags.enable_experimental_regexp_engine_on_excessive_backtracks = false;
- RegExpInterruptTest test;
- test.RunTest(RegExpInterruptTest::MakeSubjectTwoByteExternal);
-}
-
class RequestInterruptTestBase {
public:
RequestInterruptTestBase()
@@ -21834,7 +21722,6 @@ TEST(EscapableHandleScope) {
}
}
for (int i = 0; i < runs; i++) {
- Local<String> expected;
if (i != 0) {
CHECK(v8_str("escape value")
->Equals(context.local(), values[i])
@@ -23058,8 +22945,7 @@ void SourceURLHelper(v8::Isolate* isolate, const char* source_text,
Local<Value>(), // source map URL
false, // is opaque
false, // is WASM
- true // is ES Module
- );
+ true); // is ES Module
v8::ScriptCompiler::Source source(source_str, origin, nullptr);
Local<v8::Module> module =
@@ -23896,12 +23782,6 @@ TEST(StreamingWithIsolateScriptCache) {
// Variant of the above test which evicts the root SharedFunctionInfo from the
// Isolate script cache but still reuses the same Script.
TEST(StreamingWithIsolateScriptCacheClearingRootSFI) {
- // TODO(v8:12808): Remove this check once background compilation is capable of
- // reusing an existing Script.
- if (i::v8_flags.stress_background_compile) {
- return;
- }
-
StreamingWithIsolateScriptCache(true);
}
@@ -24769,7 +24649,7 @@ TEST(StringConcatOverflow) {
}
TEST(TurboAsmDisablesDetach) {
-#ifndef V8_LITE_MODE
+#if !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
i::v8_flags.turbofan = true;
i::v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(CcTest::isolate());
@@ -24803,7 +24683,7 @@ TEST(TurboAsmDisablesDetach) {
result = CompileRun(store).As<v8::ArrayBuffer>();
CHECK(!result->IsDetachable());
-#endif // V8_LITE_MODE
+#endif // !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
}
TEST(ClassPrototypeCreationContext) {
@@ -25694,8 +25574,8 @@ TEST(MemoryPressure) {
WeakCallCounter counter(1234);
// Conservative stack scanning might break results.
- i::ScanStackModeScopeForTesting no_stack_scanning(
- CcTest::heap(), i::Heap::ScanStackMode::kNone);
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
// Check that critical memory pressure notification sets GC interrupt.
auto garbage = CreateGarbageWithWeakCallCounter(isolate, &counter);
@@ -26857,9 +26737,9 @@ TEST(WasmI32AtomicWaitCallback) {
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),
+ r.Build({WASM_ATOMICS_WAIT(kExprI32AtomicWait, WASM_LOCAL_GET(0),
WASM_LOCAL_GET(1),
- WASM_I64_SCONVERT_F64(WASM_LOCAL_GET(2)), 4));
+ WASM_I64_SCONVERT_F64(WASM_LOCAL_GET(2)), 4)});
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
@@ -26892,9 +26772,9 @@ TEST(WasmI64AtomicWaitCallback) {
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),
+ r.Build({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));
+ WASM_I64_SCONVERT_F64(WASM_LOCAL_GET(2)), 8)});
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
@@ -27559,8 +27439,6 @@ static void CallIsolate2(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Local<v8::Context>::New(isolate_2, context_2);
v8::Context::Scope context_scope(context);
i::Heap* heap_2 = reinterpret_cast<i::Isolate*>(isolate_2)->heap();
- i::ScanStackModeScopeForTesting no_stack_scanning(
- heap_2, i::Heap::ScanStackMode::kNone);
heap_2->CollectAllGarbage(i::Heap::kForcedGC,
i::GarbageCollectionReason::kTesting);
CompileRun("f2() //# sourceURL=isolate2b");
@@ -27669,7 +27547,7 @@ UNINITIALIZED_TEST(NestedIsolates) {
#undef THREADED_PROFILED_TEST
-#ifndef V8_LITE_MODE
+#if !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
namespace {
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
@@ -28379,10 +28257,10 @@ void CheckDynamicTypeInfo() {
CHECK_EQ(c_func.ReturnInfo().GetType(), v8::CTypeInfo::Type::kVoid);
}
} // namespace
-#endif // V8_LITE_MODE
+#endif // !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
TEST(FastApiStackSlot) {
-#ifndef V8_LITE_MODE
+#if !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
if (i::v8_flags.jitless) return;
i::v8_flags.turbofan = true;
@@ -28430,11 +28308,11 @@ TEST(FastApiStackSlot) {
int32_t slow_value_typed = checker.slow_value_.ToChecked();
CHECK_EQ(slow_value_typed, test_value);
CHECK_EQ(checker.fast_value_, test_value);
-#endif
+#endif // !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
}
TEST(FastApiCalls) {
-#ifndef V8_LITE_MODE
+#if !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
if (i::v8_flags.jitless) return;
i::v8_flags.turbofan = true;
@@ -28901,10 +28779,10 @@ TEST(FastApiCalls) {
// 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
+#endif // !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
}
-#ifndef V8_LITE_MODE
+#if !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
namespace {
static Trivial* UnwrapTrivialObject(Local<Object> object) {
i::Address addr = *reinterpret_cast<i::Address*>(*object);
@@ -28981,10 +28859,10 @@ void SequenceSlowCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
return;
}
} // namespace
-#endif // V8_LITE_MODE
+#endif // !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
TEST(FastApiSequenceOverloads) {
-#ifndef V8_LITE_MODE
+#if !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
if (i::v8_flags.jitless) return;
i::v8_flags.turbofan = true;
@@ -29038,11 +28916,11 @@ TEST(FastApiSequenceOverloads) {
CompileRun("const ta = new Int32Array([1, 2, 3, 4]);"
"func(4, ta);"));
CHECK_EQ(4, rcv->x());
-#endif // V8_LITE_MODE
+#endif // !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
}
TEST(FastApiOverloadResolution) {
-#ifndef V8_LITE_MODE
+#if !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
if (i::v8_flags.jitless) return;
i::v8_flags.turbofan = true;
@@ -29086,7 +28964,7 @@ TEST(FastApiOverloadResolution) {
CHECK_EQ(v8::CFunction::OverloadResolution::kAtCompileTime,
typed_array_callback.GetOverloadResolution(&diff_arity_callback));
-#endif // V8_LITE_MODE
+#endif // !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN)
}
THREADED_TEST(Recorder_GetContext) {
@@ -29096,6 +28974,8 @@ THREADED_TEST(Recorder_GetContext) {
// Set up isolate and context.
v8::Isolate* iso = CcTest::isolate();
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
v8::metrics::Recorder::ContextId original_id;
std::vector<v8::metrics::Recorder::ContextId> ids;
{
@@ -29179,6 +29059,9 @@ TEST(TriggerMainThreadMetricsEvent) {
using v8::Local;
using v8::MaybeLocal;
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
+
// Set up isolate and context.
v8::Isolate* iso = CcTest::isolate();
i::Isolate* i_iso = reinterpret_cast<i::Isolate*>(iso);
@@ -29218,6 +29101,9 @@ TEST(TriggerDelayedMainThreadMetricsEvent) {
using v8::MaybeLocal;
i::v8_flags.stress_concurrent_allocation = false;
+ i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
+ CcTest::heap());
+
// Set up isolate and context.
v8::Isolate* iso = CcTest::isolate();
i::Isolate* i_iso = reinterpret_cast<i::Isolate*>(iso);
@@ -29571,3 +29457,653 @@ UNINITIALIZED_TEST(OOMDetailsAreMovableAndCopyable) {
UNINITIALIZED_TEST(JitCodeEventIsMovableAndCopyable) {
TestCopyAndMoveConstructionAndAssignment<v8::JitCodeEvent>();
}
+
+#if V8_ENABLE_WEBASSEMBLY
+TEST(WasmAbortStreamingAfterContextDisposal) {
+ // This is a regression test for https://crbug.com/1403531.
+
+ class Resolver final : public i::wasm::CompilationResultResolver {
+ public:
+ void OnCompilationSucceeded(
+ i::Handle<i::WasmModuleObject> result) override {
+ UNREACHABLE();
+ }
+ void OnCompilationFailed(i::Handle<i::Object> error_reason) override {
+ UNREACHABLE();
+ }
+ };
+
+ auto resolver = std::make_shared<Resolver>();
+
+ std::unique_ptr<v8::WasmStreaming> wasm_streaming;
+ v8::Isolate* isolate = CcTest::isolate();
+ i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
+ {
+ v8::HandleScope scope(isolate);
+ LocalContext context;
+
+ wasm_streaming =
+ i::wasm::StartStreamingForTesting(i_isolate, std::move(resolver));
+ isolate->ContextDisposedNotification(false);
+ }
+
+ wasm_streaming->Abort({});
+ wasm_streaming.reset();
+}
+#endif // V8_ENABLE_WEBASSEMBLY
+
+TEST(DeepFreezeIncompatibleTypes) {
+ const int numCases = 7;
+ struct {
+ const char* script;
+ const char* exception;
+ } test_cases[numCases] = {
+ {
+ R"(
+ "use strict"
+ let foo = 1;
+ )",
+ "TypeError: Cannot DeepFreeze non-const value foo"},
+ {
+ R"(
+ "use strict"
+ const foo = 1;
+ const generator = function*() {
+ yield 1;
+ yield 2;
+ }
+ const gen = generator();
+ )",
+ "TypeError: Cannot DeepFreeze object of type Generator"},
+ {
+ R"(
+ "use strict"
+ const incrementer = (function() {
+ let a = 1;
+ return function() { a += 1; return a; };
+ })();
+ )",
+ "TypeError: Cannot DeepFreeze non-const value a"},
+ {
+ R"(
+ let a = new Number();
+ )",
+ "TypeError: Cannot DeepFreeze non-const value a"},
+ {
+ R"(
+ const a = [0, 1, 2, 3, 4, 5];
+ var it = a[Symbol.iterator]();
+ function foo() {
+ return it.next().value;
+ }
+ foo();
+ )",
+ "TypeError: Cannot DeepFreeze object of type Array Iterator"},
+ {
+ R"(
+ const a = "0123456789";
+ var it = a[Symbol.iterator]();
+ function foo() {
+ return it.next().value;
+ }
+ foo();
+ )",
+ "TypeError: Cannot DeepFreeze object of type Object"},
+ {R"(
+ const a = "0123456789";
+ var it = a.matchAll(/\d/g);
+ function foo() {
+ return it.next().value;
+ }
+ foo();
+ )",
+ "TypeError: Cannot DeepFreeze object of type Object"},
+ };
+
+ for (int idx = 0; idx < numCases; idx++) {
+ LocalContext env;
+ v8::Isolate* isolate = env->GetIsolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<v8::Context> context = env.local();
+ v8::Maybe<void> maybe_success = v8::Nothing<void>();
+ CompileRun(context, test_cases[idx].script);
+ v8::TryCatch tc(isolate);
+ maybe_success = context->DeepFreeze(nullptr);
+ CHECK(maybe_success.IsNothing());
+ CHECK(tc.HasCaught());
+ v8::String::Utf8Value uS(isolate, tc.Exception());
+ std::string exception(*uS, uS.length());
+ CHECK_EQ(std::string(test_cases[idx].exception), exception);
+ }
+}
+
+TEST(DeepFreezeIsFrozen) {
+ const int numCases = 10;
+ struct {
+ const char* script;
+ const char* exception;
+ int32_t expected;
+ } test_cases[numCases] = {
+ {// Closure
+ R"(
+ const incrementer = (function() {
+ const a = {b: 1};
+ return function() { a.b += 1; return a.b; };
+ })();
+ const foo = function() { return incrementer(); }
+ foo();
+ )",
+ nullptr, 2},
+ {
+ R"(
+ const incrementer = (function() {
+ const a = {b: 1};
+ return function() { a.b += 1; return a.b; };
+ })();
+ const foo = function() { return incrementer(); }
+ foo();
+ )",
+ nullptr, 2},
+ {// Array
+ R"(
+ const a = [0, -1, -2];
+ const foo = function() { a[0] += 1; return a[0]; }
+ )",
+ nullptr, 0},
+ {
+ R"(
+ const a = [0, -1, -2];
+ const foo = function() { a[0] += 1; return a[0]; }
+ )",
+ nullptr, 0},
+ {// Wrapper Objects
+ R"(
+ const a = {b: new Number()};
+ const foo = function() {
+ a.b = new Number(a.b + 1);
+ return a.b.valueOf();
+ }
+ )",
+ nullptr, 0},
+ {// Functions
+ // Assignment to constant doesn't work.
+ R"(
+ const foo = function() {
+ foo = function() { return 2;}
+ return 1;
+ }
+ )",
+ "TypeError: Assignment to constant variable.", 0},
+ {
+ R"(
+ const a = {b: {c: {d: {e: {f: 1}}}}};
+ const foo = function() {
+ a.b.c.d.e.f += 1;
+ return a.b.c.d.e.f;
+ }
+ )",
+ nullptr, 1},
+ {
+ R"(
+ const foo = function() {
+ if (!('count' in globalThis))
+ globalThis.count = 1;
+ ++count;
+ return count;
+ }
+ )",
+ "ReferenceError: count is not defined", 0},
+ {
+ R"(
+ const countPrototype = {
+ get() {
+ return 1;
+ },
+ };
+ const count = Object.create(countPrototype);
+ function foo() {
+ const curr_count = count.get();
+ count.prototype = { get() { return curr_count + 1; }};
+ return count.get();
+ }
+ )",
+ nullptr, 1},
+ {
+ R"(
+ const a = (function(){
+ function A(){};
+ A.o = 1;
+ return new A();
+ })();
+ function foo() {
+ a.constructor.o++;
+ return a.constructor.o;
+ }
+ )",
+ nullptr, 1},
+ };
+ for (int idx = 0; idx < numCases; idx++) {
+ LocalContext env;
+ v8::Isolate* isolate = env->GetIsolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<v8::Context> context = env.local();
+ v8::Maybe<void> maybe_success = v8::Nothing<void>();
+ v8::TryCatch tc(isolate);
+ v8::MaybeLocal<v8::Value> status =
+ CompileRun(context, test_cases[idx].script);
+ CHECK(!status.IsEmpty());
+ CHECK(!tc.HasCaught());
+
+ maybe_success = context->DeepFreeze(nullptr);
+ CHECK(!tc.HasCaught());
+ status = CompileRun(context, "foo()");
+
+ if (test_cases[idx].exception) {
+ CHECK(tc.HasCaught());
+ v8::String::Utf8Value uS(isolate, tc.Exception());
+ std::string exception(*uS, uS.length());
+ CHECK_EQ(std::string(test_cases[idx].exception), exception);
+ } else {
+ CHECK(!tc.HasCaught());
+ CHECK(!status.IsEmpty());
+ ExpectInt32("foo()", test_cases[idx].expected);
+ }
+ }
+}
+
+TEST(DeepFreezeAllowsSyntax) {
+ const int numCases = 2;
+ struct {
+ const char* script;
+ int32_t expected;
+ } test_cases[numCases] = {
+ {
+ R"(
+ const a = 1;
+ function foo() {
+ let b = 4;
+ b += 1;
+ return a + b;
+ }
+ )",
+ 6,
+ },
+ {
+ R"(
+ var a = 1;
+ function foo() {
+ let b = 4;
+ b += 1;
+ return a + b;
+ }
+ )",
+ 6,
+ }}; // TODO(behamilton): Add more cases that should be supported.
+ for (int idx = 0; idx < numCases; idx++) {
+ LocalContext env;
+ v8::Isolate* isolate = env->GetIsolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<v8::Context> context = env.local();
+ v8::Maybe<void> maybe_success = v8::Nothing<void>();
+ v8::MaybeLocal<v8::Value> status =
+ CompileRun(context, test_cases[idx].script);
+ CHECK(!status.IsEmpty());
+ maybe_success = context->DeepFreeze(nullptr);
+ CHECK(!maybe_success.IsNothing());
+ ExpectInt32("foo()", test_cases[idx].expected);
+ }
+}
+
+namespace {
+void DoNothing(const v8::FunctionCallbackInfo<v8::Value>& ignored) {}
+
+class AllowEmbedderObjects : public v8::Context::DeepFreezeDelegate {
+ public:
+ bool FreezeEmbedderObjectAndGetChildren(
+ v8::Local<v8::Object> obj,
+ std::vector<v8::Local<v8::Object>>& children_out) override {
+ return true;
+ }
+};
+
+} // namespace
+
+TEST(DeepFreezesJSApiObjectWithDelegate) {
+ const int numCases = 3;
+ struct {
+ const char* script;
+ std::function<void()> run_check;
+ } test_cases[numCases] = {
+ {
+ R"(
+ globalThis.jsApiObject.foo = {test: 4};
+ function foo() {
+ globalThis.jsApiObject.foo.test++;
+ return globalThis.jsApiObject.foo.test;
+ }
+ foo();
+ )",
+ []() { ExpectInt32("foo()", 5); }},
+ {
+ R"(
+ function foo() {
+ if (!('foo' in globalThis.jsApiObject))
+ globalThis.jsApiObject.foo = {test: 4}
+ globalThis.jsApiObject.foo.test++;
+ return globalThis.jsApiObject.foo.test;
+ }
+ foo();
+ )",
+ []() { ExpectInt32("foo()", 5); }},
+ {
+ R"(
+ function foo() {
+ if (!('foo' in globalThis.jsApiObject))
+ globalThis.jsApiObject.foo = 4
+ globalThis.jsApiObject.foo++;
+ return globalThis.jsApiObject.foo;
+ }
+ )",
+ []() { ExpectUndefined("foo()"); }},
+ };
+
+ for (int idx = 0; idx < numCases; idx++) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<v8::ObjectTemplate> global_template =
+ v8::ObjectTemplate::New(isolate);
+ v8::Local<v8::FunctionTemplate> v8_template =
+ v8::FunctionTemplate::New(isolate, &DoNothing);
+ v8_template->RemovePrototype();
+ global_template->Set(v8_str("jsApiObject"), v8_template);
+
+ LocalContext env(isolate, /*extensions=*/nullptr, global_template);
+ v8::Local<v8::Context> context = env.local();
+
+ v8::TryCatch tc(isolate);
+ v8::MaybeLocal<v8::Value> status =
+ CompileRun(context, test_cases[idx].script);
+ CHECK(!tc.HasCaught());
+ CHECK(!status.IsEmpty());
+
+ AllowEmbedderObjects delegate;
+ v8::Maybe<void> maybe_success = context->DeepFreeze(&delegate);
+ CHECK(!tc.HasCaught());
+ CHECK(!maybe_success.IsNothing());
+
+ test_cases[idx].run_check();
+ }
+}
+
+namespace {
+
+class MyObject {
+ public:
+ bool Freeze() {
+ was_frozen_ = true;
+ return true;
+ }
+
+ bool was_frozen_ = false;
+ v8::Local<v8::Object> internal_data_;
+};
+
+class HiddenDataDelegate : public v8::Context::DeepFreezeDelegate {
+ public:
+ explicit HiddenDataDelegate(v8::Local<v8::External> my_object)
+ : my_object_(my_object) {}
+
+ bool FreezeEmbedderObjectAndGetChildren(
+ v8::Local<v8::Object> obj,
+ std::vector<v8::Local<v8::Object>>& children_out) override {
+ int fields = obj->InternalFieldCount();
+ for (int idx = 0; idx < fields; idx++) {
+ v8::Local<v8::Value> child_value = obj->GetInternalField(idx);
+ if (child_value->IsExternal()) {
+ if (!FreezeExternal(v8::Local<v8::External>::Cast(child_value),
+ children_out)) {
+ return false;
+ }
+ }
+ }
+ if (obj->IsExternal()) {
+ return FreezeExternal(v8::Local<v8::External>::Cast(obj), children_out);
+ }
+ return true;
+ }
+
+ private:
+ bool FreezeExternal(v8::Local<v8::External> ext,
+ std::vector<v8::Local<v8::Object>>& children_out) {
+ if (ext->Value() == my_object_->Value()) {
+ MyObject* my_obj = static_cast<MyObject*>(ext->Value());
+ if (my_obj->Freeze()) {
+ children_out.push_back(my_obj->internal_data_);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ v8::Local<v8::External> my_object_;
+};
+
+} // namespace
+
+TEST(DeepFreezeDoesntFreezeJSApiObjectFunctionData) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+
+ MyObject foo;
+ v8::Local<v8::External> v8_foo = v8::External::New(isolate, &foo);
+
+ v8::Local<v8::ObjectTemplate> global_template =
+ v8::ObjectTemplate::New(isolate);
+ v8::Local<v8::FunctionTemplate> v8_template =
+ v8::FunctionTemplate::New(isolate, &DoNothing, /*data=*/v8_foo);
+ v8_template->RemovePrototype();
+ global_template->Set(v8_str("jsApiObject"), v8_template);
+
+ LocalContext env(isolate, /*extensions=*/nullptr, global_template);
+ v8::Local<v8::Context> context = env.local();
+
+ foo = {false, v8::Object::New(isolate)};
+
+ HiddenDataDelegate hdd{v8_foo};
+ v8::TryCatch tc(isolate);
+
+ v8::Maybe<void> maybe_success = context->DeepFreeze(&hdd);
+
+ CHECK(!maybe_success.IsNothing());
+ CHECK(!foo.was_frozen_);
+
+ v8::Local<v8::String> param_list[] = {v8_str("obj")};
+ v8::Local<v8::Value> params[] = {
+ v8::Local<v8::Value>::Cast(foo.internal_data_)};
+ v8::ScriptCompiler::Source source{v8_str("return Object.isFrozen(obj)")};
+ v8::Local<v8::Function> is_frozen =
+ v8::ScriptCompiler::CompileFunction(context, &source, 1, param_list)
+ .ToLocalChecked();
+ v8::MaybeLocal<v8::Value> result =
+ is_frozen->Call(context, context->Global(), 1, params);
+
+ CHECK(!result.IsEmpty());
+ CHECK(result.ToLocalChecked()->IsFalse());
+}
+
+TEST(DeepFreezeForbidsJSApiObjectWithoutDelegate) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+
+ v8::Local<v8::ObjectTemplate> global_template =
+ v8::ObjectTemplate::New(isolate);
+ v8::Local<v8::ObjectTemplate> v8_template = v8::ObjectTemplate::New(isolate);
+ v8_template->SetInternalFieldCount(1);
+ global_template->Set(v8_str("jsApiObject"), v8_template);
+
+ LocalContext env(isolate, /*extensions=*/nullptr, global_template);
+ v8::Local<v8::Context> context = env.local();
+
+ MyObject foo{false, v8::Object::New(isolate)};
+ v8::Local<v8::External> v8_foo = v8::External::New(isolate, &foo);
+
+ v8::Local<v8::Value> val =
+ context->Global()->Get(context, v8_str("jsApiObject")).ToLocalChecked();
+ CHECK(val->IsObject());
+ v8::Local<v8::Object> obj = v8::Local<v8::Object>::Cast(val);
+ CHECK_EQ(1, obj->InternalFieldCount());
+ obj->SetInternalField(0, v8_foo);
+
+ v8::TryCatch tc(isolate);
+ v8::Maybe<void> maybe_success = context->DeepFreeze(nullptr);
+
+ CHECK(tc.HasCaught());
+ v8::String::Utf8Value uS(isolate, tc.Exception());
+ std::string exception(*uS, uS.length());
+ CHECK_EQ(std::string("TypeError: Cannot DeepFreeze object of type Object"),
+ exception);
+ CHECK(maybe_success.IsNothing());
+}
+
+TEST(DeepFreezeFreezesJSApiObjectData) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+
+ v8::Local<v8::ObjectTemplate> global_template =
+ v8::ObjectTemplate::New(isolate);
+ v8::Local<v8::ObjectTemplate> v8_template = v8::ObjectTemplate::New(isolate);
+ v8_template->SetInternalFieldCount(1);
+ global_template->Set(v8_str("jsApiObject"), v8_template);
+
+ LocalContext env(isolate, /*extensions=*/nullptr, global_template);
+ v8::Local<v8::Context> context = env.local();
+
+ MyObject foo{false, v8::Object::New(isolate)};
+ v8::Local<v8::External> v8_foo = v8::External::New(isolate, &foo);
+
+ v8::Local<v8::Value> val =
+ context->Global()->Get(context, v8_str("jsApiObject")).ToLocalChecked();
+ CHECK(val->IsObject());
+ v8::Local<v8::Object> obj = v8::Local<v8::Object>::Cast(val);
+ CHECK_EQ(1, obj->InternalFieldCount());
+ obj->SetInternalField(0, v8_foo);
+
+ HiddenDataDelegate hdd{v8_foo};
+
+ v8::TryCatch tc(isolate);
+
+ v8::Maybe<void> maybe_success = context->DeepFreeze(&hdd);
+
+ CHECK(!maybe_success.IsNothing());
+ CHECK(foo.was_frozen_);
+
+ v8::Local<v8::String> param_list[] = {v8_str("obj")};
+ v8::Local<v8::Value> params[] = {
+ v8::Local<v8::Value>::Cast(foo.internal_data_)};
+ v8::ScriptCompiler::Source source{v8_str("return Object.isFrozen(obj)")};
+ v8::Local<v8::Function> is_frozen =
+ v8::ScriptCompiler::CompileFunction(context, &source, 1, param_list)
+ .ToLocalChecked();
+ v8::MaybeLocal<v8::Value> result =
+ is_frozen->Call(context, context->Global(), 1, params);
+
+ CHECK(!result.IsEmpty());
+ CHECK(result.ToLocalChecked()->IsTrue());
+}
+
+TEST(DeepFreezeFreezesExternalObjectData) {
+ LocalContext env;
+ v8::Isolate* isolate = env->GetIsolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<v8::Context> context = env.local();
+
+ MyObject foo{false, v8::Object::New(isolate)};
+ v8::Local<v8::External> v8_foo = v8::External::New(isolate, &foo);
+ v8::Maybe<bool> success =
+ context->Global()->CreateDataProperty(context, v8_str("foo"), v8_foo);
+ CHECK(!success.IsNothing() && success.FromJust());
+
+ HiddenDataDelegate hdd{v8_foo};
+
+ v8::Maybe<void> maybe_success = context->DeepFreeze(&hdd);
+
+ CHECK(!maybe_success.IsNothing());
+ CHECK(foo.was_frozen_);
+
+ v8::Local<v8::String> param_list[] = {v8_str("obj")};
+ v8::Local<v8::Value> params[] = {
+ v8::Local<v8::Value>::Cast(foo.internal_data_)};
+ v8::ScriptCompiler::Source source{v8_str("return Object.isFrozen(obj)")};
+ v8::Local<v8::Function> is_frozen =
+ v8::ScriptCompiler::CompileFunction(context, &source, 1, param_list)
+ .ToLocalChecked();
+ v8::MaybeLocal<v8::Value> result =
+ is_frozen->Call(context, context->Global(), 1, params);
+
+ CHECK(!result.IsEmpty());
+ CHECK(result.ToLocalChecked()->IsTrue());
+}
+
+namespace {
+void handle_property(Local<String> name,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ info.GetReturnValue().Set(v8_num(900));
+}
+
+void handle_property_2(Local<String> name,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ info.GetReturnValue().Set(v8_num(902));
+}
+
+void handle_property(const v8::FunctionCallbackInfo<v8::Value>& info) {
+ CHECK_EQ(0, info.Length());
+ info.GetReturnValue().Set(v8_num(907));
+}
+
+} // namespace
+
+TEST(DeepFreezeInstantiatesAccessors) {
+ LocalContext env;
+ v8::Isolate* isolate = env->GetIsolate();
+ v8::HandleScope scope(isolate);
+ Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(isolate);
+ Local<v8::FunctionTemplate> getter_templ =
+ v8::FunctionTemplate::New(isolate, handle_property);
+ getter_templ->SetLength(0);
+ fun_templ->SetAccessorProperty(v8_str("bar"), getter_templ);
+ fun_templ->SetNativeDataProperty(v8_str("instance_foo"), handle_property);
+ fun_templ->SetNativeDataProperty(v8_str("object_foo"), handle_property_2);
+ Local<Function> fun = fun_templ->GetFunction(env.local()).ToLocalChecked();
+ CHECK(env->Global()->Set(env.local(), v8_str("Fun"), fun).FromJust());
+
+ v8::Local<v8::Context> context = env.local();
+ v8::Maybe<void> maybe_success = context->DeepFreeze(nullptr);
+ CHECK(maybe_success.IsNothing());
+}
+
+namespace {
+void handle_object_property(v8::Local<v8::String> property,
+ const v8::PropertyCallbackInfo<Value>& info) {
+ info.GetReturnValue().Set(v8_num(909));
+}
+} // namespace
+
+TEST(DeepFreezeInstantiatesAccessors2) {
+ LocalContext env;
+ v8::Isolate* isolate = env->GetIsolate();
+ v8::HandleScope scope(isolate);
+ Local<v8::ObjectTemplate> fun_templ = v8::ObjectTemplate::New(isolate);
+ fun_templ->SetAccessor(v8_str("foo"), handle_object_property);
+ Local<v8::FunctionTemplate> getter_templ =
+ v8::FunctionTemplate::New(isolate, handle_property);
+ getter_templ->SetLength(0);
+ fun_templ->SetAccessorProperty(v8_str("bar"), getter_templ);
+ fun_templ->SetNativeDataProperty(v8_str("instance_foo"), handle_property);
+ fun_templ->SetNativeDataProperty(v8_str("object_foo"), handle_property_2);
+ Local<Object> fun = fun_templ->NewInstance(env.local()).ToLocalChecked();
+ CHECK(env->Global()->Set(env.local(), v8_str("Fun"), fun).FromJust());
+
+ v8::Local<v8::Context> context = env.local();
+ v8::Maybe<void> maybe_success = context->DeepFreeze(nullptr);
+ CHECK(maybe_success.IsNothing());
+}