Serialize native errors

Make native errors serializable.

The implementation is mostly straightforward, but there is one
exception: the stack property. Although the property is not specified,
the spec for error cloning asks us to preserve the property if
possible. This implementation serializes the property only when it is
a string, and otherwise ignores it.

Spec: https://github.com/whatwg/html/pull/4665
Intent-to-Ship: <TBD>

Bug: chromium:970079
Change-Id: I7f36b8b4fc5dff22d726d849ccfb9748d0888365
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1649257
Commit-Queue: Yutaka Hirano <yhirano@chromium.org>
Reviewed-by: Simon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62584}
This commit is contained in:
Yutaka Hirano 2019-07-09 13:52:07 +09:00 committed by Commit Bot
parent 2d546908c3
commit 85bc4ef6c2
3 changed files with 221 additions and 0 deletions

View File

@ -22,6 +22,7 @@
#include "src/objects/objects-inl.h"
#include "src/objects/oddball-inl.h"
#include "src/objects/ordered-hash-table-inl.h"
#include "src/objects/property-descriptor.h"
#include "src/objects/smi.h"
#include "src/objects/transitions-inl.h"
#include "src/snapshot/code-serializer.h"
@ -161,6 +162,9 @@ enum class SerializationTag : uint8_t {
// A transferred WebAssembly.Memory object. maximumPages:int32_t, then by
// SharedArrayBuffer tag and its data.
kWasmMemoryTransfer = 'm',
// A list of (subtag: ErrorTag, [subtag dependent data]). See ErrorTag for
// details.
kError = 'e',
};
namespace {
@ -184,6 +188,28 @@ enum class WasmEncodingTag : uint8_t {
kRawBytes = 'y',
};
// Sub-tags only meaningful for error serialization.
enum class ErrorTag : uint8_t {
// The error is a EvalError. No accompanying data.
kEvalErrorPrototype = 'E',
// The error is a RangeError. No accompanying data.
kRangeErrorPrototype = 'R',
// The error is a ReferenceError. No accompanying data.
kReferenceErrorPrototype = 'F',
// The error is a SyntaxError. No accompanying data.
kSyntaxErrorPrototype = 'S',
// The error is a TypeError. No accompanying data.
kTypeErrorPrototype = 'T',
// The error is a URIError. No accompanying data.
kUriErrorPrototype = 'U',
// Followed by message: string.
kMessage = 'm',
// Followed by stack: string.
kStack = 's',
// The end of this error information.
kEnd = '.',
};
} // namespace
ValueSerializer::ValueSerializer(Isolate* isolate,
@ -520,6 +546,8 @@ Maybe<bool> ValueSerializer::WriteJSReceiver(Handle<JSReceiver> receiver) {
case JS_TYPED_ARRAY_TYPE:
case JS_DATA_VIEW_TYPE:
return WriteJSArrayBufferView(JSArrayBufferView::cast(*receiver));
case JS_ERROR_TYPE:
return WriteJSError(Handle<JSObject>::cast(receiver));
case WASM_MODULE_TYPE: {
auto enabled_features = wasm::WasmFeaturesFromIsolate(isolate_);
if (!FLAG_wasm_disable_structured_cloning || enabled_features.threads) {
@ -876,6 +904,60 @@ Maybe<bool> ValueSerializer::WriteJSArrayBufferView(JSArrayBufferView view) {
return ThrowIfOutOfMemory();
}
Maybe<bool> ValueSerializer::WriteJSError(Handle<JSObject> error) {
Handle<Object> stack;
PropertyDescriptor message_desc;
Maybe<bool> message_found = JSReceiver::GetOwnPropertyDescriptor(
isolate_, error, isolate_->factory()->message_string(), &message_desc);
MAYBE_RETURN(message_found, Nothing<bool>());
WriteTag(SerializationTag::kError);
Handle<HeapObject> prototype;
if (!JSObject::GetPrototype(isolate_, error).ToHandle(&prototype)) {
return Nothing<bool>();
}
if (*prototype == isolate_->eval_error_function()->prototype()) {
WriteVarint(static_cast<uint8_t>(ErrorTag::kEvalErrorPrototype));
} else if (*prototype == isolate_->range_error_function()->prototype()) {
WriteVarint(static_cast<uint8_t>(ErrorTag::kRangeErrorPrototype));
} else if (*prototype == isolate_->reference_error_function()->prototype()) {
WriteVarint(static_cast<uint8_t>(ErrorTag::kReferenceErrorPrototype));
} else if (*prototype == isolate_->syntax_error_function()->prototype()) {
WriteVarint(static_cast<uint8_t>(ErrorTag::kSyntaxErrorPrototype));
} else if (*prototype == isolate_->type_error_function()->prototype()) {
WriteVarint(static_cast<uint8_t>(ErrorTag::kTypeErrorPrototype));
} else if (*prototype == isolate_->uri_error_function()->prototype()) {
WriteVarint(static_cast<uint8_t>(ErrorTag::kUriErrorPrototype));
} else {
// The default prototype in the deserialization side is Error.prototype, so
// we don't have to do anything here.
}
if (message_found.FromJust() &&
PropertyDescriptor::IsDataDescriptor(&message_desc)) {
Handle<String> message;
if (!Object::ToString(isolate_, message_desc.value()).ToHandle(&message)) {
return Nothing<bool>();
}
WriteVarint(static_cast<uint8_t>(ErrorTag::kMessage));
WriteString(message);
}
if (!Object::GetProperty(isolate_, error, isolate_->factory()->stack_string())
.ToHandle(&stack)) {
return Nothing<bool>();
}
if (stack->IsString()) {
WriteVarint(static_cast<uint8_t>(ErrorTag::kStack));
WriteString(Handle<String>::cast(stack));
}
WriteVarint(static_cast<uint8_t>(ErrorTag::kEnd));
return ThrowIfOutOfMemory();
}
Maybe<bool> ValueSerializer::WriteWasmModule(Handle<WasmModuleObject> object) {
if (delegate_ != nullptr) {
// TODO(titzer): introduce a Utils::ToLocal for WasmModuleObject.
@ -1258,6 +1340,8 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() {
const bool is_shared = true;
return ReadJSArrayBuffer(is_shared);
}
case SerializationTag::kError:
return ReadJSError();
case SerializationTag::kWasmModule:
return ReadWasmModule();
case SerializationTag::kWasmModuleTransfer:
@ -1773,6 +1857,78 @@ MaybeHandle<JSArrayBufferView> ValueDeserializer::ReadJSArrayBufferView(
return typed_array;
}
MaybeHandle<Object> ValueDeserializer::ReadJSError() {
Handle<Object> message = isolate_->factory()->undefined_value();
Handle<Object> stack = isolate_->factory()->undefined_value();
Handle<Object> no_caller;
auto constructor = isolate_->error_function();
bool done = false;
while (!done) {
uint8_t tag;
if (!ReadVarint<uint8_t>().To(&tag)) {
return MaybeHandle<JSObject>();
}
switch (static_cast<ErrorTag>(tag)) {
case ErrorTag::kEvalErrorPrototype:
constructor = isolate_->eval_error_function();
break;
case ErrorTag::kRangeErrorPrototype:
constructor = isolate_->range_error_function();
break;
case ErrorTag::kReferenceErrorPrototype:
constructor = isolate_->reference_error_function();
break;
case ErrorTag::kSyntaxErrorPrototype:
constructor = isolate_->syntax_error_function();
break;
case ErrorTag::kTypeErrorPrototype:
constructor = isolate_->type_error_function();
break;
case ErrorTag::kUriErrorPrototype:
constructor = isolate_->uri_error_function();
break;
case ErrorTag::kMessage: {
Handle<String> message_string;
if (!ReadString().ToHandle(&message_string)) {
return MaybeHandle<JSObject>();
}
message = message_string;
break;
}
case ErrorTag::kStack: {
Handle<String> stack_string;
if (!ReadString().ToHandle(&stack_string)) {
return MaybeHandle<JSObject>();
}
stack = stack_string;
break;
}
case ErrorTag::kEnd:
done = true;
break;
default:
return MaybeHandle<JSObject>();
}
}
Handle<Object> error;
if (!ErrorUtils::Construct(isolate_, constructor, constructor, message,
SKIP_NONE, no_caller,
ErrorUtils::StackTraceCollection::kNone)
.ToHandle(&error)) {
return MaybeHandle<Object>();
}
if (Object::SetProperty(
isolate_, error, isolate_->factory()->stack_trace_symbol(), stack,
StoreOrigin::kMaybeKeyed, Just(ShouldThrow::kThrowOnError))
.is_null()) {
return MaybeHandle<Object>();
}
return error;
}
MaybeHandle<JSObject> ValueDeserializer::ReadWasmModuleTransfer() {
auto enabled_features = wasm::WasmFeaturesFromIsolate(isolate_);
if ((FLAG_wasm_disable_structured_cloning && !enabled_features.threads) ||

View File

@ -128,6 +128,7 @@ class ValueSerializer {
Maybe<bool> WriteJSArrayBuffer(Handle<JSArrayBuffer> array_buffer)
V8_WARN_UNUSED_RESULT;
Maybe<bool> WriteJSArrayBufferView(JSArrayBufferView array_buffer);
Maybe<bool> WriteJSError(Handle<JSObject> error) V8_WARN_UNUSED_RESULT;
Maybe<bool> WriteWasmModule(Handle<WasmModuleObject> object)
V8_WARN_UNUSED_RESULT;
Maybe<bool> WriteWasmMemory(Handle<WasmMemoryObject> object)
@ -276,6 +277,7 @@ class ValueDeserializer {
V8_WARN_UNUSED_RESULT;
MaybeHandle<JSArrayBufferView> ReadJSArrayBufferView(
Handle<JSArrayBuffer> buffer) V8_WARN_UNUSED_RESULT;
MaybeHandle<Object> ReadJSError() V8_WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadWasmModule() V8_WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadWasmModuleTransfer() V8_WARN_UNUSED_RESULT;
MaybeHandle<WasmMemoryObject> ReadWasmMemory() V8_WARN_UNUSED_RESULT;

View File

@ -2885,5 +2885,68 @@ TEST_F(ValueSerializerTestWithLimitedMemory, FailIfNoMemoryInWriteHostObject) {
EXPECT_TRUE(EvaluateScriptForInput("gotA")->IsFalse());
}
// We only have basic tests and tests for .stack here, because we have more
// comprehensive tests as web platform tests.
TEST_F(ValueSerializerTest, RoundTripError) {
Local<Value> value = RoundTripTest("Error('hello')");
ASSERT_TRUE(value->IsObject());
Local<Object> error = value.As<Object>();
Local<Value> name;
Local<Value> message;
{
Context::Scope scope(deserialization_context());
EXPECT_EQ(error->GetPrototype(), Exception::Error(String::Empty(isolate()))
.As<Object>()
->GetPrototype());
}
ASSERT_TRUE(error->Get(deserialization_context(), StringFromUtf8("name"))
.ToLocal(&name));
ASSERT_TRUE(name->IsString());
EXPECT_EQ(Utf8Value(name), "Error");
ASSERT_TRUE(error->Get(deserialization_context(), StringFromUtf8("message"))
.ToLocal(&message));
ASSERT_TRUE(message->IsString());
EXPECT_EQ(Utf8Value(message), "hello");
}
TEST_F(ValueSerializerTest, DefaultErrorStack) {
Local<Value> value =
RoundTripTest("function hkalkcow() { return Error(); } hkalkcow();");
ASSERT_TRUE(value->IsObject());
Local<Object> error = value.As<Object>();
Local<Value> stack;
ASSERT_TRUE(error->Get(deserialization_context(), StringFromUtf8("stack"))
.ToLocal(&stack));
ASSERT_TRUE(stack->IsString());
EXPECT_NE(Utf8Value(stack).find("hkalkcow"), std::string::npos);
}
TEST_F(ValueSerializerTest, ModifiedErrorStack) {
Local<Value> value = RoundTripTest("let e = Error(); e.stack = 'hello'; e");
ASSERT_TRUE(value->IsObject());
Local<Object> error = value.As<Object>();
Local<Value> stack;
ASSERT_TRUE(error->Get(deserialization_context(), StringFromUtf8("stack"))
.ToLocal(&stack));
ASSERT_TRUE(stack->IsString());
EXPECT_EQ(Utf8Value(stack), "hello");
}
TEST_F(ValueSerializerTest, NonStringErrorStack) {
Local<Value> value = RoundTripTest("let e = Error(); e.stack = 17; e");
ASSERT_TRUE(value->IsObject());
Local<Object> error = value.As<Object>();
Local<Value> stack;
ASSERT_TRUE(error->Get(deserialization_context(), StringFromUtf8("stack"))
.ToLocal(&stack));
EXPECT_TRUE(stack->IsUndefined());
}
} // namespace
} // namespace v8