// Copyright 2020 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/api/api.h" #include "src/base/platform/semaphore.h" #include "src/handles/handles-inl.h" #include "src/handles/local-handles-inl.h" #include "src/handles/persistent-handles.h" #include "src/heap/heap.h" #include "src/heap/local-heap-inl.h" #include "src/heap/local-heap.h" #include "src/heap/parked-scope.h" #include "test/cctest/cctest.h" #include "test/cctest/heap/heap-utils.h" namespace v8 { namespace internal { namespace { #define DOUBLE_VALUE 28.123456789 #define STRING_VALUE "28.123456789" #define ARRAY_VALUE \ { '2', '8', '.', '1', '2', '3', '4', '5', '6', '7', '8', '9' } // Adapted from cctest/test-api.cc, and // test/cctest/heap/test-external-string-tracker.cc. class TestOneByteResource : public v8::String::ExternalOneByteStringResource { public: explicit TestOneByteResource(const char* data) : data_(data), length_(strlen(data)) {} ~TestOneByteResource() override { i::DeleteArray(data_); } const char* data() const override { return data_; } size_t length() const override { return length_; } private: const char* data_; size_t length_; }; // Adapted from cctest/test-api.cc. class TestTwoByteResource : public v8::String::ExternalStringResource { public: explicit TestTwoByteResource(uint16_t* data) : data_(data), length_(0) { while (data[length_]) ++length_; } ~TestTwoByteResource() override { i::DeleteArray(data_); } const uint16_t* data() const override { return data_; } size_t length() const override { return length_; } private: uint16_t* data_; size_t length_; }; class ConcurrentStringThread final : public v8::base::Thread { public: ConcurrentStringThread(Isolate* isolate, Handle str, std::unique_ptr ph, base::Semaphore* sema_started, std::vector chars) : v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")), isolate_(isolate), str_(str), ph_(std::move(ph)), sema_started_(sema_started), length_(chars.size()), chars_(chars) {} void Run() override { LocalIsolate local_isolate(isolate_, ThreadKind::kBackground); local_isolate.heap()->AttachPersistentHandles(std::move(ph_)); UnparkedScope unparked_scope(local_isolate.heap()); sema_started_->Signal(); // Check the three operations we do from the StringRef concurrently: get the // string, the nth character, and convert into a double. CHECK_EQ(str_->length(kAcquireLoad), length_); for (unsigned int i = 0; i < length_; ++i) { CHECK_EQ(str_->Get(i, &local_isolate), chars_[i]); } CHECK_EQ(TryStringToDouble(&local_isolate, str_).value(), DOUBLE_VALUE); } private: Isolate* isolate_; Handle str_; std::unique_ptr ph_; base::Semaphore* sema_started_; uint64_t length_; std::vector chars_; }; // Inspect a one byte string, while the main thread externalizes it. TEST(InspectOneByteExternalizing) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); std::unique_ptr ph = isolate->NewPersistentHandles(); auto factory = isolate->factory(); HandleScope handle_scope(isolate); // Crate an internalized one-byte string. const char* raw_string = STRING_VALUE; Handle one_byte_string = factory->InternalizeString( factory->NewStringFromAsciiChecked(raw_string)); CHECK(one_byte_string->IsOneByteRepresentation()); CHECK(!one_byte_string->IsExternalString()); CHECK(one_byte_string->IsInternalizedString()); Handle persistent_string = ph->NewHandle(one_byte_string); std::vector chars; for (int i = 0; i < one_byte_string->length(); ++i) { chars.push_back(one_byte_string->Get(i)); } base::Semaphore sema_started(0); std::unique_ptr thread(new ConcurrentStringThread( isolate, persistent_string, std::move(ph), &sema_started, chars)); CHECK(thread->Start()); sema_started.Wait(); // Externalize it to a one-byte external string. // We need to use StrDup in this case since the TestOneByteResource will get // ownership of raw_string otherwise. CHECK(one_byte_string->MakeExternal( new TestOneByteResource(i::StrDup(raw_string)))); CHECK(one_byte_string->IsExternalOneByteString()); CHECK(one_byte_string->IsInternalizedString()); thread->Join(); } // Inspect a one byte string, while the main thread externalizes it into a two // bytes string. TEST(InspectOneIntoTwoByteExternalizing) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); std::unique_ptr ph = isolate->NewPersistentHandles(); auto factory = isolate->factory(); HandleScope handle_scope(isolate); // Crate an internalized one-byte string. const char* raw_string = STRING_VALUE; Handle one_byte_string = factory->InternalizeString( factory->NewStringFromAsciiChecked(raw_string)); CHECK(one_byte_string->IsOneByteRepresentation()); CHECK(!one_byte_string->IsExternalString()); CHECK(one_byte_string->IsInternalizedString()); Handle persistent_string = ph->NewHandle(one_byte_string); std::vector chars; for (int i = 0; i < one_byte_string->length(); ++i) { chars.push_back(one_byte_string->Get(i)); } base::Semaphore sema_started(0); std::unique_ptr thread(new ConcurrentStringThread( isolate, persistent_string, std::move(ph), &sema_started, chars)); CHECK(thread->Start()); sema_started.Wait(); // Externalize it to a two-bytes external string. AsciiToTwoByteString does // the string duplication for us. CHECK(one_byte_string->MakeExternal( new TestTwoByteResource(AsciiToTwoByteString(raw_string)))); CHECK(one_byte_string->IsExternalTwoByteString()); CHECK(one_byte_string->IsInternalizedString()); thread->Join(); } // Inspect a two byte string, while the main thread externalizes it. TEST(InspectTwoByteExternalizing) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); std::unique_ptr ph = isolate->NewPersistentHandles(); auto factory = isolate->factory(); HandleScope handle_scope(isolate); // Crate an internalized two-byte string. // TODO(solanes): Can we have only one raw string? const char* raw_string = STRING_VALUE; // TODO(solanes): Is this the best way to create a two byte string from chars? const int kLength = 12; const uint16_t two_byte_array[kLength] = ARRAY_VALUE; Handle two_bytes_string; { Handle raw = factory->NewRawTwoByteString(kLength).ToHandleChecked(); DisallowGarbageCollection no_gc; CopyChars(raw->GetChars(no_gc), two_byte_array, kLength); two_bytes_string = raw; } two_bytes_string = factory->InternalizeString(two_bytes_string); CHECK(two_bytes_string->IsTwoByteRepresentation()); CHECK(!two_bytes_string->IsExternalString()); CHECK(two_bytes_string->IsInternalizedString()); Handle persistent_string = ph->NewHandle(two_bytes_string); std::vector chars; for (int i = 0; i < two_bytes_string->length(); ++i) { chars.push_back(two_bytes_string->Get(i)); } base::Semaphore sema_started(0); std::unique_ptr thread(new ConcurrentStringThread( isolate, persistent_string, std::move(ph), &sema_started, chars)); CHECK(thread->Start()); sema_started.Wait(); // Externalize it to a two-bytes external string. CHECK(two_bytes_string->MakeExternal( new TestTwoByteResource(AsciiToTwoByteString(raw_string)))); CHECK(two_bytes_string->IsExternalTwoByteString()); CHECK(two_bytes_string->IsInternalizedString()); thread->Join(); } // Inspect a one byte string, while the main thread externalizes it. Same as // InspectOneByteExternalizing, but using thin strings. TEST(InspectOneByteExternalizing_ThinString) { // We will not create a thin string if single_generation is turned on. if (FLAG_single_generation) return; CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); std::unique_ptr ph = isolate->NewPersistentHandles(); auto factory = isolate->factory(); HandleScope handle_scope(isolate); // Create a string. const char* raw_string = STRING_VALUE; Handle thin_string = factory->NewStringFromAsciiChecked(raw_string); CHECK(thin_string->IsOneByteRepresentation()); CHECK(!thin_string->IsExternalString()); CHECK(!thin_string->IsInternalizedString()); // Crate an internalized one-byte version of that string string. Handle internalized_string = factory->InternalizeString(thin_string); CHECK(internalized_string->IsOneByteRepresentation()); CHECK(!internalized_string->IsExternalString()); CHECK(internalized_string->IsInternalizedString()); // We now should have an internalized string, and a thin string pointing to // it. CHECK(thin_string->IsThinString()); CHECK_NE(*thin_string, *internalized_string); Handle persistent_string = ph->NewHandle(thin_string); std::vector chars; for (int i = 0; i < thin_string->length(); ++i) { chars.push_back(thin_string->Get(i)); } base::Semaphore sema_started(0); std::unique_ptr thread(new ConcurrentStringThread( isolate, persistent_string, std::move(ph), &sema_started, chars)); CHECK(thread->Start()); sema_started.Wait(); // Externalize it to a one-byte external string. // We need to use StrDup in this case since the TestOneByteResource will get // ownership of raw_string otherwise. CHECK(internalized_string->MakeExternal( new TestOneByteResource(i::StrDup(raw_string)))); CHECK(internalized_string->IsExternalOneByteString()); CHECK(internalized_string->IsInternalizedString()); // Check that the thin string is unmodified. CHECK(!thin_string->IsExternalString()); CHECK(!thin_string->IsInternalizedString()); CHECK(thin_string->IsThinString()); thread->Join(); } // Inspect a one byte string, while the main thread externalizes it into a two // bytes string. Same as InspectOneIntoTwoByteExternalizing, but using thin // strings. TEST(InspectOneIntoTwoByteExternalizing_ThinString) { // We will not create a thin string if single_generation is turned on. if (FLAG_single_generation) return; CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); std::unique_ptr ph = isolate->NewPersistentHandles(); auto factory = isolate->factory(); HandleScope handle_scope(isolate); // Create a string. const char* raw_string = STRING_VALUE; Handle thin_string = factory->NewStringFromAsciiChecked(raw_string); CHECK(thin_string->IsOneByteRepresentation()); CHECK(!thin_string->IsExternalString()); CHECK(!thin_string->IsInternalizedString()); // Crate an internalized one-byte version of that string string. Handle internalized_string = factory->InternalizeString(thin_string); CHECK(internalized_string->IsOneByteRepresentation()); CHECK(!internalized_string->IsExternalString()); CHECK(internalized_string->IsInternalizedString()); // We now should have an internalized string, and a thin string pointing to // it. CHECK(thin_string->IsThinString()); CHECK_NE(*thin_string, *internalized_string); Handle persistent_string = ph->NewHandle(thin_string); std::vector chars; for (int i = 0; i < thin_string->length(); ++i) { chars.push_back(thin_string->Get(i)); } base::Semaphore sema_started(0); std::unique_ptr thread(new ConcurrentStringThread( isolate, persistent_string, std::move(ph), &sema_started, chars)); CHECK(thread->Start()); sema_started.Wait(); // Externalize it to a two-bytes external string. AsciiToTwoByteString does // the string duplication for us. CHECK(internalized_string->MakeExternal( new TestTwoByteResource(AsciiToTwoByteString(raw_string)))); CHECK(internalized_string->IsExternalTwoByteString()); CHECK(internalized_string->IsInternalizedString()); // Check that the thin string is unmodified. CHECK(!thin_string->IsExternalString()); CHECK(!thin_string->IsInternalizedString()); CHECK(thin_string->IsThinString()); // Even its representation is still one byte, even when the internalized // string moved to two bytes. CHECK(thin_string->IsOneByteRepresentation()); thread->Join(); } // Inspect a two byte string, while the main thread externalizes it. Same as // InspectTwoByteExternalizing, but using thin strings. TEST(InspectTwoByteExternalizing_ThinString) { // We will not create a thin string if single_generation is turned on. if (FLAG_single_generation) return; CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); std::unique_ptr ph = isolate->NewPersistentHandles(); auto factory = isolate->factory(); HandleScope handle_scope(isolate); // Crate an internalized two-byte string. // TODO(solanes): Can we have only one raw string? const char* raw_string = STRING_VALUE; // TODO(solanes): Is this the best way to create a two byte string from chars? const int kLength = 12; const uint16_t two_byte_array[kLength] = ARRAY_VALUE; Handle thin_string; { Handle raw = factory->NewRawTwoByteString(kLength).ToHandleChecked(); DisallowGarbageCollection no_gc; CopyChars(raw->GetChars(no_gc), two_byte_array, kLength); thin_string = raw; } Handle internalized_string = factory->InternalizeString(thin_string); CHECK(internalized_string->IsTwoByteRepresentation()); CHECK(!internalized_string->IsExternalString()); CHECK(internalized_string->IsInternalizedString()); Handle persistent_string = ph->NewHandle(thin_string); std::vector chars; for (int i = 0; i < thin_string->length(); ++i) { chars.push_back(thin_string->Get(i)); } base::Semaphore sema_started(0); std::unique_ptr thread(new ConcurrentStringThread( isolate, persistent_string, std::move(ph), &sema_started, chars)); CHECK(thread->Start()); sema_started.Wait(); // Externalize it to a two-bytes external string. CHECK(internalized_string->MakeExternal( new TestTwoByteResource(AsciiToTwoByteString(raw_string)))); CHECK(internalized_string->IsExternalTwoByteString()); CHECK(internalized_string->IsInternalizedString()); // Check that the thin string is unmodified. CHECK(!thin_string->IsExternalString()); CHECK(!thin_string->IsInternalizedString()); CHECK(thin_string->IsThinString()); thread->Join(); } } // anonymous namespace } // namespace internal } // namespace v8