deps: cherry-pick V8 ValueSerializer changes
Refs: https://github.com/nodejs/node/pull/11048
Below is the list of commits:
deps: cherry-pick 78c0be52d from V8 upstream
Original commit message:
ValueSerializer: Promote scheduled exceptions from wasm::ErrorThrower.
wasm::ErrorThrower doesn't actually throw exceptions, it just schedules them.
As a result, this exception isn't handled properly by code which expects
ValueDeserializer to actually throw. For instance, the unit tests use a
TryCatch to catch and handle expected exceptions in unit tests.
Before this patch, I see local unit test failures because a wasm decode test
schedules one, but it isn't caught (and instead causes Context::New to fail
at the beginning of the next test).
BUG=685713
Review-Url: https://codereview.chromium.org/2659483004
Cr-Commit-Position: refs/heads/master@{#42718}
deps: cherry-pick 966355585 from V8 upstream
Original commit message:
[d8] Use ValueSerializer for postMessage (instead of ad-hoc serializer)
Review-Url: https://codereview.chromium.org/2643723010
Cr-Commit-Position: refs/heads/master@{#42749}
deps: cherry-pick bf511b426 from V8 upstream
Original commit message:
ValueSerializer: Support efficiently reading and writing one-byte strings.
memcpy is faster than UTF-8 encoding/decoding. This yields 10-20% wins on
serializing and deserializing long ASCII strings, according to
blink_perf.bindings -- and these are already in a fast path where the entire
string is known to be ASCII (but this has to be checked). The win may be
larger for strings in Latin-1 but not ASCII (though I suspect this is an
uncommon case).
A change is also made to make ValueSerializerTest.EncodeTwoByteStringUsesPadding
survive wire format version number changes.
This is the first of a series of wire format changes from the previous Blink
format. The deserializer continues to be able to read the old format, but
Chromium M56 will no longer be able to read the messages written by this, in M58.
BUG=chromium:686159
Review-Url: https://codereview.chromium.org/2658793004
Cr-Commit-Position: refs/heads/master@{#42753}
deps: cherry-pick 6f1639ed1 from V8 upstream
Original commit message:
ValueSerializer: Distinguish between 'undefined' and an absent property.
Dealing with this case requires a wire format change. It is possible that an
element can be absent even in an array where the dense format was chosen
(because the array initially had no holes), if the elements are modified while
they are being serialized. In this case, a new tag for the "hole" is emitted.
The logic to treat undefined in dense arrays as an absent property is restricted
to versions of the wire format that this tag did not exist.
BUG=chromium:686159,chromium:665820
Review-Url: https://codereview.chromium.org/2660093002
Cr-Original-Commit-Position: refs/heads/master@{#42784}
Committed: dc85f4c833
Review-Url: https://codereview.chromium.org/2660093002
Cr-Commit-Position: refs/heads/master@{#42800}
deps: cherry-pick c3856de37 from V8 upstream
Original commit message:
ValueSerializer: Check for zero length before casting to FixedDoubleArray.
Even though the elements kind is FAST_DOUBLE_ELEMENTS, if length is zero
the isolate's empty_fixed_array is used. It's illegal to cast this to
FixedDoubleArray, so we avoid the cast.
BUG=chromium:686479
Review-Url: https://codereview.chromium.org/2665313003
Cr-Commit-Position: refs/heads/master@{#42867}
deps: cherry-pick 591cc0b4c from V8 upstream
Original commit message:
ValueSerializer: Share string encoding code with String and RegExp objects.
This avoids the need to pull in the UTF-8 encoding code from the public API,
and allows it to take advantage of any supported way that i::String can be
encoded (one- or two-byte).
Backward compatibility is maintained, but this is the behavior beginning
with this version.
BUG=chromium:686159
Review-Url: https://codereview.chromium.org/2665653004
Cr-Commit-Position: refs/heads/master@{#42872}
deps: cherry-pick 79837f5f6 from V8 upstream
Original commit message:
Improve ValueSerializer perf regression after 96635558
BUG=687196
R=jbroman@chromium.org
Review-Url: https://codereview.chromium.org/2674613002
Cr-Commit-Position: refs/heads/master@{#42938}
deps: cherry-pick 8990399dc from V8 upstream
Original commit message:
ValueDeserializer: Only allow valid keys when deserializing object properties.
The serializer won't ever write a more complex object. Not validating this
allows other things to be used as keys, and converted to string when the
property set actually occurs. It turns out this gives an opportunity to trigger
OOM by giving an object a key which is a very large sparse array (whose string
representation is very large).
This case is now rejected by the deserializer.
BUG=chromium:686511
Review-Url: https://codereview.chromium.org/2697023002
Cr-Commit-Position: refs/heads/master@{#43249}
deps: cherry-pick 68960eeb7 from V8 upstream
Original commit message:
ValueDeserializer: Make sure that an exception is the legacy path.
The entry points to the deserializer are responsible for ensuring that an
exception is pending by the time they return. Some failures throw exceptions
themselves, while others (like errors in the format) are exceptions caused by
the deserializer, not coming from the runtime.
Like the non-legacy path, a default deserialization exception should be thrown
in such cases.
BUG=chromium:693411
Review-Url: https://codereview.chromium.org/2712713002
Cr-Commit-Position: refs/heads/master@{#43390}
deps: cherry-pick 3b15d950e from V8 upstream
Original commit message:
ValueSerializer: Add SetTreatArrayBufferViewsAsHostObjects() flag
Add `ValueSerializer::SetTreatArrayBufferViewsAsHostObjects()` which
instructs the `ValueSerializer` to treat ArrayBufferView objects as
host objects.
BUG=v8:5926
Review-Url: https://codereview.chromium.org/2696133007
Cr-Commit-Position: refs/heads/master@{#43281}
deps: cherry-pick 654351997 from V8 upstream
Original commit message:
ValueSerializer: Add an explicit tag for host objects.
This makes it no longer necessary to ensure that V8 and Blink have non-colliding
tags, which makes it easier for them to evolve independently, and also makes
the wire format more suitable for other V8 embedders, who would not
necessarily be surveyed before V8 introduced a new tag that might collide
with theirs.
BUG=chromium:686159
Review-Url: https://codereview.chromium.org/2709023003
Cr-Commit-Position: refs/heads/master@{#43466}
PR-URL: https://github.com/nodejs/node/pull/11752
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
This commit is contained in:
parent
3f9c8fb87f
commit
83bf2975ec
13
deps/v8/include/v8.h
vendored
13
deps/v8/include/v8.h
vendored
@ -1726,6 +1726,10 @@ class V8_EXPORT ValueSerializer {
|
||||
* Allocates memory for the buffer of at least the size provided. The actual
|
||||
* size (which may be greater or equal) is written to |actual_size|. If no
|
||||
* buffer has been allocated yet, nullptr will be provided.
|
||||
*
|
||||
* If the memory cannot be allocated, nullptr should be returned.
|
||||
* |actual_size| will be ignored. It is assumed that |old_buffer| is still
|
||||
* valid in this case and has not been modified.
|
||||
*/
|
||||
virtual void* ReallocateBufferMemory(void* old_buffer, size_t size,
|
||||
size_t* actual_size);
|
||||
@ -1781,6 +1785,15 @@ class V8_EXPORT ValueSerializer {
|
||||
uint32_t transfer_id,
|
||||
Local<SharedArrayBuffer> shared_array_buffer));
|
||||
|
||||
/*
|
||||
* Indicate whether to treat ArrayBufferView objects as host objects,
|
||||
* i.e. pass them to Delegate::WriteHostObject. This should not be
|
||||
* called when no Delegate was passed.
|
||||
*
|
||||
* The default is not to treat ArrayBufferViews as host objects.
|
||||
*/
|
||||
void SetTreatArrayBufferViewsAsHostObjects(bool mode);
|
||||
|
||||
/*
|
||||
* Write raw data in various common formats to the buffer.
|
||||
* Note that integer types are written in base-128 varint format, not with a
|
||||
|
4
deps/v8/src/api.cc
vendored
4
deps/v8/src/api.cc
vendored
@ -3130,6 +3130,10 @@ ValueSerializer::~ValueSerializer() { delete private_; }
|
||||
|
||||
void ValueSerializer::WriteHeader() { private_->serializer.WriteHeader(); }
|
||||
|
||||
void ValueSerializer::SetTreatArrayBufferViewsAsHostObjects(bool mode) {
|
||||
private_->serializer.SetTreatArrayBufferViewsAsHostObjects(mode);
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteValue(Local<Context> context,
|
||||
Local<Value> value) {
|
||||
PREPARE_FOR_EXECUTION_PRIMITIVE(context, ValueSerializer, WriteValue, bool);
|
||||
|
612
deps/v8/src/d8.cc
vendored
612
deps/v8/src/d8.cc
vendored
@ -31,6 +31,7 @@
|
||||
#include "src/base/sys-info.h"
|
||||
#include "src/basic-block-profiler.h"
|
||||
#include "src/interpreter/interpreter.h"
|
||||
#include "src/list-inl.h"
|
||||
#include "src/msan.h"
|
||||
#include "src/objects-inl.h"
|
||||
#include "src/snapshot/natives.h"
|
||||
@ -224,16 +225,6 @@ static Local<Value> Throw(Isolate* isolate, const char* message) {
|
||||
}
|
||||
|
||||
|
||||
bool FindInObjectList(Local<Object> object, const Shell::ObjectList& list) {
|
||||
for (int i = 0; i < list.length(); ++i) {
|
||||
if (list[i]->StrictEquals(object)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Worker* GetWorkerFromInternalField(Isolate* isolate, Local<Object> object) {
|
||||
if (object->InternalFieldCount() != 1) {
|
||||
Throw(isolate, "this is not a Worker");
|
||||
@ -416,7 +407,10 @@ Global<Function> Shell::stringify_function_;
|
||||
base::LazyMutex Shell::workers_mutex_;
|
||||
bool Shell::allow_new_workers_ = true;
|
||||
i::List<Worker*> Shell::workers_;
|
||||
i::List<SharedArrayBuffer::Contents> Shell::externalized_shared_contents_;
|
||||
std::unordered_set<SharedArrayBuffer::Contents,
|
||||
Shell::SharedArrayBufferContentsHash,
|
||||
Shell::SharedArrayBufferContentsIsEqual>
|
||||
Shell::externalized_shared_contents_;
|
||||
|
||||
Global<Context> Shell::evaluation_context_;
|
||||
ArrayBuffer::Allocator* Shell::array_buffer_allocator;
|
||||
@ -1179,7 +1173,6 @@ void Shell::WorkerNew(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
void Shell::WorkerPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
HandleScope handle_scope(isolate);
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
|
||||
if (args.Length() < 1) {
|
||||
Throw(isolate, "Invalid argument");
|
||||
@ -1192,36 +1185,12 @@ void Shell::WorkerPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
}
|
||||
|
||||
Local<Value> message = args[0];
|
||||
ObjectList to_transfer;
|
||||
if (args.Length() >= 2) {
|
||||
if (!args[1]->IsArray()) {
|
||||
Throw(isolate, "Transfer list must be an Array");
|
||||
return;
|
||||
}
|
||||
|
||||
Local<Array> transfer = Local<Array>::Cast(args[1]);
|
||||
uint32_t length = transfer->Length();
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
Local<Value> element;
|
||||
if (transfer->Get(context, i).ToLocal(&element)) {
|
||||
if (!element->IsArrayBuffer() && !element->IsSharedArrayBuffer()) {
|
||||
Throw(isolate,
|
||||
"Transfer array elements must be an ArrayBuffer or "
|
||||
"SharedArrayBuffer.");
|
||||
break;
|
||||
}
|
||||
|
||||
to_transfer.Add(Local<Object>::Cast(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ObjectList seen_objects;
|
||||
SerializationData* data = new SerializationData;
|
||||
if (SerializeValue(isolate, message, to_transfer, &seen_objects, data)) {
|
||||
worker->PostMessage(data);
|
||||
} else {
|
||||
delete data;
|
||||
Local<Value> transfer =
|
||||
args.Length() >= 2 ? args[1] : Local<Value>::Cast(Undefined(isolate));
|
||||
std::unique_ptr<SerializationData> data =
|
||||
Shell::SerializeValue(isolate, message, transfer);
|
||||
if (data) {
|
||||
worker->PostMessage(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1234,14 +1203,12 @@ void Shell::WorkerGetMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
return;
|
||||
}
|
||||
|
||||
SerializationData* data = worker->GetMessage();
|
||||
std::unique_ptr<SerializationData> data = worker->GetMessage();
|
||||
if (data) {
|
||||
int offset = 0;
|
||||
Local<Value> data_value;
|
||||
if (Shell::DeserializeValue(isolate, *data, &offset).ToLocal(&data_value)) {
|
||||
args.GetReturnValue().Set(data_value);
|
||||
Local<Value> value;
|
||||
if (Shell::DeserializeValue(isolate, std::move(data)).ToLocal(&value)) {
|
||||
args.GetReturnValue().Set(value);
|
||||
}
|
||||
delete data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2181,14 +2148,12 @@ void SourceGroup::JoinThread() {
|
||||
thread_->Join();
|
||||
}
|
||||
|
||||
|
||||
SerializationData::~SerializationData() {
|
||||
// Any ArrayBuffer::Contents are owned by this SerializationData object if
|
||||
// ownership hasn't been transferred out via ReadArrayBufferContents.
|
||||
// ownership hasn't been transferred out.
|
||||
// SharedArrayBuffer::Contents may be used by multiple threads, so must be
|
||||
// cleaned up by the main thread in Shell::CleanupWorkers().
|
||||
for (int i = 0; i < array_buffer_contents_.length(); ++i) {
|
||||
ArrayBuffer::Contents& contents = array_buffer_contents_[i];
|
||||
for (const auto& contents : array_buffer_contents_) {
|
||||
if (contents.Data()) {
|
||||
Shell::array_buffer_allocator->Free(contents.Data(),
|
||||
contents.ByteLength());
|
||||
@ -2196,96 +2161,35 @@ SerializationData::~SerializationData() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SerializationData::WriteTag(SerializationTag tag) { data_.Add(tag); }
|
||||
|
||||
|
||||
void SerializationData::WriteMemory(const void* p, int length) {
|
||||
if (length > 0) {
|
||||
i::Vector<uint8_t> block = data_.AddBlock(0, length);
|
||||
memcpy(&block[0], p, length);
|
||||
}
|
||||
void SerializationData::ClearTransferredArrayBuffers() {
|
||||
array_buffer_contents_.clear();
|
||||
}
|
||||
|
||||
|
||||
void SerializationData::WriteArrayBufferContents(
|
||||
const ArrayBuffer::Contents& contents) {
|
||||
array_buffer_contents_.Add(contents);
|
||||
WriteTag(kSerializationTagTransferredArrayBuffer);
|
||||
int index = array_buffer_contents_.length() - 1;
|
||||
Write(index);
|
||||
}
|
||||
|
||||
|
||||
void SerializationData::WriteSharedArrayBufferContents(
|
||||
const SharedArrayBuffer::Contents& contents) {
|
||||
shared_array_buffer_contents_.Add(contents);
|
||||
WriteTag(kSerializationTagTransferredSharedArrayBuffer);
|
||||
int index = shared_array_buffer_contents_.length() - 1;
|
||||
Write(index);
|
||||
}
|
||||
|
||||
|
||||
SerializationTag SerializationData::ReadTag(int* offset) const {
|
||||
return static_cast<SerializationTag>(Read<uint8_t>(offset));
|
||||
}
|
||||
|
||||
|
||||
void SerializationData::ReadMemory(void* p, int length, int* offset) const {
|
||||
if (length > 0) {
|
||||
memcpy(p, &data_[*offset], length);
|
||||
(*offset) += length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SerializationData::ReadArrayBufferContents(ArrayBuffer::Contents* contents,
|
||||
int* offset) const {
|
||||
int index = Read<int>(offset);
|
||||
DCHECK(index < array_buffer_contents_.length());
|
||||
*contents = array_buffer_contents_[index];
|
||||
// Ownership of this ArrayBuffer::Contents is passed to the caller. Neuter
|
||||
// our copy so it won't be double-free'd when this SerializationData is
|
||||
// destroyed.
|
||||
array_buffer_contents_[index] = ArrayBuffer::Contents();
|
||||
}
|
||||
|
||||
|
||||
void SerializationData::ReadSharedArrayBufferContents(
|
||||
SharedArrayBuffer::Contents* contents, int* offset) const {
|
||||
int index = Read<int>(offset);
|
||||
DCHECK(index < shared_array_buffer_contents_.length());
|
||||
*contents = shared_array_buffer_contents_[index];
|
||||
}
|
||||
|
||||
|
||||
void SerializationDataQueue::Enqueue(SerializationData* data) {
|
||||
void SerializationDataQueue::Enqueue(std::unique_ptr<SerializationData> data) {
|
||||
base::LockGuard<base::Mutex> lock_guard(&mutex_);
|
||||
data_.Add(data);
|
||||
data_.push_back(std::move(data));
|
||||
}
|
||||
|
||||
|
||||
bool SerializationDataQueue::Dequeue(SerializationData** data) {
|
||||
bool SerializationDataQueue::Dequeue(
|
||||
std::unique_ptr<SerializationData>* out_data) {
|
||||
out_data->reset();
|
||||
base::LockGuard<base::Mutex> lock_guard(&mutex_);
|
||||
*data = NULL;
|
||||
if (data_.is_empty()) return false;
|
||||
*data = data_.Remove(0);
|
||||
if (data_.empty()) return false;
|
||||
*out_data = std::move(data_[0]);
|
||||
data_.erase(data_.begin());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool SerializationDataQueue::IsEmpty() {
|
||||
base::LockGuard<base::Mutex> lock_guard(&mutex_);
|
||||
return data_.is_empty();
|
||||
return data_.empty();
|
||||
}
|
||||
|
||||
|
||||
void SerializationDataQueue::Clear() {
|
||||
base::LockGuard<base::Mutex> lock_guard(&mutex_);
|
||||
for (int i = 0; i < data_.length(); ++i) {
|
||||
delete data_[i];
|
||||
}
|
||||
data_.Clear();
|
||||
data_.clear();
|
||||
}
|
||||
|
||||
|
||||
@ -2314,22 +2218,20 @@ void Worker::StartExecuteInThread(const char* script) {
|
||||
thread_->Start();
|
||||
}
|
||||
|
||||
|
||||
void Worker::PostMessage(SerializationData* data) {
|
||||
in_queue_.Enqueue(data);
|
||||
void Worker::PostMessage(std::unique_ptr<SerializationData> data) {
|
||||
in_queue_.Enqueue(std::move(data));
|
||||
in_semaphore_.Signal();
|
||||
}
|
||||
|
||||
|
||||
SerializationData* Worker::GetMessage() {
|
||||
SerializationData* data = NULL;
|
||||
while (!out_queue_.Dequeue(&data)) {
|
||||
std::unique_ptr<SerializationData> Worker::GetMessage() {
|
||||
std::unique_ptr<SerializationData> result;
|
||||
while (!out_queue_.Dequeue(&result)) {
|
||||
// If the worker is no longer running, and there are no messages in the
|
||||
// queue, don't expect any more messages from it.
|
||||
if (!base::NoBarrier_Load(&running_)) break;
|
||||
out_semaphore_.Wait();
|
||||
}
|
||||
return data;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ -2393,19 +2295,21 @@ void Worker::ExecuteInThread() {
|
||||
// Now wait for messages
|
||||
while (true) {
|
||||
in_semaphore_.Wait();
|
||||
SerializationData* data;
|
||||
std::unique_ptr<SerializationData> data;
|
||||
if (!in_queue_.Dequeue(&data)) continue;
|
||||
if (data == NULL) {
|
||||
if (!data) {
|
||||
break;
|
||||
}
|
||||
int offset = 0;
|
||||
Local<Value> data_value;
|
||||
if (Shell::DeserializeValue(isolate, *data, &offset)
|
||||
.ToLocal(&data_value)) {
|
||||
Local<Value> argv[] = {data_value};
|
||||
v8::TryCatch try_catch(isolate);
|
||||
Local<Value> value;
|
||||
if (Shell::DeserializeValue(isolate, std::move(data))
|
||||
.ToLocal(&value)) {
|
||||
Local<Value> argv[] = {value};
|
||||
(void)onmessage_fun->Call(context, global, 1, argv);
|
||||
}
|
||||
delete data;
|
||||
if (try_catch.HasCaught()) {
|
||||
Shell::ReportException(isolate, &try_catch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2432,21 +2336,15 @@ void Worker::PostMessageOut(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
}
|
||||
|
||||
Local<Value> message = args[0];
|
||||
|
||||
// TODO(binji): Allow transferring from worker to main thread?
|
||||
Shell::ObjectList to_transfer;
|
||||
|
||||
Shell::ObjectList seen_objects;
|
||||
SerializationData* data = new SerializationData;
|
||||
if (Shell::SerializeValue(isolate, message, to_transfer, &seen_objects,
|
||||
data)) {
|
||||
Local<Value> transfer = Undefined(isolate);
|
||||
std::unique_ptr<SerializationData> data =
|
||||
Shell::SerializeValue(isolate, message, transfer);
|
||||
if (data) {
|
||||
DCHECK(args.Data()->IsExternal());
|
||||
Local<External> this_value = Local<External>::Cast(args.Data());
|
||||
Worker* worker = static_cast<Worker*>(this_value->Value());
|
||||
worker->out_queue_.Enqueue(data);
|
||||
worker->out_queue_.Enqueue(std::move(data));
|
||||
worker->out_semaphore_.Signal();
|
||||
} else {
|
||||
delete data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2637,234 +2535,202 @@ void Shell::EmptyMessageQueues(Isolate* isolate) {
|
||||
}
|
||||
}
|
||||
|
||||
class Serializer : public ValueSerializer::Delegate {
|
||||
public:
|
||||
explicit Serializer(Isolate* isolate)
|
||||
: isolate_(isolate), serializer_(isolate, this) {}
|
||||
|
||||
bool Shell::SerializeValue(Isolate* isolate, Local<Value> value,
|
||||
const ObjectList& to_transfer,
|
||||
ObjectList* seen_objects,
|
||||
SerializationData* out_data) {
|
||||
DCHECK(out_data);
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
Maybe<bool> WriteValue(Local<Context> context, Local<Value> value,
|
||||
Local<Value> transfer) {
|
||||
bool ok;
|
||||
DCHECK(!data_);
|
||||
data_.reset(new SerializationData);
|
||||
if (!PrepareTransfer(context, transfer).To(&ok)) {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
serializer_.WriteHeader();
|
||||
|
||||
if (value->IsUndefined()) {
|
||||
out_data->WriteTag(kSerializationTagUndefined);
|
||||
} else if (value->IsNull()) {
|
||||
out_data->WriteTag(kSerializationTagNull);
|
||||
} else if (value->IsTrue()) {
|
||||
out_data->WriteTag(kSerializationTagTrue);
|
||||
} else if (value->IsFalse()) {
|
||||
out_data->WriteTag(kSerializationTagFalse);
|
||||
} else if (value->IsNumber()) {
|
||||
Local<Number> num = Local<Number>::Cast(value);
|
||||
double value = num->Value();
|
||||
out_data->WriteTag(kSerializationTagNumber);
|
||||
out_data->Write(value);
|
||||
} else if (value->IsString()) {
|
||||
v8::String::Utf8Value str(value);
|
||||
out_data->WriteTag(kSerializationTagString);
|
||||
out_data->Write(str.length());
|
||||
out_data->WriteMemory(*str, str.length());
|
||||
} else if (value->IsArray()) {
|
||||
Local<Array> array = Local<Array>::Cast(value);
|
||||
if (FindInObjectList(array, *seen_objects)) {
|
||||
Throw(isolate, "Duplicated arrays not supported");
|
||||
return false;
|
||||
}
|
||||
seen_objects->Add(array);
|
||||
out_data->WriteTag(kSerializationTagArray);
|
||||
uint32_t length = array->Length();
|
||||
out_data->Write(length);
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
Local<Value> element_value;
|
||||
if (array->Get(context, i).ToLocal(&element_value)) {
|
||||
if (!SerializeValue(isolate, element_value, to_transfer, seen_objects,
|
||||
out_data))
|
||||
return false;
|
||||
} else {
|
||||
Throw(isolate, "Failed to serialize array element.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (value->IsArrayBuffer()) {
|
||||
Local<ArrayBuffer> array_buffer = Local<ArrayBuffer>::Cast(value);
|
||||
if (FindInObjectList(array_buffer, *seen_objects)) {
|
||||
Throw(isolate, "Duplicated array buffers not supported");
|
||||
return false;
|
||||
}
|
||||
seen_objects->Add(array_buffer);
|
||||
if (FindInObjectList(array_buffer, to_transfer)) {
|
||||
// Transfer ArrayBuffer
|
||||
if (!array_buffer->IsNeuterable()) {
|
||||
Throw(isolate, "Attempting to transfer an un-neuterable ArrayBuffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
ArrayBuffer::Contents contents = array_buffer->IsExternal()
|
||||
? array_buffer->GetContents()
|
||||
: array_buffer->Externalize();
|
||||
array_buffer->Neuter();
|
||||
out_data->WriteArrayBufferContents(contents);
|
||||
} else {
|
||||
ArrayBuffer::Contents contents = array_buffer->GetContents();
|
||||
// Clone ArrayBuffer
|
||||
if (contents.ByteLength() > i::kMaxInt) {
|
||||
Throw(isolate, "ArrayBuffer is too big to clone");
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t byte_length = static_cast<int32_t>(contents.ByteLength());
|
||||
out_data->WriteTag(kSerializationTagArrayBuffer);
|
||||
out_data->Write(byte_length);
|
||||
out_data->WriteMemory(contents.Data(), byte_length);
|
||||
}
|
||||
} else if (value->IsSharedArrayBuffer()) {
|
||||
Local<SharedArrayBuffer> sab = Local<SharedArrayBuffer>::Cast(value);
|
||||
if (FindInObjectList(sab, *seen_objects)) {
|
||||
Throw(isolate, "Duplicated shared array buffers not supported");
|
||||
return false;
|
||||
}
|
||||
seen_objects->Add(sab);
|
||||
if (!FindInObjectList(sab, to_transfer)) {
|
||||
Throw(isolate, "SharedArrayBuffer must be transferred");
|
||||
return false;
|
||||
if (!serializer_.WriteValue(context, value).To(&ok)) {
|
||||
data_.reset();
|
||||
return Nothing<bool>();
|
||||
}
|
||||
|
||||
SharedArrayBuffer::Contents contents;
|
||||
if (sab->IsExternal()) {
|
||||
contents = sab->GetContents();
|
||||
} else {
|
||||
contents = sab->Externalize();
|
||||
base::LockGuard<base::Mutex> lock_guard(workers_mutex_.Pointer());
|
||||
externalized_shared_contents_.Add(contents);
|
||||
}
|
||||
out_data->WriteSharedArrayBufferContents(contents);
|
||||
} else if (value->IsObject()) {
|
||||
Local<Object> object = Local<Object>::Cast(value);
|
||||
if (FindInObjectList(object, *seen_objects)) {
|
||||
Throw(isolate, "Duplicated objects not supported");
|
||||
return false;
|
||||
}
|
||||
seen_objects->Add(object);
|
||||
Local<Array> property_names;
|
||||
if (!object->GetOwnPropertyNames(context).ToLocal(&property_names)) {
|
||||
Throw(isolate, "Unable to get property names");
|
||||
return false;
|
||||
if (!FinalizeTransfer().To(&ok)) {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
|
||||
uint32_t length = property_names->Length();
|
||||
out_data->WriteTag(kSerializationTagObject);
|
||||
out_data->Write(length);
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
Local<Value> name;
|
||||
Local<Value> property_value;
|
||||
if (property_names->Get(context, i).ToLocal(&name) &&
|
||||
object->Get(context, name).ToLocal(&property_value)) {
|
||||
if (!SerializeValue(isolate, name, to_transfer, seen_objects, out_data))
|
||||
return false;
|
||||
if (!SerializeValue(isolate, property_value, to_transfer, seen_objects,
|
||||
out_data))
|
||||
return false;
|
||||
} else {
|
||||
Throw(isolate, "Failed to serialize property.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Throw(isolate, "Don't know how to serialize object");
|
||||
return false;
|
||||
std::pair<uint8_t*, size_t> pair = serializer_.Release();
|
||||
data_->data_.reset(pair.first);
|
||||
data_->size_ = pair.second;
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
std::unique_ptr<SerializationData> Release() { return std::move(data_); }
|
||||
|
||||
protected:
|
||||
// Implements ValueSerializer::Delegate.
|
||||
void ThrowDataCloneError(Local<String> message) override {
|
||||
isolate_->ThrowException(Exception::Error(message));
|
||||
}
|
||||
|
||||
Maybe<uint32_t> GetSharedArrayBufferId(
|
||||
Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer) override {
|
||||
DCHECK(data_ != nullptr);
|
||||
for (size_t index = 0; index < shared_array_buffers_.size(); ++index) {
|
||||
if (shared_array_buffers_[index] == shared_array_buffer) {
|
||||
return Just<uint32_t>(static_cast<uint32_t>(index));
|
||||
}
|
||||
}
|
||||
|
||||
size_t index = shared_array_buffers_.size();
|
||||
shared_array_buffers_.emplace_back(isolate_, shared_array_buffer);
|
||||
return Just<uint32_t>(static_cast<uint32_t>(index));
|
||||
}
|
||||
|
||||
void* ReallocateBufferMemory(void* old_buffer, size_t size,
|
||||
size_t* actual_size) override {
|
||||
void* result = realloc(old_buffer, size);
|
||||
*actual_size = result ? size : 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
void FreeBufferMemory(void* buffer) override { free(buffer); }
|
||||
|
||||
private:
|
||||
Maybe<bool> PrepareTransfer(Local<Context> context, Local<Value> transfer) {
|
||||
if (transfer->IsArray()) {
|
||||
Local<Array> transfer_array = Local<Array>::Cast(transfer);
|
||||
uint32_t length = transfer_array->Length();
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
Local<Value> element;
|
||||
if (transfer_array->Get(context, i).ToLocal(&element)) {
|
||||
if (!element->IsArrayBuffer()) {
|
||||
Throw(isolate_, "Transfer array elements must be an ArrayBuffer");
|
||||
break;
|
||||
}
|
||||
|
||||
Local<ArrayBuffer> array_buffer = Local<ArrayBuffer>::Cast(element);
|
||||
serializer_.TransferArrayBuffer(
|
||||
static_cast<uint32_t>(array_buffers_.size()), array_buffer);
|
||||
array_buffers_.emplace_back(isolate_, array_buffer);
|
||||
} else {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
}
|
||||
return Just(true);
|
||||
} else if (transfer->IsUndefined()) {
|
||||
return Just(true);
|
||||
} else {
|
||||
Throw(isolate_, "Transfer list must be an Array or undefined");
|
||||
return Nothing<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<bool> FinalizeTransfer() {
|
||||
for (const auto& global_array_buffer : array_buffers_) {
|
||||
Local<ArrayBuffer> array_buffer =
|
||||
Local<ArrayBuffer>::New(isolate_, global_array_buffer);
|
||||
if (!array_buffer->IsNeuterable()) {
|
||||
Throw(isolate_, "ArrayBuffer could not be transferred");
|
||||
return Nothing<bool>();
|
||||
}
|
||||
|
||||
if (!array_buffer->IsExternal()) {
|
||||
array_buffer->Externalize();
|
||||
}
|
||||
ArrayBuffer::Contents contents = array_buffer->GetContents();
|
||||
array_buffer->Neuter();
|
||||
data_->array_buffer_contents_.push_back(contents);
|
||||
}
|
||||
|
||||
for (const auto& global_shared_array_buffer : shared_array_buffers_) {
|
||||
Local<SharedArrayBuffer> shared_array_buffer =
|
||||
Local<SharedArrayBuffer>::New(isolate_, global_shared_array_buffer);
|
||||
if (!shared_array_buffer->IsExternal()) {
|
||||
shared_array_buffer->Externalize();
|
||||
}
|
||||
data_->shared_array_buffer_contents_.push_back(
|
||||
shared_array_buffer->GetContents());
|
||||
}
|
||||
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
Isolate* isolate_;
|
||||
ValueSerializer serializer_;
|
||||
std::unique_ptr<SerializationData> data_;
|
||||
std::vector<Global<ArrayBuffer>> array_buffers_;
|
||||
std::vector<Global<SharedArrayBuffer>> shared_array_buffers_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Serializer);
|
||||
};
|
||||
|
||||
class Deserializer : public ValueDeserializer::Delegate {
|
||||
public:
|
||||
Deserializer(Isolate* isolate, std::unique_ptr<SerializationData> data)
|
||||
: isolate_(isolate),
|
||||
deserializer_(isolate, data->data(), data->size(), this),
|
||||
data_(std::move(data)) {
|
||||
deserializer_.SetSupportsLegacyWireFormat(true);
|
||||
}
|
||||
|
||||
MaybeLocal<Value> ReadValue(Local<Context> context) {
|
||||
bool read_header;
|
||||
if (!deserializer_.ReadHeader(context).To(&read_header)) {
|
||||
return MaybeLocal<Value>();
|
||||
}
|
||||
|
||||
uint32_t index = 0;
|
||||
for (const auto& contents : data_->array_buffer_contents()) {
|
||||
Local<ArrayBuffer> array_buffer =
|
||||
ArrayBuffer::New(isolate_, contents.Data(), contents.ByteLength());
|
||||
deserializer_.TransferArrayBuffer(index++, array_buffer);
|
||||
}
|
||||
|
||||
index = 0;
|
||||
for (const auto& contents : data_->shared_array_buffer_contents()) {
|
||||
Local<SharedArrayBuffer> shared_array_buffer = SharedArrayBuffer::New(
|
||||
isolate_, contents.Data(), contents.ByteLength());
|
||||
deserializer_.TransferSharedArrayBuffer(index++, shared_array_buffer);
|
||||
}
|
||||
|
||||
MaybeLocal<Value> result = deserializer_.ReadValue(context);
|
||||
if (!result.IsEmpty()) {
|
||||
data_->ClearTransferredArrayBuffers();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
Isolate* isolate_;
|
||||
ValueDeserializer deserializer_;
|
||||
std::unique_ptr<SerializationData> data_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Deserializer);
|
||||
};
|
||||
|
||||
std::unique_ptr<SerializationData> Shell::SerializeValue(
|
||||
Isolate* isolate, Local<Value> value, Local<Value> transfer) {
|
||||
bool ok;
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
Serializer serializer(isolate);
|
||||
if (serializer.WriteValue(context, value, transfer).To(&ok)) {
|
||||
std::unique_ptr<SerializationData> data = serializer.Release();
|
||||
base::LockGuard<base::Mutex> lock_guard(workers_mutex_.Pointer());
|
||||
for (const auto& contents : data->shared_array_buffer_contents()) {
|
||||
externalized_shared_contents_.insert(contents);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
MaybeLocal<Value> Shell::DeserializeValue(Isolate* isolate,
|
||||
const SerializationData& data,
|
||||
int* offset) {
|
||||
DCHECK(offset);
|
||||
EscapableHandleScope scope(isolate);
|
||||
Local<Value> result;
|
||||
SerializationTag tag = data.ReadTag(offset);
|
||||
|
||||
switch (tag) {
|
||||
case kSerializationTagUndefined:
|
||||
result = Undefined(isolate);
|
||||
break;
|
||||
case kSerializationTagNull:
|
||||
result = Null(isolate);
|
||||
break;
|
||||
case kSerializationTagTrue:
|
||||
result = True(isolate);
|
||||
break;
|
||||
case kSerializationTagFalse:
|
||||
result = False(isolate);
|
||||
break;
|
||||
case kSerializationTagNumber:
|
||||
result = Number::New(isolate, data.Read<double>(offset));
|
||||
break;
|
||||
case kSerializationTagString: {
|
||||
int length = data.Read<int>(offset);
|
||||
CHECK(length >= 0);
|
||||
std::vector<char> buffer(length + 1); // + 1 so it is never empty.
|
||||
data.ReadMemory(&buffer[0], length, offset);
|
||||
MaybeLocal<String> str =
|
||||
String::NewFromUtf8(isolate, &buffer[0], NewStringType::kNormal,
|
||||
length).ToLocalChecked();
|
||||
if (!str.IsEmpty()) result = str.ToLocalChecked();
|
||||
break;
|
||||
}
|
||||
case kSerializationTagArray: {
|
||||
uint32_t length = data.Read<uint32_t>(offset);
|
||||
Local<Array> array = Array::New(isolate, length);
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
Local<Value> element_value;
|
||||
CHECK(DeserializeValue(isolate, data, offset).ToLocal(&element_value));
|
||||
array->Set(isolate->GetCurrentContext(), i, element_value).FromJust();
|
||||
}
|
||||
result = array;
|
||||
break;
|
||||
}
|
||||
case kSerializationTagObject: {
|
||||
int length = data.Read<int>(offset);
|
||||
Local<Object> object = Object::New(isolate);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
Local<Value> property_name;
|
||||
CHECK(DeserializeValue(isolate, data, offset).ToLocal(&property_name));
|
||||
Local<Value> property_value;
|
||||
CHECK(DeserializeValue(isolate, data, offset).ToLocal(&property_value));
|
||||
object->Set(isolate->GetCurrentContext(), property_name, property_value)
|
||||
.FromJust();
|
||||
}
|
||||
result = object;
|
||||
break;
|
||||
}
|
||||
case kSerializationTagArrayBuffer: {
|
||||
int32_t byte_length = data.Read<int32_t>(offset);
|
||||
Local<ArrayBuffer> array_buffer = ArrayBuffer::New(isolate, byte_length);
|
||||
ArrayBuffer::Contents contents = array_buffer->GetContents();
|
||||
DCHECK(static_cast<size_t>(byte_length) == contents.ByteLength());
|
||||
data.ReadMemory(contents.Data(), byte_length, offset);
|
||||
result = array_buffer;
|
||||
break;
|
||||
}
|
||||
case kSerializationTagTransferredArrayBuffer: {
|
||||
ArrayBuffer::Contents contents;
|
||||
data.ReadArrayBufferContents(&contents, offset);
|
||||
result = ArrayBuffer::New(isolate, contents.Data(), contents.ByteLength(),
|
||||
ArrayBufferCreationMode::kInternalized);
|
||||
break;
|
||||
}
|
||||
case kSerializationTagTransferredSharedArrayBuffer: {
|
||||
SharedArrayBuffer::Contents contents;
|
||||
data.ReadSharedArrayBufferContents(&contents, offset);
|
||||
result = SharedArrayBuffer::New(isolate, contents.Data(),
|
||||
contents.ByteLength());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return scope.Escape(result);
|
||||
MaybeLocal<Value> Shell::DeserializeValue(
|
||||
Isolate* isolate, std::unique_ptr<SerializationData> data) {
|
||||
Local<Value> value;
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
Deserializer deserializer(isolate, std::move(data));
|
||||
return deserializer.ReadValue(context);
|
||||
}
|
||||
|
||||
|
||||
@ -2890,12 +2756,10 @@ void Shell::CleanupWorkers() {
|
||||
base::LockGuard<base::Mutex> lock_guard(workers_mutex_.Pointer());
|
||||
allow_new_workers_ = true;
|
||||
|
||||
for (int i = 0; i < externalized_shared_contents_.length(); ++i) {
|
||||
const SharedArrayBuffer::Contents& contents =
|
||||
externalized_shared_contents_[i];
|
||||
for (const auto& contents : externalized_shared_contents_) {
|
||||
Shell::array_buffer_allocator->Free(contents.Data(), contents.ByteLength());
|
||||
}
|
||||
externalized_shared_contents_.Clear();
|
||||
externalized_shared_contents_.clear();
|
||||
}
|
||||
|
||||
|
||||
|
109
deps/v8/src/d8.h
vendored
109
deps/v8/src/d8.h
vendored
@ -5,9 +5,13 @@
|
||||
#ifndef V8_D8_H_
|
||||
#define V8_D8_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "src/allocation.h"
|
||||
#include "src/base/functional.h"
|
||||
#include "src/base/hashmap.h"
|
||||
#include "src/base/platform/time.h"
|
||||
#include "src/list.h"
|
||||
@ -143,68 +147,51 @@ class SourceGroup {
|
||||
int end_offset_;
|
||||
};
|
||||
|
||||
enum SerializationTag {
|
||||
kSerializationTagUndefined,
|
||||
kSerializationTagNull,
|
||||
kSerializationTagTrue,
|
||||
kSerializationTagFalse,
|
||||
kSerializationTagNumber,
|
||||
kSerializationTagString,
|
||||
kSerializationTagArray,
|
||||
kSerializationTagObject,
|
||||
kSerializationTagArrayBuffer,
|
||||
kSerializationTagTransferredArrayBuffer,
|
||||
kSerializationTagTransferredSharedArrayBuffer,
|
||||
};
|
||||
|
||||
|
||||
class SerializationData {
|
||||
public:
|
||||
SerializationData() {}
|
||||
SerializationData() : data_(nullptr), size_(0) {}
|
||||
~SerializationData();
|
||||
|
||||
void WriteTag(SerializationTag tag);
|
||||
void WriteMemory(const void* p, int length);
|
||||
void WriteArrayBufferContents(const ArrayBuffer::Contents& contents);
|
||||
void WriteSharedArrayBufferContents(
|
||||
const SharedArrayBuffer::Contents& contents);
|
||||
|
||||
template <typename T>
|
||||
void Write(const T& data) {
|
||||
WriteMemory(&data, sizeof(data));
|
||||
uint8_t* data() { return data_.get(); }
|
||||
size_t size() { return size_; }
|
||||
const std::vector<ArrayBuffer::Contents>& array_buffer_contents() {
|
||||
return array_buffer_contents_;
|
||||
}
|
||||
const std::vector<SharedArrayBuffer::Contents>&
|
||||
shared_array_buffer_contents() {
|
||||
return shared_array_buffer_contents_;
|
||||
}
|
||||
|
||||
SerializationTag ReadTag(int* offset) const;
|
||||
void ReadMemory(void* p, int length, int* offset) const;
|
||||
void ReadArrayBufferContents(ArrayBuffer::Contents* contents,
|
||||
int* offset) const;
|
||||
void ReadSharedArrayBufferContents(SharedArrayBuffer::Contents* contents,
|
||||
int* offset) const;
|
||||
|
||||
template <typename T>
|
||||
T Read(int* offset) const {
|
||||
T value;
|
||||
ReadMemory(&value, sizeof(value), offset);
|
||||
return value;
|
||||
}
|
||||
void ClearTransferredArrayBuffers();
|
||||
|
||||
private:
|
||||
i::List<uint8_t> data_;
|
||||
i::List<ArrayBuffer::Contents> array_buffer_contents_;
|
||||
i::List<SharedArrayBuffer::Contents> shared_array_buffer_contents_;
|
||||
struct DataDeleter {
|
||||
void operator()(uint8_t* p) const { free(p); }
|
||||
};
|
||||
|
||||
std::unique_ptr<uint8_t, DataDeleter> data_;
|
||||
size_t size_;
|
||||
std::vector<ArrayBuffer::Contents> array_buffer_contents_;
|
||||
std::vector<SharedArrayBuffer::Contents> shared_array_buffer_contents_;
|
||||
|
||||
private:
|
||||
friend class Serializer;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SerializationData);
|
||||
};
|
||||
|
||||
|
||||
class SerializationDataQueue {
|
||||
public:
|
||||
void Enqueue(SerializationData* data);
|
||||
bool Dequeue(SerializationData** data);
|
||||
void Enqueue(std::unique_ptr<SerializationData> data);
|
||||
bool Dequeue(std::unique_ptr<SerializationData>* data);
|
||||
bool IsEmpty();
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
base::Mutex mutex_;
|
||||
i::List<SerializationData*> data_;
|
||||
std::vector<std::unique_ptr<SerializationData>> data_;
|
||||
};
|
||||
|
||||
|
||||
@ -219,13 +206,13 @@ class Worker {
|
||||
// Post a message to the worker's incoming message queue. The worker will
|
||||
// take ownership of the SerializationData.
|
||||
// This function should only be called by the thread that created the Worker.
|
||||
void PostMessage(SerializationData* data);
|
||||
void PostMessage(std::unique_ptr<SerializationData> data);
|
||||
// Synchronously retrieve messages from the worker's outgoing message queue.
|
||||
// If there is no message in the queue, block until a message is available.
|
||||
// If there are no messages in the queue and the worker is no longer running,
|
||||
// return nullptr.
|
||||
// This function should only be called by the thread that created the Worker.
|
||||
SerializationData* GetMessage();
|
||||
std::unique_ptr<SerializationData> GetMessage();
|
||||
// Terminate the worker's event loop. Messages from the worker that have been
|
||||
// queued can still be read via GetMessage().
|
||||
// This function can be called by any thread.
|
||||
@ -335,16 +322,10 @@ class Shell : public i::AllStatic {
|
||||
static void CollectGarbage(Isolate* isolate);
|
||||
static void EmptyMessageQueues(Isolate* isolate);
|
||||
|
||||
// TODO(binji): stupid implementation for now. Is there an easy way to hash an
|
||||
// object for use in base::HashMap? By pointer?
|
||||
typedef i::List<Local<Object>> ObjectList;
|
||||
static bool SerializeValue(Isolate* isolate, Local<Value> value,
|
||||
const ObjectList& to_transfer,
|
||||
ObjectList* seen_objects,
|
||||
SerializationData* out_data);
|
||||
static MaybeLocal<Value> DeserializeValue(Isolate* isolate,
|
||||
const SerializationData& data,
|
||||
int* offset);
|
||||
static std::unique_ptr<SerializationData> SerializeValue(
|
||||
Isolate* isolate, Local<Value> value, Local<Value> transfer);
|
||||
static MaybeLocal<Value> DeserializeValue(
|
||||
Isolate* isolate, std::unique_ptr<SerializationData> data);
|
||||
static void CleanupWorkers();
|
||||
static int* LookupCounter(const char* name);
|
||||
static void* CreateHistogram(const char* name,
|
||||
@ -444,10 +425,26 @@ class Shell : public i::AllStatic {
|
||||
static base::LazyMutex context_mutex_;
|
||||
static const base::TimeTicks kInitialTicks;
|
||||
|
||||
struct SharedArrayBufferContentsHash {
|
||||
size_t operator()(const v8::SharedArrayBuffer::Contents& contents) const {
|
||||
return base::hash_combine(contents.Data(), contents.ByteLength());
|
||||
}
|
||||
};
|
||||
|
||||
struct SharedArrayBufferContentsIsEqual {
|
||||
bool operator()(const SharedArrayBuffer::Contents& a,
|
||||
const SharedArrayBuffer::Contents& b) const {
|
||||
return a.Data() == b.Data() && a.ByteLength() == b.ByteLength();
|
||||
}
|
||||
};
|
||||
|
||||
static base::LazyMutex workers_mutex_;
|
||||
static bool allow_new_workers_;
|
||||
static i::List<Worker*> workers_;
|
||||
static i::List<SharedArrayBuffer::Contents> externalized_shared_contents_;
|
||||
static std::unordered_set<SharedArrayBuffer::Contents,
|
||||
SharedArrayBufferContentsHash,
|
||||
SharedArrayBufferContentsIsEqual>
|
||||
externalized_shared_contents_;
|
||||
|
||||
static void WriteIgnitionDispatchCountersFile(v8::Isolate* isolate);
|
||||
static Counter* GetCounter(const char* name, bool is_histogram);
|
||||
|
1
deps/v8/src/messages.h
vendored
1
deps/v8/src/messages.h
vendored
@ -675,6 +675,7 @@ class ErrorUtils : public AllStatic {
|
||||
T(AsmJsInstantiated, "Instantiated asm.js: %") \
|
||||
/* DataCloneError messages */ \
|
||||
T(DataCloneError, "% could not be cloned.") \
|
||||
T(DataCloneErrorOutOfMemory, "Data cannot be cloned, out of memory.") \
|
||||
T(DataCloneErrorNeuteredArrayBuffer, \
|
||||
"An ArrayBuffer is neutered and could not be cloned.") \
|
||||
T(DataCloneErrorSharedArrayBufferTransferred, \
|
||||
|
253
deps/v8/src/value-serializer.cc
vendored
253
deps/v8/src/value-serializer.cc
vendored
@ -23,7 +23,14 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
static const uint32_t kLatestVersion = 9;
|
||||
// Version 9: (imported from Blink)
|
||||
// Version 10: one-byte (Latin-1) strings
|
||||
// Version 11: properly separate undefined from the hole in arrays
|
||||
// Version 12: regexp and string objects share normal string encoding
|
||||
// Version 13: host objects have an explicit tag (rather than handling all
|
||||
// unknown tags)
|
||||
static const uint32_t kLatestVersion = 13;
|
||||
|
||||
static const int kPretenureThreshold = 100 * KB;
|
||||
|
||||
template <typename T>
|
||||
@ -46,6 +53,7 @@ enum class SerializationTag : uint8_t {
|
||||
// refTableSize:uint32_t (previously used for sanity checks; safe to ignore)
|
||||
kVerifyObjectCount = '?',
|
||||
// Oddballs (no data).
|
||||
kTheHole = '-',
|
||||
kUndefined = '_',
|
||||
kNull = '0',
|
||||
kTrue = 'T',
|
||||
@ -61,6 +69,7 @@ enum class SerializationTag : uint8_t {
|
||||
kDouble = 'N',
|
||||
// byteLength:uint32_t, then raw data
|
||||
kUtf8String = 'S',
|
||||
kOneByteString = '"',
|
||||
kTwoByteString = 'c',
|
||||
// Reference to a serialized object. objectID:uint32_t
|
||||
kObjectReference = '^',
|
||||
@ -117,6 +126,9 @@ enum class SerializationTag : uint8_t {
|
||||
// wasmWireByteLength:uint32_t, then raw data
|
||||
// compiledDataLength:uint32_t, then raw data
|
||||
kWasmModule = 'W',
|
||||
// The delegate is responsible for processing all following data.
|
||||
// This "escapes" to whatever wire format the delegate chooses.
|
||||
kHostObject = '\\',
|
||||
};
|
||||
|
||||
namespace {
|
||||
@ -163,6 +175,10 @@ void ValueSerializer::WriteHeader() {
|
||||
WriteVarint(kLatestVersion);
|
||||
}
|
||||
|
||||
void ValueSerializer::SetTreatArrayBufferViewsAsHostObjects(bool mode) {
|
||||
treat_array_buffer_views_as_host_objects_ = mode;
|
||||
}
|
||||
|
||||
void ValueSerializer::WriteTag(SerializationTag tag) {
|
||||
uint8_t raw_tag = static_cast<uint8_t>(tag);
|
||||
WriteRawBytes(&raw_tag, sizeof(raw_tag));
|
||||
@ -217,18 +233,26 @@ void ValueSerializer::WriteTwoByteString(Vector<const uc16> chars) {
|
||||
}
|
||||
|
||||
void ValueSerializer::WriteRawBytes(const void* source, size_t length) {
|
||||
memcpy(ReserveRawBytes(length), source, length);
|
||||
uint8_t* dest;
|
||||
if (ReserveRawBytes(length).To(&dest)) {
|
||||
memcpy(dest, source, length);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* ValueSerializer::ReserveRawBytes(size_t bytes) {
|
||||
Maybe<uint8_t*> ValueSerializer::ReserveRawBytes(size_t bytes) {
|
||||
size_t old_size = buffer_size_;
|
||||
size_t new_size = old_size + bytes;
|
||||
if (new_size > buffer_capacity_) ExpandBuffer(new_size);
|
||||
if (V8_UNLIKELY(new_size > buffer_capacity_)) {
|
||||
bool ok;
|
||||
if (!ExpandBuffer(new_size).To(&ok)) {
|
||||
return Nothing<uint8_t*>();
|
||||
}
|
||||
}
|
||||
buffer_size_ = new_size;
|
||||
return &buffer_[old_size];
|
||||
return Just(&buffer_[old_size]);
|
||||
}
|
||||
|
||||
void ValueSerializer::ExpandBuffer(size_t required_capacity) {
|
||||
Maybe<bool> ValueSerializer::ExpandBuffer(size_t required_capacity) {
|
||||
DCHECK_GT(required_capacity, buffer_capacity_);
|
||||
size_t requested_capacity =
|
||||
std::max(required_capacity, buffer_capacity_ * 2) + 64;
|
||||
@ -241,9 +265,15 @@ void ValueSerializer::ExpandBuffer(size_t required_capacity) {
|
||||
new_buffer = realloc(buffer_, requested_capacity);
|
||||
provided_capacity = requested_capacity;
|
||||
}
|
||||
DCHECK_GE(provided_capacity, requested_capacity);
|
||||
buffer_ = reinterpret_cast<uint8_t*>(new_buffer);
|
||||
buffer_capacity_ = provided_capacity;
|
||||
if (new_buffer) {
|
||||
DCHECK(provided_capacity >= requested_capacity);
|
||||
buffer_ = reinterpret_cast<uint8_t*>(new_buffer);
|
||||
buffer_capacity_ = provided_capacity;
|
||||
return Just(true);
|
||||
} else {
|
||||
out_of_memory_ = true;
|
||||
return Nothing<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
void ValueSerializer::WriteUint32(uint32_t value) {
|
||||
@ -274,20 +304,21 @@ void ValueSerializer::TransferArrayBuffer(uint32_t transfer_id,
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
|
||||
out_of_memory_ = false;
|
||||
if (object->IsSmi()) {
|
||||
WriteSmi(Smi::cast(*object));
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
DCHECK(object->IsHeapObject());
|
||||
switch (HeapObject::cast(*object)->map()->instance_type()) {
|
||||
case ODDBALL_TYPE:
|
||||
WriteOddball(Oddball::cast(*object));
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
case HEAP_NUMBER_TYPE:
|
||||
case MUTABLE_HEAP_NUMBER_TYPE:
|
||||
WriteHeapNumber(HeapNumber::cast(*object));
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
case JS_TYPED_ARRAY_TYPE:
|
||||
case JS_DATA_VIEW_TYPE: {
|
||||
// Despite being JSReceivers, these have their wrapped buffer serialized
|
||||
@ -296,7 +327,7 @@ Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
|
||||
// TODO(jbroman): It may be possible to avoid materializing a typed
|
||||
// array's buffer here.
|
||||
Handle<JSArrayBufferView> view = Handle<JSArrayBufferView>::cast(object);
|
||||
if (!id_map_.Find(view)) {
|
||||
if (!id_map_.Find(view) && !treat_array_buffer_views_as_host_objects_) {
|
||||
Handle<JSArrayBuffer> buffer(
|
||||
view->IsJSTypedArray()
|
||||
? Handle<JSTypedArray>::cast(view)->GetBuffer()
|
||||
@ -308,7 +339,7 @@ Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
|
||||
default:
|
||||
if (object->IsString()) {
|
||||
WriteString(Handle<String>::cast(object));
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
} else if (object->IsJSReceiver()) {
|
||||
return WriteJSReceiver(Handle<JSReceiver>::cast(object));
|
||||
} else {
|
||||
@ -357,22 +388,9 @@ void ValueSerializer::WriteString(Handle<String> string) {
|
||||
String::FlatContent flat = string->GetFlatContent();
|
||||
DCHECK(flat.IsFlat());
|
||||
if (flat.IsOneByte()) {
|
||||
// The existing format uses UTF-8, rather than Latin-1. As a result we must
|
||||
// to do work to encode strings that have characters outside ASCII.
|
||||
// TODO(jbroman): In a future format version, consider adding a tag for
|
||||
// Latin-1 strings, so that this can be skipped.
|
||||
WriteTag(SerializationTag::kUtf8String);
|
||||
Vector<const uint8_t> chars = flat.ToOneByteVector();
|
||||
if (String::IsAscii(chars.begin(), chars.length())) {
|
||||
WriteOneByteString(chars);
|
||||
} else {
|
||||
v8::Local<v8::String> api_string = Utils::ToLocal(string);
|
||||
uint32_t utf8_length = api_string->Utf8Length();
|
||||
WriteVarint(utf8_length);
|
||||
api_string->WriteUtf8(
|
||||
reinterpret_cast<char*>(ReserveRawBytes(utf8_length)), utf8_length,
|
||||
nullptr, v8::String::NO_NULL_TERMINATION);
|
||||
}
|
||||
WriteTag(SerializationTag::kOneByteString);
|
||||
WriteOneByteString(chars);
|
||||
} else if (flat.IsTwoByte()) {
|
||||
Vector<const uc16> chars = flat.ToUC16Vector();
|
||||
uint32_t byte_length = chars.length() * sizeof(uc16);
|
||||
@ -392,7 +410,7 @@ Maybe<bool> ValueSerializer::WriteJSReceiver(Handle<JSReceiver> receiver) {
|
||||
if (uint32_t id = *id_map_entry) {
|
||||
WriteTag(SerializationTag::kObjectReference);
|
||||
WriteVarint(id - 1);
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
// Otherwise, allocate an ID for it.
|
||||
@ -432,12 +450,12 @@ Maybe<bool> ValueSerializer::WriteJSReceiver(Handle<JSReceiver> receiver) {
|
||||
return WriteHostObject(Handle<JSObject>::cast(receiver));
|
||||
case JS_DATE_TYPE:
|
||||
WriteJSDate(JSDate::cast(*receiver));
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
case JS_VALUE_TYPE:
|
||||
return WriteJSValue(Handle<JSValue>::cast(receiver));
|
||||
case JS_REGEXP_TYPE:
|
||||
WriteJSRegExp(JSRegExp::cast(*receiver));
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
case JS_MAP_TYPE:
|
||||
return WriteJSMap(Handle<JSMap>::cast(receiver));
|
||||
case JS_SET_TYPE:
|
||||
@ -498,7 +516,7 @@ Maybe<bool> ValueSerializer::WriteJSObject(Handle<JSObject> object) {
|
||||
|
||||
WriteTag(SerializationTag::kEndJSObject);
|
||||
WriteVarint<uint32_t>(properties_written);
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteJSObjectSlow(Handle<JSObject> object) {
|
||||
@ -513,7 +531,7 @@ Maybe<bool> ValueSerializer::WriteJSObjectSlow(Handle<JSObject> object) {
|
||||
}
|
||||
WriteTag(SerializationTag::kEndJSObject);
|
||||
WriteVarint<uint32_t>(properties_written);
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteJSArray(Handle<JSArray> array) {
|
||||
@ -532,10 +550,6 @@ Maybe<bool> ValueSerializer::WriteJSArray(Handle<JSArray> array) {
|
||||
|
||||
if (should_serialize_densely) {
|
||||
DCHECK_LE(length, static_cast<uint32_t>(FixedArray::kMaxLength));
|
||||
|
||||
// TODO(jbroman): Distinguish between undefined and a hole (this can happen
|
||||
// if serializing one of the elements deletes another). This requires wire
|
||||
// format changes.
|
||||
WriteTag(SerializationTag::kBeginDenseJSArray);
|
||||
WriteVarint<uint32_t>(length);
|
||||
uint32_t i = 0;
|
||||
@ -550,6 +564,9 @@ Maybe<bool> ValueSerializer::WriteJSArray(Handle<JSArray> array) {
|
||||
break;
|
||||
}
|
||||
case FAST_DOUBLE_ELEMENTS: {
|
||||
// Elements are empty_fixed_array, not a FixedDoubleArray, if the array
|
||||
// is empty. No elements to encode in this case anyhow.
|
||||
if (length == 0) break;
|
||||
Handle<FixedDoubleArray> elements(
|
||||
FixedDoubleArray::cast(array->elements()), isolate_);
|
||||
for (; i < length; i++) {
|
||||
@ -583,6 +600,13 @@ Maybe<bool> ValueSerializer::WriteJSArray(Handle<JSArray> array) {
|
||||
// with.
|
||||
Handle<Object> element;
|
||||
LookupIterator it(isolate_, array, i, array, LookupIterator::OWN);
|
||||
if (!it.IsFound()) {
|
||||
// This can happen in the case where an array that was originally dense
|
||||
// became sparse during serialization. It's too late to switch to the
|
||||
// sparse format, but we can mark the elements as absent.
|
||||
WriteTag(SerializationTag::kTheHole);
|
||||
continue;
|
||||
}
|
||||
if (!Object::GetProperty(&it).ToHandle(&element) ||
|
||||
!WriteObject(element).FromMaybe(false)) {
|
||||
return Nothing<bool>();
|
||||
@ -618,7 +642,7 @@ Maybe<bool> ValueSerializer::WriteJSArray(Handle<JSArray> array) {
|
||||
WriteVarint<uint32_t>(properties_written);
|
||||
WriteVarint<uint32_t>(length);
|
||||
}
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
void ValueSerializer::WriteJSDate(JSDate* date) {
|
||||
@ -636,32 +660,19 @@ Maybe<bool> ValueSerializer::WriteJSValue(Handle<JSValue> value) {
|
||||
WriteTag(SerializationTag::kNumberObject);
|
||||
WriteDouble(inner_value->Number());
|
||||
} else if (inner_value->IsString()) {
|
||||
// TODO(jbroman): Replace UTF-8 encoding with the same options available for
|
||||
// ordinary strings.
|
||||
WriteTag(SerializationTag::kStringObject);
|
||||
v8::Local<v8::String> api_string =
|
||||
Utils::ToLocal(handle(String::cast(inner_value), isolate_));
|
||||
uint32_t utf8_length = api_string->Utf8Length();
|
||||
WriteVarint(utf8_length);
|
||||
api_string->WriteUtf8(reinterpret_cast<char*>(ReserveRawBytes(utf8_length)),
|
||||
utf8_length, nullptr,
|
||||
v8::String::NO_NULL_TERMINATION);
|
||||
WriteString(handle(String::cast(inner_value), isolate_));
|
||||
} else {
|
||||
DCHECK(inner_value->IsSymbol());
|
||||
ThrowDataCloneError(MessageTemplate::kDataCloneError, value);
|
||||
return Nothing<bool>();
|
||||
}
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
void ValueSerializer::WriteJSRegExp(JSRegExp* regexp) {
|
||||
WriteTag(SerializationTag::kRegExp);
|
||||
v8::Local<v8::String> api_string =
|
||||
Utils::ToLocal(handle(regexp->Pattern(), isolate_));
|
||||
uint32_t utf8_length = api_string->Utf8Length();
|
||||
WriteVarint(utf8_length);
|
||||
api_string->WriteUtf8(reinterpret_cast<char*>(ReserveRawBytes(utf8_length)),
|
||||
utf8_length, nullptr, v8::String::NO_NULL_TERMINATION);
|
||||
WriteString(handle(regexp->Pattern(), isolate_));
|
||||
WriteVarint(static_cast<uint32_t>(regexp->GetFlags()));
|
||||
}
|
||||
|
||||
@ -693,7 +704,7 @@ Maybe<bool> ValueSerializer::WriteJSMap(Handle<JSMap> map) {
|
||||
}
|
||||
WriteTag(SerializationTag::kEndJSMap);
|
||||
WriteVarint<uint32_t>(length);
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteJSSet(Handle<JSSet> set) {
|
||||
@ -723,7 +734,7 @@ Maybe<bool> ValueSerializer::WriteJSSet(Handle<JSSet> set) {
|
||||
}
|
||||
WriteTag(SerializationTag::kEndJSSet);
|
||||
WriteVarint<uint32_t>(length);
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteJSArrayBuffer(
|
||||
@ -741,14 +752,14 @@ Maybe<bool> ValueSerializer::WriteJSArrayBuffer(
|
||||
|
||||
WriteTag(SerializationTag::kSharedArrayBuffer);
|
||||
WriteVarint(index.FromJust());
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
uint32_t* transfer_entry = array_buffer_transfer_map_.Find(array_buffer);
|
||||
if (transfer_entry) {
|
||||
WriteTag(SerializationTag::kArrayBufferTransfer);
|
||||
WriteVarint(*transfer_entry);
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
if (array_buffer->was_neutered()) {
|
||||
ThrowDataCloneError(MessageTemplate::kDataCloneErrorNeuteredArrayBuffer);
|
||||
@ -762,10 +773,13 @@ Maybe<bool> ValueSerializer::WriteJSArrayBuffer(
|
||||
WriteTag(SerializationTag::kArrayBuffer);
|
||||
WriteVarint<uint32_t>(byte_length);
|
||||
WriteRawBytes(array_buffer->backing_store(), byte_length);
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteJSArrayBufferView(JSArrayBufferView* view) {
|
||||
if (treat_array_buffer_views_as_host_objects_) {
|
||||
return WriteHostObject(handle(view, isolate_));
|
||||
}
|
||||
WriteTag(SerializationTag::kArrayBufferView);
|
||||
ArrayBufferViewTag tag = ArrayBufferViewTag::kInt8Array;
|
||||
if (view->IsJSTypedArray()) {
|
||||
@ -784,7 +798,7 @@ Maybe<bool> ValueSerializer::WriteJSArrayBufferView(JSArrayBufferView* view) {
|
||||
WriteVarint(static_cast<uint8_t>(tag));
|
||||
WriteVarint(NumberToUint32(view->byte_offset()));
|
||||
WriteVarint(NumberToUint32(view->byte_length()));
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteWasmModule(Handle<JSObject> object) {
|
||||
@ -797,8 +811,10 @@ Maybe<bool> ValueSerializer::WriteWasmModule(Handle<JSObject> object) {
|
||||
Handle<String> wire_bytes(compiled_part->module_bytes(), isolate_);
|
||||
int wire_bytes_length = wire_bytes->length();
|
||||
WriteVarint<uint32_t>(wire_bytes_length);
|
||||
uint8_t* destination = ReserveRawBytes(wire_bytes_length);
|
||||
String::WriteToFlat(*wire_bytes, destination, 0, wire_bytes_length);
|
||||
uint8_t* destination;
|
||||
if (ReserveRawBytes(wire_bytes_length).To(&destination)) {
|
||||
String::WriteToFlat(*wire_bytes, destination, 0, wire_bytes_length);
|
||||
}
|
||||
|
||||
std::unique_ptr<ScriptData> script_data =
|
||||
WasmCompiledModuleSerializer::SerializeWasmModule(isolate_,
|
||||
@ -807,10 +823,11 @@ Maybe<bool> ValueSerializer::WriteWasmModule(Handle<JSObject> object) {
|
||||
WriteVarint<uint32_t>(script_data_length);
|
||||
WriteRawBytes(script_data->data(), script_data_length);
|
||||
|
||||
return Just(true);
|
||||
return ThrowIfOutOfMemory();
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteHostObject(Handle<JSObject> object) {
|
||||
WriteTag(SerializationTag::kHostObject);
|
||||
if (!delegate_) {
|
||||
isolate_->Throw(*isolate_->factory()->NewError(
|
||||
isolate_->error_function(), MessageTemplate::kDataCloneError, object));
|
||||
@ -858,6 +875,14 @@ void ValueSerializer::ThrowDataCloneError(
|
||||
isolate_->factory()->empty_string());
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::ThrowIfOutOfMemory() {
|
||||
if (out_of_memory_) {
|
||||
ThrowDataCloneError(MessageTemplate::kDataCloneErrorOutOfMemory);
|
||||
return Nothing<bool>();
|
||||
}
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
void ValueSerializer::ThrowDataCloneError(
|
||||
MessageTemplate::Template template_index, Handle<Object> arg0) {
|
||||
Handle<String> message =
|
||||
@ -1084,6 +1109,8 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() {
|
||||
}
|
||||
case SerializationTag::kUtf8String:
|
||||
return ReadUtf8String();
|
||||
case SerializationTag::kOneByteString:
|
||||
return ReadOneByteString();
|
||||
case SerializationTag::kTwoByteString:
|
||||
return ReadTwoByteString();
|
||||
case SerializationTag::kObjectReference: {
|
||||
@ -1122,14 +1149,28 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() {
|
||||
}
|
||||
case SerializationTag::kWasmModule:
|
||||
return ReadWasmModule();
|
||||
default:
|
||||
// TODO(jbroman): Introduce an explicit tag for host objects to avoid
|
||||
// having to treat every unknown tag as a potential host object.
|
||||
position_--;
|
||||
case SerializationTag::kHostObject:
|
||||
return ReadHostObject();
|
||||
default:
|
||||
// Before there was an explicit tag for host objects, all unknown tags
|
||||
// were delegated to the host.
|
||||
if (version_ < 13) {
|
||||
position_--;
|
||||
return ReadHostObject();
|
||||
}
|
||||
return MaybeHandle<Object>();
|
||||
}
|
||||
}
|
||||
|
||||
MaybeHandle<String> ValueDeserializer::ReadString() {
|
||||
if (version_ < 12) return ReadUtf8String();
|
||||
Handle<Object> object;
|
||||
if (!ReadObject().ToHandle(&object) || !object->IsString()) {
|
||||
return MaybeHandle<String>();
|
||||
}
|
||||
return Handle<String>::cast(object);
|
||||
}
|
||||
|
||||
MaybeHandle<String> ValueDeserializer::ReadUtf8String() {
|
||||
uint32_t utf8_length;
|
||||
Vector<const uint8_t> utf8_bytes;
|
||||
@ -1142,6 +1183,18 @@ MaybeHandle<String> ValueDeserializer::ReadUtf8String() {
|
||||
Vector<const char>::cast(utf8_bytes), pretenure_);
|
||||
}
|
||||
|
||||
MaybeHandle<String> ValueDeserializer::ReadOneByteString() {
|
||||
uint32_t byte_length;
|
||||
Vector<const uint8_t> bytes;
|
||||
if (!ReadVarint<uint32_t>().To(&byte_length) ||
|
||||
byte_length >
|
||||
static_cast<uint32_t>(std::numeric_limits<int32_t>::max()) ||
|
||||
!ReadRawBytes(byte_length).To(&bytes)) {
|
||||
return MaybeHandle<String>();
|
||||
}
|
||||
return isolate_->factory()->NewStringFromOneByte(bytes, pretenure_);
|
||||
}
|
||||
|
||||
MaybeHandle<String> ValueDeserializer::ReadTwoByteString() {
|
||||
uint32_t byte_length;
|
||||
Vector<const uint8_t> bytes;
|
||||
@ -1280,10 +1333,20 @@ MaybeHandle<JSArray> ValueDeserializer::ReadDenseJSArray() {
|
||||
|
||||
Handle<FixedArray> elements(FixedArray::cast(array->elements()), isolate_);
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
SerializationTag tag;
|
||||
if (PeekTag().To(&tag) && tag == SerializationTag::kTheHole) {
|
||||
ConsumeTag(SerializationTag::kTheHole);
|
||||
continue;
|
||||
}
|
||||
|
||||
Handle<Object> element;
|
||||
if (!ReadObject().ToHandle(&element)) return MaybeHandle<JSArray>();
|
||||
// TODO(jbroman): Distinguish between undefined and a hole.
|
||||
if (element->IsUndefined(isolate_)) continue;
|
||||
|
||||
// Serialization versions less than 11 encode the hole the same as
|
||||
// undefined. For consistency with previous behavior, store these as the
|
||||
// hole. Past version 11, undefined means undefined.
|
||||
if (version_ < 11 && element->IsUndefined(isolate_)) continue;
|
||||
|
||||
elements->set(i, *element);
|
||||
}
|
||||
|
||||
@ -1341,7 +1404,7 @@ MaybeHandle<JSValue> ValueDeserializer::ReadJSValue(SerializationTag tag) {
|
||||
}
|
||||
case SerializationTag::kStringObject: {
|
||||
Handle<String> string;
|
||||
if (!ReadUtf8String().ToHandle(&string)) return MaybeHandle<JSValue>();
|
||||
if (!ReadString().ToHandle(&string)) return MaybeHandle<JSValue>();
|
||||
value = Handle<JSValue>::cast(isolate_->factory()->NewJSObject(
|
||||
isolate_->string_function(), pretenure_));
|
||||
value->set_value(*string);
|
||||
@ -1360,7 +1423,7 @@ MaybeHandle<JSRegExp> ValueDeserializer::ReadJSRegExp() {
|
||||
Handle<String> pattern;
|
||||
uint32_t raw_flags;
|
||||
Handle<JSRegExp> regexp;
|
||||
if (!ReadUtf8String().ToHandle(&pattern) ||
|
||||
if (!ReadString().ToHandle(&pattern) ||
|
||||
!ReadVarint<uint32_t>().To(&raw_flags) ||
|
||||
!JSRegExp::New(pattern, static_cast<JSRegExp::Flags>(raw_flags))
|
||||
.ToHandle(®exp)) {
|
||||
@ -1564,11 +1627,16 @@ MaybeHandle<JSObject> ValueDeserializer::ReadWasmModule() {
|
||||
}
|
||||
|
||||
// If that fails, recompile.
|
||||
wasm::ErrorThrower thrower(isolate_, "ValueDeserializer::ReadWasmModule");
|
||||
return wasm::CreateModuleObjectFromBytes(
|
||||
isolate_, wire_bytes.begin(), wire_bytes.end(), &thrower,
|
||||
wasm::ModuleOrigin::kWasmOrigin, Handle<Script>::null(),
|
||||
Vector<const byte>::empty());
|
||||
MaybeHandle<JSObject> result;
|
||||
{
|
||||
wasm::ErrorThrower thrower(isolate_, "ValueDeserializer::ReadWasmModule");
|
||||
result = wasm::CreateModuleObjectFromBytes(
|
||||
isolate_, wire_bytes.begin(), wire_bytes.end(), &thrower,
|
||||
wasm::ModuleOrigin::kWasmOrigin, Handle<Script>::null(),
|
||||
Vector<const byte>::empty());
|
||||
}
|
||||
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, JSObject);
|
||||
return result;
|
||||
}
|
||||
|
||||
MaybeHandle<JSObject> ValueDeserializer::ReadHostObject() {
|
||||
@ -1602,6 +1670,10 @@ static void CommitProperties(Handle<JSObject> object, Handle<Map> map,
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsValidObjectKey(Handle<Object> value) {
|
||||
return value->IsName() || value->IsNumber();
|
||||
}
|
||||
|
||||
Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties(
|
||||
Handle<JSObject> object, SerializationTag end_tag,
|
||||
bool can_use_transitions) {
|
||||
@ -1637,7 +1709,9 @@ Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties(
|
||||
key = expected_key;
|
||||
target = TransitionArray::ExpectedTransitionTarget(map);
|
||||
} else {
|
||||
if (!ReadObject().ToHandle(&key)) return Nothing<uint32_t>();
|
||||
if (!ReadObject().ToHandle(&key) || !IsValidObjectKey(key)) {
|
||||
return Nothing<uint32_t>();
|
||||
}
|
||||
if (key->IsString()) {
|
||||
key =
|
||||
isolate_->factory()->InternalizeString(Handle<String>::cast(key));
|
||||
@ -1717,7 +1791,9 @@ Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties(
|
||||
}
|
||||
|
||||
Handle<Object> key;
|
||||
if (!ReadObject().ToHandle(&key)) return Nothing<uint32_t>();
|
||||
if (!ReadObject().ToHandle(&key) || !IsValidObjectKey(key)) {
|
||||
return Nothing<uint32_t>();
|
||||
}
|
||||
Handle<Object> value;
|
||||
if (!ReadObject().ToHandle(&value)) return Nothing<uint32_t>();
|
||||
|
||||
@ -1766,6 +1842,7 @@ static Maybe<bool> SetPropertiesFromKeyValuePairs(Isolate* isolate,
|
||||
uint32_t num_properties) {
|
||||
for (unsigned i = 0; i < 2 * num_properties; i += 2) {
|
||||
Handle<Object> key = data[i];
|
||||
if (!IsValidObjectKey(key)) return Nothing<bool>();
|
||||
Handle<Object> value = data[i + 1];
|
||||
bool success;
|
||||
LookupIterator it = LookupIterator::PropertyOrElement(
|
||||
@ -1779,6 +1856,20 @@ static Maybe<bool> SetPropertiesFromKeyValuePairs(Isolate* isolate,
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Throws a generic "deserialization failed" exception by default, unless a more
|
||||
// specific exception has already been thrown.
|
||||
void ThrowDeserializationExceptionIfNonePending(Isolate* isolate) {
|
||||
if (!isolate->has_pending_exception()) {
|
||||
isolate->Throw(*isolate->factory()->NewError(
|
||||
MessageTemplate::kDataCloneDeserializationError));
|
||||
}
|
||||
DCHECK(isolate->has_pending_exception());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MaybeHandle<Object>
|
||||
ValueDeserializer::ReadObjectUsingEntireBufferForLegacyFormat() {
|
||||
DCHECK_EQ(version_, 0u);
|
||||
@ -1811,7 +1902,7 @@ ValueDeserializer::ReadObjectUsingEntireBufferForLegacyFormat() {
|
||||
!SetPropertiesFromKeyValuePairs(
|
||||
isolate_, js_object, &stack[begin_properties], num_properties)
|
||||
.FromMaybe(false)) {
|
||||
DCHECK(isolate_->has_pending_exception());
|
||||
ThrowDeserializationExceptionIfNonePending(isolate_);
|
||||
return MaybeHandle<Object>();
|
||||
}
|
||||
|
||||
@ -1842,7 +1933,7 @@ ValueDeserializer::ReadObjectUsingEntireBufferForLegacyFormat() {
|
||||
!SetPropertiesFromKeyValuePairs(
|
||||
isolate_, js_array, &stack[begin_properties], num_properties)
|
||||
.FromMaybe(false)) {
|
||||
DCHECK(isolate_->has_pending_exception());
|
||||
ThrowDeserializationExceptionIfNonePending(isolate_);
|
||||
return MaybeHandle<Object>();
|
||||
}
|
||||
|
||||
|
23
deps/v8/src/value-serializer.h
vendored
23
deps/v8/src/value-serializer.h
vendored
@ -84,9 +84,18 @@ class ValueSerializer {
|
||||
void WriteRawBytes(const void* source, size_t length);
|
||||
void WriteDouble(double value);
|
||||
|
||||
/*
|
||||
* Indicate whether to treat ArrayBufferView objects as host objects,
|
||||
* i.e. pass them to Delegate::WriteHostObject. This should not be
|
||||
* called when no Delegate was passed.
|
||||
*
|
||||
* The default is not to treat ArrayBufferViews as host objects.
|
||||
*/
|
||||
void SetTreatArrayBufferViewsAsHostObjects(bool mode);
|
||||
|
||||
private:
|
||||
// Managing allocations of the internal buffer.
|
||||
void ExpandBuffer(size_t required_capacity);
|
||||
Maybe<bool> ExpandBuffer(size_t required_capacity);
|
||||
|
||||
// Writing the wire format.
|
||||
void WriteTag(SerializationTag tag);
|
||||
@ -96,7 +105,7 @@ class ValueSerializer {
|
||||
void WriteZigZag(T value);
|
||||
void WriteOneByteString(Vector<const uint8_t> chars);
|
||||
void WriteTwoByteString(Vector<const uc16> chars);
|
||||
uint8_t* ReserveRawBytes(size_t bytes);
|
||||
Maybe<uint8_t*> ReserveRawBytes(size_t bytes);
|
||||
|
||||
// Writing V8 objects of various kinds.
|
||||
void WriteOddball(Oddball* oddball);
|
||||
@ -134,11 +143,15 @@ class ValueSerializer {
|
||||
V8_NOINLINE void ThrowDataCloneError(MessageTemplate::Template template_index,
|
||||
Handle<Object> arg0);
|
||||
|
||||
Maybe<bool> ThrowIfOutOfMemory();
|
||||
|
||||
Isolate* const isolate_;
|
||||
v8::ValueSerializer::Delegate* const delegate_;
|
||||
bool treat_array_buffer_views_as_host_objects_ = false;
|
||||
uint8_t* buffer_ = nullptr;
|
||||
size_t buffer_size_ = 0;
|
||||
size_t buffer_capacity_ = 0;
|
||||
bool out_of_memory_ = false;
|
||||
Zone zone_;
|
||||
|
||||
// To avoid extra lookups in the identity map, ID+1 is actually stored in the
|
||||
@ -226,9 +239,15 @@ class ValueDeserializer {
|
||||
// "stack machine".
|
||||
MaybeHandle<Object> ReadObjectInternal() WARN_UNUSED_RESULT;
|
||||
|
||||
// Reads a string intended to be part of a more complicated object.
|
||||
// Before v12, these are UTF-8 strings. After, they can be any encoding
|
||||
// permissible for a string (with the relevant tag).
|
||||
MaybeHandle<String> ReadString() WARN_UNUSED_RESULT;
|
||||
|
||||
// Reading V8 objects of specific kinds.
|
||||
// The tag is assumed to have already been read.
|
||||
MaybeHandle<String> ReadUtf8String() WARN_UNUSED_RESULT;
|
||||
MaybeHandle<String> ReadOneByteString() WARN_UNUSED_RESULT;
|
||||
MaybeHandle<String> ReadTwoByteString() WARN_UNUSED_RESULT;
|
||||
MaybeHandle<JSObject> ReadJSObject() WARN_UNUSED_RESULT;
|
||||
MaybeHandle<JSArray> ReadSparseJSArray() WARN_UNUSED_RESULT;
|
||||
|
@ -28,20 +28,19 @@
|
||||
// Flags: --harmony-sharedarraybuffer
|
||||
|
||||
if (this.Worker) {
|
||||
|
||||
(function TestTransfer() {
|
||||
var workerScript =
|
||||
`onmessage = function(m) {
|
||||
var sab = m;
|
||||
var ta = new Uint32Array(sab);
|
||||
if (sab.byteLength !== 16) {
|
||||
throw new Error('SharedArrayBuffer transfer byteLength');
|
||||
}
|
||||
for (var i = 0; i < 4; ++i) {
|
||||
if (ta[i] !== i) {
|
||||
throw new Error('SharedArrayBuffer transfer value ' + i);
|
||||
}
|
||||
}
|
||||
var sab = m;
|
||||
var ta = new Uint32Array(sab);
|
||||
if (sab.byteLength !== 16) {
|
||||
throw new Error('SharedArrayBuffer transfer byteLength');
|
||||
}
|
||||
for (var i = 0; i < 4; ++i) {
|
||||
if (ta[i] !== i) {
|
||||
throw new Error('SharedArrayBuffer transfer value ' + i);
|
||||
}
|
||||
}
|
||||
// Atomically update ta[0]
|
||||
Atomics.store(ta, 0, 100);
|
||||
};`;
|
||||
@ -55,7 +54,7 @@ if (this.Worker) {
|
||||
}
|
||||
|
||||
// Transfer SharedArrayBuffer
|
||||
w.postMessage(sab, [sab]);
|
||||
w.postMessage(sab);
|
||||
assertEquals(16, sab.byteLength); // ArrayBuffer should not be neutered.
|
||||
|
||||
// Spinwait for the worker to update ta[0]
|
||||
@ -86,7 +85,7 @@ if (this.Worker) {
|
||||
var workers = [];
|
||||
for (id = 0; id < 4; ++id) {
|
||||
workers[id] = new Worker(workerScript);
|
||||
workers[id].postMessage({sab: sab, id: id}, [sab]);
|
||||
workers[id].postMessage({sab: sab, id: id});
|
||||
}
|
||||
|
||||
// Spinwait for each worker to update ta[id]
|
||||
|
16
deps/v8/test/mjsunit/d8-worker.js
vendored
16
deps/v8/test/mjsunit/d8-worker.js
vendored
@ -61,8 +61,8 @@ var workerScript =
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
if (JSON.stringify(m) !== \"{'a':1,'b':2.5,'c':'three'}\")
|
||||
throw new Error('Object');
|
||||
if (JSON.stringify(m) !== '{"a":1,"b":2.5,"c":"three"}')
|
||||
throw new Error('Object' + JSON.stringify(m));
|
||||
break;
|
||||
case 8:
|
||||
var ab = m;
|
||||
@ -88,7 +88,6 @@ var workerScript =
|
||||
}
|
||||
};`;
|
||||
|
||||
|
||||
if (this.Worker) {
|
||||
function createArrayBuffer(byteLength) {
|
||||
var ab = new ArrayBuffer(byteLength);
|
||||
@ -111,6 +110,17 @@ if (this.Worker) {
|
||||
w.postMessage([4, true, "bye"]);
|
||||
w.postMessage({a: 1, b: 2.5, c: "three"});
|
||||
|
||||
// Test bad get in transfer list.
|
||||
var transferList = [undefined];
|
||||
Object.defineProperty(transferList, '0', {
|
||||
get: function() {
|
||||
throw 'unexpected!';
|
||||
}
|
||||
});
|
||||
assertThrows(function() {
|
||||
w.postMessage([], transferList);
|
||||
});
|
||||
|
||||
// Clone ArrayBuffer
|
||||
var ab1 = createArrayBuffer(16);
|
||||
w.postMessage(ab1);
|
||||
|
8
deps/v8/test/mjsunit/harmony/futex.js
vendored
8
deps/v8/test/mjsunit/harmony/futex.js
vendored
@ -111,7 +111,7 @@ if (this.Worker) {
|
||||
};`;
|
||||
|
||||
var worker = new Worker(workerScript);
|
||||
worker.postMessage({sab: sab, offset: offset}, [sab]);
|
||||
worker.postMessage({sab: sab, offset: offset});
|
||||
|
||||
// Spin until the worker is waiting on the futex.
|
||||
while (%AtomicsNumWaitersForTesting(i32a, 0) != 1) {}
|
||||
@ -123,7 +123,7 @@ if (this.Worker) {
|
||||
var worker2 = new Worker(workerScript);
|
||||
var offset = 8;
|
||||
var i32a2 = new Int32Array(sab, offset);
|
||||
worker2.postMessage({sab: sab, offset: offset}, [sab]);
|
||||
worker2.postMessage({sab: sab, offset: offset});
|
||||
|
||||
// Spin until the worker is waiting on the futex.
|
||||
while (%AtomicsNumWaitersForTesting(i32a2, 0) != 1) {}
|
||||
@ -135,7 +135,7 @@ if (this.Worker) {
|
||||
// the real address is the same.
|
||||
var worker3 = new Worker(workerScript);
|
||||
i32a2 = new Int32Array(sab, 4);
|
||||
worker3.postMessage({sab: sab, offset: 8}, [sab]);
|
||||
worker3.postMessage({sab: sab, offset: 8});
|
||||
|
||||
// Spin until the worker is waiting on the futex.
|
||||
while (%AtomicsNumWaitersForTesting(i32a2, 1) != 1) {}
|
||||
@ -181,7 +181,7 @@ if (this.Worker) {
|
||||
var workers = [];
|
||||
for (id = 0; id < 4; id++) {
|
||||
workers[id] = new Worker(workerScript);
|
||||
workers[id].postMessage({sab: sab, id: id}, [sab]);
|
||||
workers[id].postMessage({sab: sab, id: id});
|
||||
}
|
||||
|
||||
// Spin until all workers are waiting on the futex.
|
||||
|
@ -4,10 +4,15 @@
|
||||
|
||||
if (this.Worker) {
|
||||
var __v_7 = new Worker('onmessage = function() {};');
|
||||
var e;
|
||||
try {
|
||||
var ab = new ArrayBuffer(2147483648);
|
||||
// If creating the ArrayBuffer succeeded, then postMessage should fail.
|
||||
assertThrows(function() { __v_7.postMessage(ab); });
|
||||
try {
|
||||
__v_7.postMessage(ab);
|
||||
} catch (e) {
|
||||
// postMessage failed, should be a DataCloneError message.
|
||||
assertContains('cloned', e.message);
|
||||
}
|
||||
} catch (e) {
|
||||
// Creating the ArrayBuffer failed.
|
||||
assertInstanceof(e, RangeError);
|
||||
|
148
deps/v8/test/unittests/value-serializer-unittest.cc
vendored
148
deps/v8/test/unittests/value-serializer-unittest.cc
vendored
@ -252,6 +252,13 @@ class ValueSerializerTest : public TestWithIsolate {
|
||||
.ToLocalChecked();
|
||||
}
|
||||
|
||||
Local<Object> NewDummyUint8Array() {
|
||||
static uint8_t data[] = {4, 5, 6};
|
||||
Local<ArrayBuffer> ab =
|
||||
ArrayBuffer::New(isolate(), static_cast<void*>(data), sizeof(data));
|
||||
return Uint8Array::New(ab, 0, sizeof(data));
|
||||
}
|
||||
|
||||
private:
|
||||
Local<Context> serialization_context_;
|
||||
Local<Context> deserialization_context_;
|
||||
@ -455,6 +462,24 @@ TEST_F(ValueSerializerTest, DecodeString) {
|
||||
EXPECT_EQ(kEmojiString, Utf8Value(value));
|
||||
});
|
||||
|
||||
// And from Latin-1 (for the ones that fit).
|
||||
DecodeTest({0xff, 0x0a, 0x22, 0x00}, [](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsString());
|
||||
EXPECT_EQ(0, String::Cast(*value)->Length());
|
||||
});
|
||||
DecodeTest({0xff, 0x0a, 0x22, 0x05, 'H', 'e', 'l', 'l', 'o'},
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsString());
|
||||
EXPECT_EQ(5, String::Cast(*value)->Length());
|
||||
EXPECT_EQ(kHelloString, Utf8Value(value));
|
||||
});
|
||||
DecodeTest({0xff, 0x0a, 0x22, 0x06, 'Q', 'u', 0xe9, 'b', 'e', 'c'},
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsString());
|
||||
EXPECT_EQ(6, String::Cast(*value)->Length());
|
||||
EXPECT_EQ(kQuebecString, Utf8Value(value));
|
||||
});
|
||||
|
||||
// And from two-byte strings (endianness dependent).
|
||||
#if defined(V8_TARGET_LITTLE_ENDIAN)
|
||||
DecodeTest({0xff, 0x09, 0x63, 0x00},
|
||||
@ -489,6 +514,8 @@ TEST_F(ValueSerializerTest, DecodeString) {
|
||||
TEST_F(ValueSerializerTest, DecodeInvalidString) {
|
||||
// UTF-8 string with too few bytes available.
|
||||
InvalidDecodeTest({0xff, 0x09, 0x53, 0x10, 'v', '8'});
|
||||
// One-byte string with too few bytes available.
|
||||
InvalidDecodeTest({0xff, 0x0a, 0x22, 0x10, 'v', '8'});
|
||||
#if defined(V8_TARGET_LITTLE_ENDIAN)
|
||||
// Two-byte string with too few bytes available.
|
||||
InvalidDecodeTest({0xff, 0x09, 0x63, 0x10, 'v', '\0', '8', '\0'});
|
||||
@ -513,12 +540,16 @@ TEST_F(ValueSerializerTest, EncodeTwoByteStringUsesPadding) {
|
||||
return StringFromUtf8(string.c_str());
|
||||
},
|
||||
[](const std::vector<uint8_t>& data) {
|
||||
// This is a sufficient but not necessary condition to be aligned.
|
||||
// Note that the third byte (0x00) is padding.
|
||||
const uint8_t expected_prefix[] = {0xff, 0x09, 0x00, 0x63, 0x94, 0x03};
|
||||
ASSERT_GT(data.size(), sizeof(expected_prefix) / sizeof(uint8_t));
|
||||
// This is a sufficient but not necessary condition. This test assumes
|
||||
// that the wire format version is one byte long, but is flexible to
|
||||
// what that value may be.
|
||||
const uint8_t expected_prefix[] = {0x00, 0x63, 0x94, 0x03};
|
||||
ASSERT_GT(data.size(), sizeof(expected_prefix) + 2);
|
||||
EXPECT_EQ(0xff, data[0]);
|
||||
EXPECT_GE(data[1], 0x09);
|
||||
EXPECT_LE(data[1], 0x7f);
|
||||
EXPECT_TRUE(std::equal(std::begin(expected_prefix),
|
||||
std::end(expected_prefix), data.begin()));
|
||||
std::end(expected_prefix), data.begin() + 2));
|
||||
});
|
||||
}
|
||||
|
||||
@ -646,6 +677,14 @@ TEST_F(ValueSerializerTest, DecodeDictionaryObject) {
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTest, InvalidDecodeObjectWithInvalidKeyType) {
|
||||
// Objects which would need conversion to string shouldn't be present as
|
||||
// object keys. The serializer would have obtained them from the own property
|
||||
// keys list, which should only contain names and indices.
|
||||
InvalidDecodeTest(
|
||||
{0xff, 0x09, 0x6f, 0x61, 0x00, 0x40, 0x00, 0x00, 0x7b, 0x01});
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTest, RoundTripOnlyOwnEnumerableStringKeys) {
|
||||
// Only "own" properties should be serialized, not ones on the prototype.
|
||||
RoundTripTest("(() => { var x = {}; x.__proto__ = {a: 4}; return x; })()",
|
||||
@ -1209,6 +1248,36 @@ TEST_F(ValueSerializerTest, DecodeSparseArrayVersion0) {
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTest, RoundTripDenseArrayContainingUndefined) {
|
||||
// In previous serialization versions, this would be interpreted as an absent
|
||||
// property.
|
||||
RoundTripTest("[undefined]", [this](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsArray());
|
||||
EXPECT_EQ(1u, Array::Cast(*value)->Length());
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty(0)"));
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === undefined"));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTest, DecodeDenseArrayContainingUndefined) {
|
||||
// In previous versions, "undefined" in a dense array signified absence of the
|
||||
// element (for compatibility). In new versions, it has a separate encoding.
|
||||
DecodeTest({0xff, 0x09, 0x41, 0x01, 0x5f, 0x24, 0x00, 0x01},
|
||||
[this](Local<Value> value) {
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("!(0 in result)"));
|
||||
});
|
||||
DecodeTest(
|
||||
{0xff, 0x0b, 0x41, 0x01, 0x5f, 0x24, 0x00, 0x01},
|
||||
[this](Local<Value> value) {
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("0 in result"));
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === undefined"));
|
||||
});
|
||||
DecodeTest({0xff, 0x0b, 0x41, 0x01, 0x2d, 0x24, 0x00, 0x01},
|
||||
[this](Local<Value> value) {
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("!(0 in result)"));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTest, RoundTripDate) {
|
||||
RoundTripTest("new Date(1e6)", [this](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsDate());
|
||||
@ -1435,6 +1504,16 @@ TEST_F(ValueSerializerTest, DecodeValueObjects) {
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof String"));
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
|
||||
});
|
||||
|
||||
// String object containing a Latin-1 string.
|
||||
DecodeTest({0xff, 0x0c, 0x73, 0x22, 0x06, 'Q', 'u', 0xe9, 'b', 'e', 'c'},
|
||||
[this](Local<Value> value) {
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool(
|
||||
"Object.getPrototypeOf(result) === String.prototype"));
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool(
|
||||
"result.valueOf() === 'Qu\\xe9bec'"));
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result.length === 6"));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTest, RoundTripRegExp) {
|
||||
@ -1494,6 +1573,15 @@ TEST_F(ValueSerializerTest, DecodeRegExp) {
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof RegExp"));
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
|
||||
});
|
||||
|
||||
// RegExp containing a Latin-1 string.
|
||||
DecodeTest(
|
||||
{0xff, 0x0c, 0x52, 0x22, 0x06, 'Q', 'u', 0xe9, 'b', 'e', 'c', 0x02},
|
||||
[this](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsRegExp());
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool(
|
||||
"result.toString() === '/Qu\\xe9bec/i'"));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTest, RoundTripMap) {
|
||||
@ -2270,8 +2358,9 @@ class ValueSerializerTestWithHostObject : public ValueSerializerTest {
|
||||
friend class DeserializerDelegate;
|
||||
};
|
||||
|
||||
// This is a tag that's not used in V8.
|
||||
const uint8_t ValueSerializerTestWithHostObject::kExampleHostObjectTag = '+';
|
||||
// This is a tag that is used in V8. Using this ensures that we have separate
|
||||
// tag namespaces.
|
||||
const uint8_t ValueSerializerTestWithHostObject::kExampleHostObjectTag = 'T';
|
||||
|
||||
TEST_F(ValueSerializerTestWithHostObject, RoundTripUint32) {
|
||||
// The host can serialize data as uint32_t.
|
||||
@ -2444,6 +2533,51 @@ TEST_F(ValueSerializerTestWithHostObject, RoundTripSameObject) {
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTestWithHostObject, DecodeSimpleHostObject) {
|
||||
EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
|
||||
.WillRepeatedly(Invoke([this](Isolate*) {
|
||||
EXPECT_TRUE(ReadExampleHostObjectTag());
|
||||
return NewHostObject(deserialization_context(), 0, nullptr);
|
||||
}));
|
||||
DecodeTest(
|
||||
{0xff, 0x0d, 0x5c, kExampleHostObjectTag}, [this](Local<Value> value) {
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool(
|
||||
"Object.getPrototypeOf(result) === ExampleHostObject.prototype"));
|
||||
});
|
||||
}
|
||||
|
||||
class ValueSerializerTestWithHostArrayBufferView
|
||||
: public ValueSerializerTestWithHostObject {
|
||||
protected:
|
||||
void BeforeEncode(ValueSerializer* serializer) override {
|
||||
ValueSerializerTestWithHostObject::BeforeEncode(serializer);
|
||||
serializer_->SetTreatArrayBufferViewsAsHostObjects(true);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ValueSerializerTestWithHostArrayBufferView, RoundTripUint8ArrayInput) {
|
||||
EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
|
||||
.WillOnce(Invoke([this](Isolate*, Local<Object> object) {
|
||||
EXPECT_TRUE(object->IsUint8Array());
|
||||
WriteExampleHostObjectTag();
|
||||
return Just(true);
|
||||
}));
|
||||
EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
|
||||
.WillOnce(Invoke([this](Isolate*) {
|
||||
EXPECT_TRUE(ReadExampleHostObjectTag());
|
||||
return NewDummyUint8Array();
|
||||
}));
|
||||
RoundTripTest(
|
||||
"({ a: new Uint8Array([1, 2, 3]), get b() { return this.a; }})",
|
||||
[this](Local<Value> value) {
|
||||
EXPECT_TRUE(
|
||||
EvaluateScriptForResultBool("result.a instanceof Uint8Array"));
|
||||
EXPECT_TRUE(
|
||||
EvaluateScriptForResultBool("result.a.toString() === '4,5,6'"));
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
|
||||
});
|
||||
}
|
||||
|
||||
// It's expected that WebAssembly has more exhaustive tests elsewhere; this
|
||||
// mostly checks that the logic to embed it in structured clone serialization
|
||||
// works correctly.
|
||||
|
Loading…
x
Reference in New Issue
Block a user