Commit 8f8ae4f8 authored by Yutaka Hirano's avatar Yutaka Hirano Committed by Commit Bot

Reland: Serialize native errors

This is a reland of https://crrev.com/c/v8/v8/+/1649257. The original
change was reverted because it conflicted with a blink-side serialization
tag.

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: https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/f8JngIi8qYs

Bug: chromium:970079
Change-Id: Ic1ff07be2c5be415bfb564fa3975bc1a55a06a72
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1692366Reviewed-by: 's avatarSimon Zünd <szuend@chromium.org>
Commit-Queue: Yutaka Hirano <yhirano@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62607}
parent bf1ab278
......@@ -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 = 'r',
};
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) ||
......
......@@ -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;
......
......@@ -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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment