blob: 1254eb5095a8c87cc188b11c382aa6cf427ed65f [file] [log] [blame]
// Copyright 2010 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/extensions/externalize-string-extension.h"
#include "include/v8-template.h"
#include "src/api/api-inl.h"
#include "src/base/strings.h"
#include "src/execution/isolate.h"
#include "src/handles/handles.h"
#include "src/objects/heap-object-inl.h"
#include "src/objects/objects-inl.h"
namespace v8 {
namespace internal {
template <typename Char, typename Base>
class SimpleStringResource : public Base {
public:
// Takes ownership of |data|.
SimpleStringResource(Char* data, size_t length)
: data_(data),
length_(length) {}
~SimpleStringResource() override { delete[] data_; }
const Char* data() const override { return data_; }
size_t length() const override { return length_; }
private:
Char* const data_;
const size_t length_;
};
using SimpleOneByteStringResource =
SimpleStringResource<char, v8::String::ExternalOneByteStringResource>;
using SimpleTwoByteStringResource =
SimpleStringResource<base::uc16, v8::String::ExternalStringResource>;
static constexpr int kMinOneByteLength =
kExternalPointerSlotSize - kTaggedSize + 1;
static constexpr int kMinTwoByteLength =
(kExternalPointerSlotSize - kTaggedSize) / sizeof(base::uc16) + 1;
static constexpr int kMinOneByteCachedLength =
2 * kExternalPointerSlotSize - kTaggedSize + 1;
static constexpr int kMinTwoByteCachedLength =
(2 * kExternalPointerSlotSize - kTaggedSize) / sizeof(base::uc16) + 1;
// static
const char* ExternalizeStringExtension::BuildSource(char* buf, size_t size) {
base::SNPrintF(base::Vector<char>(buf, static_cast<int>(size)),
"native function externalizeString();"
"native function createExternalizableString();"
"native function isOneByteString();"
"let kExternalStringMinOneByteLength = %d;"
"let kExternalStringMinTwoByteLength = %d;"
"let kExternalStringMinOneByteCachedLength = %d;"
"let kExternalStringMinTwoByteCachedLength = %d;",
kMinOneByteLength, kMinTwoByteLength, kMinOneByteCachedLength,
kMinTwoByteCachedLength);
return buf;
}
v8::Local<v8::FunctionTemplate>
ExternalizeStringExtension::GetNativeFunctionTemplate(
v8::Isolate* isolate, v8::Local<v8::String> str) {
if (strcmp(*v8::String::Utf8Value(isolate, str), "externalizeString") == 0) {
return v8::FunctionTemplate::New(isolate,
ExternalizeStringExtension::Externalize);
} else if (strcmp(*v8::String::Utf8Value(isolate, str),
"createExternalizableString") == 0) {
return v8::FunctionTemplate::New(
isolate, ExternalizeStringExtension::CreateExternalizableString);
} else {
DCHECK_EQ(strcmp(*v8::String::Utf8Value(isolate, str), "isOneByteString"),
0);
return v8::FunctionTemplate::New(isolate,
ExternalizeStringExtension::IsOneByte);
}
}
namespace {
bool HasExternalForwardingIndex(Handle<String> string) {
if (!string->IsShared()) return false;
uint32_t raw_hash = string->raw_hash_field(kAcquireLoad);
return Name::IsExternalForwardingIndex(raw_hash);
}
} // namespace
void ExternalizeStringExtension::Externalize(
const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
if (info.Length() < 1 || !info[0]->IsString()) {
info.GetIsolate()->ThrowError(
"First parameter to externalizeString() must be a string.");
return;
}
bool result = false;
Handle<String> string = Utils::OpenHandle(*info[0].As<v8::String>());
const bool externalize_as_one_byte = string->IsOneByteRepresentation();
if (!string->SupportsExternalization(
externalize_as_one_byte ? v8::String::Encoding::ONE_BYTE_ENCODING
: v8::String::Encoding::TWO_BYTE_ENCODING)) {
// If the string is shared, testing with the combination of
// --shared-string-table and --isolate in d8 may result in races to
// externalize the same string. If GC is stressed in addition, this test
// might fail as the string was already externalized (marked for
// externalization by another thread and externalized during GC).
if (!StringShape(*string).IsExternal()) {
info.GetIsolate()->ThrowError("string does not support externalization.");
}
return;
}
if (externalize_as_one_byte) {
uint8_t* data = new uint8_t[string->length()];
String::WriteToFlat(*string, data, 0, string->length());
SimpleOneByteStringResource* resource = new SimpleOneByteStringResource(
reinterpret_cast<char*>(data), string->length());
result = Utils::ToLocal(string)->MakeExternal(resource);
if (!result) delete resource;
} else {
base::uc16* data = new base::uc16[string->length()];
String::WriteToFlat(*string, data, 0, string->length());
SimpleTwoByteStringResource* resource = new SimpleTwoByteStringResource(
data, string->length());
result = Utils::ToLocal(string)->MakeExternal(resource);
if (!result) delete resource;
}
// If the string is shared, testing with the combination of
// --shared-string-table and --isolate in d8 may result in races to
// externalize the same string. Those races manifest as externalization
// sometimes failing if another thread won and already forwarded the string to
// the external resource. Don't consider those races as failures.
if (!result && !HasExternalForwardingIndex(string)) {
info.GetIsolate()->ThrowError("externalizeString() failed.");
return;
}
}
namespace {
MaybeHandle<String> CopyConsStringToOld(Isolate* isolate,
Handle<ConsString> string) {
return isolate->factory()->NewConsString(handle(string->first(), isolate),
handle(string->second(), isolate),
AllocationType::kOld);
}
} // namespace
void ExternalizeStringExtension::CreateExternalizableString(
const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
if (info.Length() < 1 || !info[0]->IsString()) {
info.GetIsolate()->ThrowError(
"First parameter to createExternalizableString() must be a string.");
return;
}
Handle<String> string = Utils::OpenHandle(*info[0].As<v8::String>());
Isolate* isolate = reinterpret_cast<Isolate*>(info.GetIsolate());
v8::String::Encoding encoding = string->IsOneByteRepresentation()
? v8::String::Encoding::ONE_BYTE_ENCODING
: v8::String::Encoding::TWO_BYTE_ENCODING;
if (string->SupportsExternalization(encoding)) {
info.GetReturnValue().Set(Utils::ToLocal(string));
return;
}
// Return the string if it is already externalized.
if (StringShape(*string).IsExternal()) {
info.GetReturnValue().Set(Utils::ToLocal(string));
return;
}
// Read-only strings are never externalizable. Don't try to copy them as
// some parts of the code might rely on some strings being in RO space (i.e.
// empty string).
if (IsReadOnlyHeapObject(*string)) {
info.GetIsolate()->ThrowError("Read-only strings cannot be externalized.");
return;
}
#ifdef V8_COMPRESS_POINTERS
// Small strings may not be in-place externalizable.
if (string->Size() < static_cast<int>(sizeof(UncachedExternalString))) {
info.GetIsolate()->ThrowError("String is too short to be externalized.");
return;
}
#endif
// Special handling for ConsStrings, as the ConsString -> ExternalString
// migration is special for GC (Tagged pointers to Untagged pointers).
// Skip if the ConsString is flat (second is empty), as we won't be guaranteed
// a string in old space in that case.
if (IsConsString(*string, isolate) && !string->IsFlat()) {
Handle<String> result;
if (CopyConsStringToOld(isolate, Handle<ConsString>::cast(string))
.ToHandle(&result)) {
DCHECK(result->SupportsExternalization(encoding));
info.GetReturnValue().Set(Utils::ToLocal(result));
return;
}
}
// All other strings can be implicitly flattened.
if (encoding == v8::String::ONE_BYTE_ENCODING) {
MaybeHandle<SeqOneByteString> maybe_result =
isolate->factory()->NewRawOneByteString(string->length(),
AllocationType::kOld);
Handle<SeqOneByteString> result;
if (maybe_result.ToHandle(&result)) {
DisallowGarbageCollection no_gc;
String::WriteToFlat(*string, result->GetChars(no_gc), 0,
string->length());
DCHECK(result->SupportsExternalization(encoding));
info.GetReturnValue().Set(Utils::ToLocal(Handle<String>::cast(result)));
return;
}
} else {
MaybeHandle<SeqTwoByteString> maybe_result =
isolate->factory()->NewRawTwoByteString(string->length(),
AllocationType::kOld);
Handle<SeqTwoByteString> result;
if (maybe_result.ToHandle(&result)) {
DisallowGarbageCollection no_gc;
String::WriteToFlat(*string, result->GetChars(no_gc), 0,
string->length());
DCHECK(result->SupportsExternalization(encoding));
info.GetReturnValue().Set(Utils::ToLocal(Handle<String>::cast(result)));
return;
}
}
info.GetIsolate()->ThrowError("Unable to create string");
}
void ExternalizeStringExtension::IsOneByte(
const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
if (info.Length() != 1 || !info[0]->IsString()) {
info.GetIsolate()->ThrowError(
"isOneByteString() requires a single string argument.");
return;
}
bool is_one_byte = Utils::OpenDirectHandle(*info[0].As<v8::String>())
->IsOneByteRepresentation();
info.GetReturnValue().Set(is_one_byte);
}
} // namespace internal
} // namespace v8