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:
parent
2d546908c3
commit
85bc4ef6c2
@ -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) ||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user