// 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 12345.123456789 #define STRING_VALUE "12345.123456789" #define ARRAY_VALUE \ { '1', '2', '3', '4', '5', '.', '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_->synchronized_length(), 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) { heap::EnsureFlagLocalHeapsEnabled(); 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) { heap::EnsureFlagLocalHeapsEnabled(); 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) { heap::EnsureFlagLocalHeapsEnabled(); 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 = 15; DCHECK_EQ(kLength, strlen(raw_string)); 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(); } } // anonymous namespace } // namespace internal } // namespace v8