Commit 3a6f75ac authored by Santiago Aboy Solanes's avatar Santiago Aboy Solanes Committed by Commit Bot

[objects] Remove MakeExternal case for uncached internal strings

Concurrently accessing internal external uncached strings is not
thread-safe. We are removing a case where we can make such a string
through MakeExternal.

Bug: v8:7790
Change-Id: I958062c15cf40ccc330600bb572de98620866e54
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2565511
Commit-Queue: Santiago Aboy Solanes <solanes@chromium.org>
Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71573}
parent 0396b732
...@@ -182,7 +182,9 @@ bool String::MakeExternal(v8::String::ExternalStringResource* resource) { ...@@ -182,7 +182,9 @@ bool String::MakeExternal(v8::String::ExternalStringResource* resource) {
ReadOnlyRoots roots(isolate); ReadOnlyRoots roots(isolate);
if (size < ExternalString::kSizeOfAllExternalStrings) { if (size < ExternalString::kSizeOfAllExternalStrings) {
if (is_internalized) { if (is_internalized) {
new_map = roots.uncached_external_internalized_string_map(); // We do not support this case since accessing internal external
// uncached strings is not thread-safe.
return false;
} else { } else {
new_map = roots.uncached_external_string_map(); new_map = roots.uncached_external_string_map();
} }
...@@ -259,9 +261,13 @@ bool String::MakeExternal(v8::String::ExternalOneByteStringResource* resource) { ...@@ -259,9 +261,13 @@ bool String::MakeExternal(v8::String::ExternalOneByteStringResource* resource) {
Map new_map; Map new_map;
ReadOnlyRoots roots(isolate); ReadOnlyRoots roots(isolate);
if (size < ExternalString::kSizeOfAllExternalStrings) { if (size < ExternalString::kSizeOfAllExternalStrings) {
new_map = is_internalized if (is_internalized) {
? roots.uncached_external_one_byte_internalized_string_map() // We do not support this case since accessing internal external
: roots.uncached_external_one_byte_string_map(); // uncached strings is not thread-safe.
return false;
} else {
new_map = roots.uncached_external_one_byte_string_map();
}
} else { } else {
new_map = is_internalized new_map = is_internalized
? roots.external_one_byte_internalized_string_map() ? roots.external_one_byte_internalized_string_map()
...@@ -309,6 +315,11 @@ bool String::SupportsExternalization() { ...@@ -309,6 +315,11 @@ bool String::SupportsExternalization() {
DCHECK_LE(ExternalString::kUncachedSize, this->Size()); DCHECK_LE(ExternalString::kUncachedSize, this->Size());
#endif #endif
if (this->Size() < ExternalString::kSizeOfAllExternalStrings &&
this->IsInternalizedString()) {
return false;
}
Isolate* isolate = GetIsolateFromWritableObject(*this); Isolate* isolate = GetIsolateFromWritableObject(*this);
return !isolate->heap()->IsInGCPostProcessing(); return !isolate->heap()->IsInGCPostProcessing();
} }
......
...@@ -360,7 +360,7 @@ class String : public TorqueGeneratedString<String, Name> { ...@@ -360,7 +360,7 @@ class String : public TorqueGeneratedString<String, Name> {
v8::String::ExternalStringResource* resource); v8::String::ExternalStringResource* resource);
V8_EXPORT_PRIVATE bool MakeExternal( V8_EXPORT_PRIVATE bool MakeExternal(
v8::String::ExternalOneByteStringResource* resource); v8::String::ExternalOneByteStringResource* resource);
bool SupportsExternalization(); V8_EXPORT_PRIVATE bool SupportsExternalization();
// Conversion. // Conversion.
// "array index": an index allowed by the ES spec for JSArrays. // "array index": an index allowed by the ES spec for JSArrays.
......
...@@ -5914,7 +5914,7 @@ TEST(Regress631969) { ...@@ -5914,7 +5914,7 @@ TEST(Regress631969) {
{ {
StaticOneByteResource external_string("12345678901234"); StaticOneByteResource external_string("12345678901234");
s3->MakeExternal(&external_string); CHECK(s3->MakeExternal(&external_string));
CcTest::CollectGarbage(OLD_SPACE); CcTest::CollectGarbage(OLD_SPACE);
// This avoids the GC from trying to free stack allocated resources. // This avoids the GC from trying to free stack allocated resources.
i::Handle<i::ExternalOneByteString>::cast(s3)->SetResource(isolate, i::Handle<i::ExternalOneByteString>::cast(s3)->SetResource(isolate,
......
...@@ -16873,7 +16873,7 @@ TEST(VisitExternalStrings) { ...@@ -16873,7 +16873,7 @@ TEST(VisitExternalStrings) {
v8::Isolate* isolate = CcTest::isolate(); v8::Isolate* isolate = CcTest::isolate();
LocalContext env; LocalContext env;
v8::HandleScope scope(isolate); v8::HandleScope scope(isolate);
const char string[] = "Some string"; const char string[] = "Some string that's long";
uint16_t* two_byte_string = AsciiToTwoByteString(string); uint16_t* two_byte_string = AsciiToTwoByteString(string);
TestResource* resource[4]; TestResource* resource[4];
resource[0] = new TestResource(two_byte_string); resource[0] = new TestResource(two_byte_string);
...@@ -16953,7 +16953,7 @@ TEST(ExternalInternalizedStringCollectedAtTearDown) { ...@@ -16953,7 +16953,7 @@ TEST(ExternalInternalizedStringCollectedAtTearDown) {
v8::Local<v8::String> ring = v8::Local<v8::String> ring =
CompileRun("ring")->ToString(env.local()).ToLocalChecked(); CompileRun("ring")->ToString(env.local()).ToLocalChecked();
CHECK(v8::Utils::OpenHandle(*ring)->IsInternalizedString()); CHECK(v8::Utils::OpenHandle(*ring)->IsInternalizedString());
ring->MakeExternal(inscription); CHECK(ring->MakeExternal(inscription));
// Ring is still alive. Orcs are roaming freely across our lands. // Ring is still alive. Orcs are roaming freely across our lands.
CHECK_EQ(0, destroyed); CHECK_EQ(0, destroyed);
USE(ring); USE(ring);
...@@ -16975,7 +16975,7 @@ TEST(ExternalInternalizedStringCollectedAtGC) { ...@@ -16975,7 +16975,7 @@ TEST(ExternalInternalizedStringCollectedAtGC) {
new TestOneByteResource(i::StrDup(s), &destroyed); new TestOneByteResource(i::StrDup(s), &destroyed);
v8::Local<v8::String> ring = CompileRun("ring").As<v8::String>(); v8::Local<v8::String> ring = CompileRun("ring").As<v8::String>();
CHECK(v8::Utils::OpenHandle(*ring)->IsInternalizedString()); CHECK(v8::Utils::OpenHandle(*ring)->IsInternalizedString());
ring->MakeExternal(inscription); CHECK(ring->MakeExternal(inscription));
// Ring is still alive. Orcs are roaming freely across our lands. // Ring is still alive. Orcs are roaming freely across our lands.
CHECK_EQ(0, destroyed); CHECK_EQ(0, destroyed);
USE(ring); USE(ring);
...@@ -18084,7 +18084,7 @@ THREADED_TEST(TwoByteStringInOneByteCons) { ...@@ -18084,7 +18084,7 @@ THREADED_TEST(TwoByteStringInOneByteCons) {
TestResource resource(uc16_buffer); TestResource resource(uc16_buffer);
flat_string->MakeExternal(&resource); CHECK(flat_string->MakeExternal(&resource));
CHECK(flat_string->IsTwoByteRepresentation()); CHECK(flat_string->IsTwoByteRepresentation());
...@@ -21435,7 +21435,7 @@ class RegExpInterruptTest { ...@@ -21435,7 +21435,7 @@ class RegExpInterruptTest {
v8::Local<v8::String> string = v8::Local<v8::String> string =
v8::Local<v8::String>::New(isolate, instance->string_handle_); v8::Local<v8::String>::New(isolate, instance->string_handle_);
CHECK(string->CanMakeExternal()); CHECK(string->CanMakeExternal());
string->MakeExternal(&one_byte_string_resource); CHECK(string->MakeExternal(&one_byte_string_resource));
} }
static void MakeSubjectTwoByteExternal(v8::Isolate* isolate, void* data) { static void MakeSubjectTwoByteExternal(v8::Isolate* isolate, void* data) {
...@@ -21445,7 +21445,7 @@ class RegExpInterruptTest { ...@@ -21445,7 +21445,7 @@ class RegExpInterruptTest {
v8::Local<v8::String> string = v8::Local<v8::String> string =
v8::Local<v8::String>::New(isolate, instance->string_handle_); v8::Local<v8::String>::New(isolate, instance->string_handle_);
CHECK(string->CanMakeExternal()); CHECK(string->CanMakeExternal());
string->MakeExternal(&two_byte_string_resource); CHECK(string->MakeExternal(&two_byte_string_resource));
} }
static void ReenterIrregexp(v8::Isolate* isolate, void* data) { static void ReenterIrregexp(v8::Isolate* isolate, void* data) {
...@@ -19,10 +19,10 @@ namespace internal { ...@@ -19,10 +19,10 @@ namespace internal {
namespace { namespace {
#define DOUBLE_VALUE 28.123456789 #define DOUBLE_VALUE 12345.123456789
#define STRING_VALUE "28.123456789" #define STRING_VALUE "12345.123456789"
#define ARRAY_VALUE \ #define ARRAY_VALUE \
{ '2', '8', '.', '1', '2', '3', '4', '5', '6', '7', '8', '9' } { '1', '2', '3', '4', '5', '.', '1', '2', '3', '4', '5', '6', '7', '8', '9' }
// Adapted from cctest/test-api.cc, and // Adapted from cctest/test-api.cc, and
// test/cctest/heap/test-external-string-tracker.cc. // test/cctest/heap/test-external-string-tracker.cc.
...@@ -202,7 +202,8 @@ TEST(InspectTwoByteExternalizing) { ...@@ -202,7 +202,8 @@ TEST(InspectTwoByteExternalizing) {
// TODO(solanes): Can we have only one raw string? // TODO(solanes): Can we have only one raw string?
const char* raw_string = STRING_VALUE; const char* raw_string = STRING_VALUE;
// TODO(solanes): Is this the best way to create a two byte string from chars? // TODO(solanes): Is this the best way to create a two byte string from chars?
const int kLength = 12; const int kLength = 15;
DCHECK_EQ(kLength, strlen(raw_string));
const uint16_t two_byte_array[kLength] = ARRAY_VALUE; const uint16_t two_byte_array[kLength] = ARRAY_VALUE;
Handle<String> two_bytes_string; Handle<String> two_bytes_string;
{ {
......
...@@ -2114,28 +2114,28 @@ TEST(CodeSerializerExternalString) { ...@@ -2114,28 +2114,28 @@ TEST(CodeSerializerExternalString) {
v8::HandleScope scope(CcTest::isolate()); v8::HandleScope scope(CcTest::isolate());
// Obtain external internalized one-byte string. // Obtain external internalized one-byte string.
SerializerOneByteResource one_byte_resource("one_byte", 8); SerializerOneByteResource one_byte_resource("one_byte_but_long", 17);
Handle<String> one_byte_string = Handle<String> one_byte_string =
isolate->factory()->NewStringFromAsciiChecked("one_byte"); isolate->factory()->NewStringFromAsciiChecked("one_byte_but_long");
one_byte_string = isolate->factory()->InternalizeString(one_byte_string); one_byte_string = isolate->factory()->InternalizeString(one_byte_string);
one_byte_string->MakeExternal(&one_byte_resource); one_byte_string->MakeExternal(&one_byte_resource);
CHECK(one_byte_string->IsExternalOneByteString()); CHECK(one_byte_string->IsExternalOneByteString());
CHECK(one_byte_string->IsInternalizedString()); CHECK(one_byte_string->IsInternalizedString());
// Obtain external internalized two-byte string. // Obtain external internalized two-byte string.
SerializerTwoByteResource two_byte_resource("two_byte", 8); SerializerTwoByteResource two_byte_resource("two_byte_but_long", 17);
Handle<String> two_byte_string = Handle<String> two_byte_string =
isolate->factory()->NewStringFromAsciiChecked("two_byte"); isolate->factory()->NewStringFromAsciiChecked("two_byte_but_long");
two_byte_string = isolate->factory()->InternalizeString(two_byte_string); two_byte_string = isolate->factory()->InternalizeString(two_byte_string);
two_byte_string->MakeExternal(&two_byte_resource); two_byte_string->MakeExternal(&two_byte_resource);
CHECK(two_byte_string->IsExternalTwoByteString()); CHECK(two_byte_string->IsExternalTwoByteString());
CHECK(two_byte_string->IsInternalizedString()); CHECK(two_byte_string->IsInternalizedString());
const char* source = const char* source =
"var o = {} \n" "var o = {} \n"
"o.one_byte = 7; \n" "o.one_byte_but_long = 7; \n"
"o.two_byte = 8; \n" "o.two_byte_but_long = 8; \n"
"o.one_byte + o.two_byte; \n"; "o.one_byte_but_long + o.two_byte_but_long; \n";
Handle<String> source_string = isolate->factory() Handle<String> source_string = isolate->factory()
->NewStringFromUtf8(CStrVector(source)) ->NewStringFromUtf8(CStrVector(source))
.ToHandleChecked(); .ToHandleChecked();
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#include "src/heap/heap-inl.h" #include "src/heap/heap-inl.h"
#include "src/init/v8.h" #include "src/init/v8.h"
#include "src/objects/objects-inl.h" #include "src/objects/objects-inl.h"
#include "src/objects/string.h"
#include "src/strings/unicode-decoder.h" #include "src/strings/unicode-decoder.h"
#include "test/cctest/cctest.h" #include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h" #include "test/cctest/heap/heap-utils.h"
...@@ -1901,20 +1902,6 @@ TEST(StringEquals) { ...@@ -1901,20 +1902,6 @@ TEST(StringEquals) {
CHECK(!bar_str->StringEquals(foo_str2)); CHECK(!bar_str->StringEquals(foo_str2));
} }
class OneByteStringResource : public v8::String::ExternalOneByteStringResource {
public:
// Takes ownership of |data|.
OneByteStringResource(char* data, size_t length)
: data_(data), length_(length) {}
~OneByteStringResource() override { delete[] data_; }
const char* data() const override { return data_; }
size_t length() const override { return length_; }
private:
char* data_;
size_t length_;
};
TEST(Regress876759) { TEST(Regress876759) {
// Thin strings are used in conjunction with young gen // Thin strings are used in conjunction with young gen
if (FLAG_single_generation) return; if (FLAG_single_generation) return;
...@@ -1949,9 +1936,9 @@ TEST(Regress876759) { ...@@ -1949,9 +1936,9 @@ TEST(Regress876759) {
Handle<String> grandparent = Handle<String> grandparent =
handle(ThinString::cast(*parent).actual(), isolate); handle(ThinString::cast(*parent).actual(), isolate);
CHECK_EQ(*parent, SlicedString::cast(*sliced).parent()); CHECK_EQ(*parent, SlicedString::cast(*sliced).parent());
OneByteStringResource* resource = OneByteResource* resource =
new OneByteStringResource(external_one_byte_buf, kLength); new OneByteResource(external_one_byte_buf, kLength);
grandparent->MakeExternal(resource); CHECK(grandparent->MakeExternal(resource));
// The grandparent string becomes one-byte, but the child strings are still // The grandparent string becomes one-byte, but the child strings are still
// two-byte. // two-byte.
CHECK(grandparent->IsOneByteRepresentation()); CHECK(grandparent->IsOneByteRepresentation());
...@@ -1961,6 +1948,69 @@ TEST(Regress876759) { ...@@ -1961,6 +1948,69 @@ TEST(Regress876759) {
CHECK(String::IsOneByteRepresentationUnderneath(*sliced)); CHECK(String::IsOneByteRepresentationUnderneath(*sliced));
} }
// Show failure when trying to create and internal, external and uncached string
// through MakeExternal. One byte version.
TEST(MakeExternalCreationFailureOneByte) {
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
// Due to different size restrictions the string needs to be small but not too
// small. One of these restrictions is whether pointer compression is enabled.
#ifdef V8_COMPRESS_POINTERS
const char* raw_small = "small string";
#elif V8_TARGET_ARCH_32_BIT
const char* raw_small = "smol";
#else
const char* raw_small = "smalls";
#endif // V8_COMPRESS_POINTERS
HandleScope handle_scope(isolate);
Handle<String> one_byte_string =
factory->InternalizeString(factory->NewStringFromAsciiChecked(raw_small));
CHECK(one_byte_string->IsOneByteRepresentation());
CHECK(!one_byte_string->IsExternalString());
CHECK(!one_byte_string->SupportsExternalization());
}
// Show failure when trying to create and internal, external and uncached string
// through MakeExternal. Two byte version.
TEST(MakeExternalCreationFailureTwoByte) {
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
// Due to different size restrictions the string needs to be small but not too
// small.
const char* raw_small = "smalls";
const int kLength = 6;
DCHECK_EQ(kLength, strlen(raw_small));
const uint16_t two_byte_array[kLength] = {'s', 'm', 'a', 'l', 'l', 's'};
HandleScope handle_scope(isolate);
Handle<String> two_bytes_string;
{
Handle<SeqTwoByteString> 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());
if (COMPRESS_POINTERS_BOOL) {
CHECK(!two_bytes_string->SupportsExternalization());
} else {
// Without pointer compression, there is no string size that can cause a
// failure for a two byte string. It needs to be bigger than 5 chars to
// support externalization, but at that point is bigger than the limit and
// it is not uncached anymore.
// As a note, since pointer compression is only enabled for 64 bits, all
// target 32 bit archs fall in this case.
CHECK(two_bytes_string->MakeExternal(
new Resource(AsciiToTwoByteString(raw_small), strlen(raw_small))));
auto external = Handle<ExternalString>::cast(two_bytes_string);
CHECK(!external->is_uncached());
}
}
} // namespace test_strings } // namespace test_strings
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -180,11 +180,11 @@ Object.defineProperty(non_enum, "b", { value: 2, enumerable: false }); ...@@ -180,11 +180,11 @@ Object.defineProperty(non_enum, "b", { value: 2, enumerable: false });
non_enum.c = 3; non_enum.c = 3;
TestStringify('{"a":1,"c":3}', non_enum); TestStringify('{"a":1,"c":3}', non_enum);
var str = "external"; var str = "external_string";
try { try {
externalizeString(str, true); externalizeString(str, true);
} catch (e) { } } catch (e) { }
TestStringify("\"external\"", str, null, 0); TestStringify("\"external_string\"", str, null, 0);
var o = {}; var o = {};
o.somespecialproperty = 10; o.somespecialproperty = 10;
......
...@@ -53,7 +53,7 @@ function test() { ...@@ -53,7 +53,7 @@ function test() {
assertEquals('B', charat_str[i].charAt(3*16 + 11)); assertEquals('B', charat_str[i].charAt(3*16 + 11));
} }
charat_short = "01234"; charat_short = "0123456789ABC";
try { // String can only be externalized once try { // String can only be externalized once
externalizeString(charat_short, true); externalizeString(charat_short, true);
} catch (ex) { } } catch (ex) { }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment