Commit 5a97e955 authored by jbroman's avatar jbroman Committed by Commit bot

Handle errors in v8::ValueDeserializer by throwing exceptions.

This restores the contract that all API methods that return Maybe<T> or
MaybeLocal<T> always throw an exception when they return nothing.

Since v8::ValueDeserializer::ReadHeader can now throw exceptions, it
needs a Local<Context> parameter so that it can set up execution state
(entering the context, etc.). The old method has been marked for
deprecation, but since this API is experimental I intend to remove it
as soon as I've removed the use from Blink.

value-serializer-unittest has been updated to expect an exception in
all decode failure cases.

BUG=chromium:148757,chromium:641964

Review-Url: https://codereview.chromium.org/2308053002
Cr-Commit-Position: refs/heads/master@{#39188}
parent cc1249b7
...@@ -1743,7 +1743,8 @@ class V8_EXPORT ValueDeserializer { ...@@ -1743,7 +1743,8 @@ class V8_EXPORT ValueDeserializer {
* Reads and validates a header (including the format version). * Reads and validates a header (including the format version).
* May, for example, reject an invalid or unsupported wire format. * May, for example, reject an invalid or unsupported wire format.
*/ */
V8_WARN_UNUSED_RESULT Maybe<bool> ReadHeader(); V8_WARN_UNUSED_RESULT Maybe<bool> ReadHeader(Local<Context> context);
V8_DEPRECATE_SOON("Use Local<Context> version", Maybe<bool> ReadHeader());
/* /*
* Deserializes a JavaScript value from the buffer. * Deserializes a JavaScript value from the buffer.
......
...@@ -2855,10 +2855,8 @@ Maybe<bool> ValueSerializer::WriteValue(Local<Context> context, ...@@ -2855,10 +2855,8 @@ Maybe<bool> ValueSerializer::WriteValue(Local<Context> context,
PREPARE_FOR_EXECUTION_PRIMITIVE(context, ValueSerializer, WriteValue, bool); PREPARE_FOR_EXECUTION_PRIMITIVE(context, ValueSerializer, WriteValue, bool);
i::Handle<i::Object> object = Utils::OpenHandle(*value); i::Handle<i::Object> object = Utils::OpenHandle(*value);
Maybe<bool> result = private_->serializer.WriteObject(object); Maybe<bool> result = private_->serializer.WriteObject(object);
if (result.IsNothing()) { has_pending_exception = result.IsNothing();
has_pending_exception = private_->isolate->has_pending_exception();
RETURN_ON_FAILED_EXECUTION_PRIMITIVE(bool); RETURN_ON_FAILED_EXECUTION_PRIMITIVE(bool);
}
return result; return result;
} }
...@@ -2883,6 +2881,7 @@ struct ValueDeserializer::PrivateData { ...@@ -2883,6 +2881,7 @@ struct ValueDeserializer::PrivateData {
: isolate(i), deserializer(i, data) {} : isolate(i), deserializer(i, data) {}
i::Isolate* isolate; i::Isolate* isolate;
i::ValueDeserializer deserializer; i::ValueDeserializer deserializer;
bool has_aborted = false;
bool supports_legacy_wire_format = false; bool supports_legacy_wire_format = false;
}; };
...@@ -2893,38 +2892,61 @@ ValueDeserializer::ValueDeserializer(Isolate* isolate, const uint8_t* data, ...@@ -2893,38 +2892,61 @@ ValueDeserializer::ValueDeserializer(Isolate* isolate, const uint8_t* data,
new PrivateData(reinterpret_cast<i::Isolate*>(isolate), new PrivateData(reinterpret_cast<i::Isolate*>(isolate),
i::Vector<const uint8_t>(data, static_cast<int>(size))); i::Vector<const uint8_t>(data, static_cast<int>(size)));
} else { } else {
private_ = nullptr; private_ = new PrivateData(reinterpret_cast<i::Isolate*>(isolate),
i::Vector<const uint8_t>(nullptr, 0));
private_->has_aborted = true;
} }
} }
ValueDeserializer::~ValueDeserializer() { delete private_; } ValueDeserializer::~ValueDeserializer() { delete private_; }
Maybe<bool> ValueDeserializer::ReadHeader() { Maybe<bool> ValueDeserializer::ReadHeader(Local<Context> context) {
PREPARE_FOR_EXECUTION_PRIMITIVE(context, ValueDeserializer, ReadHeader, bool);
// We could have aborted during the constructor.
// If so, ReadHeader is where we report it.
if (private_->has_aborted) {
isolate->Throw(*isolate->factory()->NewError(
i::MessageTemplate::kDataCloneDeserializationError));
has_pending_exception = true;
RETURN_ON_FAILED_EXECUTION_PRIMITIVE(bool);
}
bool read_header = false;
has_pending_exception = !private_->deserializer.ReadHeader().To(&read_header);
RETURN_ON_FAILED_EXECUTION_PRIMITIVE(bool);
DCHECK(read_header);
// TODO(jbroman): Today, all wire formats are "legacy". When a more supported // TODO(jbroman): Today, all wire formats are "legacy". When a more supported
// format is added, compare the version of the internal serializer to the // format is added, compare the version of the internal serializer to the
// minimum non-legacy version number. // minimum non-legacy version number.
if (!private_ || !private_->deserializer.ReadHeader().FromMaybe(false) || if (!private_->supports_legacy_wire_format) {
!private_->supports_legacy_wire_format) { isolate->Throw(*isolate->factory()->NewError(
delete private_; i::MessageTemplate::kDataCloneDeserializationVersionError));
private_ = nullptr; has_pending_exception = true;
return Nothing<bool>(); RETURN_ON_FAILED_EXECUTION_PRIMITIVE(bool);
} }
return Just(true); return Just(true);
} }
Maybe<bool> ValueDeserializer::ReadHeader() {
Isolate* isolate = reinterpret_cast<Isolate*>(private_->isolate);
return ReadHeader(isolate->GetEnteredContext());
}
void ValueDeserializer::SetSupportsLegacyWireFormat( void ValueDeserializer::SetSupportsLegacyWireFormat(
bool supports_legacy_wire_format) { bool supports_legacy_wire_format) {
if (!private_) return;
private_->supports_legacy_wire_format = supports_legacy_wire_format; private_->supports_legacy_wire_format = supports_legacy_wire_format;
} }
uint32_t ValueDeserializer::GetWireFormatVersion() const { uint32_t ValueDeserializer::GetWireFormatVersion() const {
CHECK(private_); CHECK(!private_->has_aborted);
return private_->deserializer.GetWireFormatVersion(); return private_->deserializer.GetWireFormatVersion();
} }
MaybeLocal<Value> ValueDeserializer::ReadValue(Local<Context> context) { MaybeLocal<Value> ValueDeserializer::ReadValue(Local<Context> context) {
CHECK(private_); CHECK(!private_->has_aborted);
PREPARE_FOR_EXECUTION(context, ValueDeserializer, ReadValue, Value); PREPARE_FOR_EXECUTION(context, ValueDeserializer, ReadValue, Value);
i::MaybeHandle<i::Object> result; i::MaybeHandle<i::Object> result;
if (GetWireFormatVersion() > 0) { if (GetWireFormatVersion() > 0) {
...@@ -2934,22 +2956,21 @@ MaybeLocal<Value> ValueDeserializer::ReadValue(Local<Context> context) { ...@@ -2934,22 +2956,21 @@ MaybeLocal<Value> ValueDeserializer::ReadValue(Local<Context> context) {
private_->deserializer.ReadObjectUsingEntireBufferForLegacyFormat(); private_->deserializer.ReadObjectUsingEntireBufferForLegacyFormat();
} }
Local<Value> value; Local<Value> value;
if (!ToLocal(result, &value)) { has_pending_exception = !ToLocal(result, &value);
has_pending_exception = private_->isolate->has_pending_exception();
RETURN_ON_FAILED_EXECUTION(Value); RETURN_ON_FAILED_EXECUTION(Value);
return MaybeLocal<Value>();
}
RETURN_ESCAPED(value); RETURN_ESCAPED(value);
} }
void ValueDeserializer::TransferArrayBuffer(uint32_t transfer_id, void ValueDeserializer::TransferArrayBuffer(uint32_t transfer_id,
Local<ArrayBuffer> array_buffer) { Local<ArrayBuffer> array_buffer) {
CHECK(!private_->has_aborted);
private_->deserializer.TransferArrayBuffer(transfer_id, private_->deserializer.TransferArrayBuffer(transfer_id,
Utils::OpenHandle(*array_buffer)); Utils::OpenHandle(*array_buffer));
} }
void ValueDeserializer::TransferSharedArrayBuffer( void ValueDeserializer::TransferSharedArrayBuffer(
uint32_t transfer_id, Local<SharedArrayBuffer> shared_array_buffer) { uint32_t transfer_id, Local<SharedArrayBuffer> shared_array_buffer) {
CHECK(!private_->has_aborted);
private_->deserializer.TransferArrayBuffer( private_->deserializer.TransferArrayBuffer(
transfer_id, Utils::OpenHandle(*shared_array_buffer)); transfer_id, Utils::OpenHandle(*shared_array_buffer));
} }
......
...@@ -661,6 +661,7 @@ class RuntimeCallTimer { ...@@ -661,6 +661,7 @@ class RuntimeCallTimer {
V(UnboundScript_GetSourceMappingURL) \ V(UnboundScript_GetSourceMappingURL) \
V(UnboundScript_GetSourceURL) \ V(UnboundScript_GetSourceURL) \
V(Value_TypeOf) \ V(Value_TypeOf) \
V(ValueDeserializer_ReadHeader) \
V(ValueDeserializer_ReadValue) \ V(ValueDeserializer_ReadValue) \
V(ValueSerializer_WriteValue) V(ValueSerializer_WriteValue)
......
...@@ -641,7 +641,11 @@ class ErrorUtils : public AllStatic { ...@@ -641,7 +641,11 @@ class ErrorUtils : public AllStatic {
"An ArrayBuffer is neutered and could not be cloned.") \ "An ArrayBuffer is neutered and could not be cloned.") \
T(DataCloneErrorSharedArrayBufferNotTransferred, \ T(DataCloneErrorSharedArrayBufferNotTransferred, \
"A SharedArrayBuffer could not be cloned. SharedArrayBuffer must be " \ "A SharedArrayBuffer could not be cloned. SharedArrayBuffer must be " \
"transferred.") "transferred.") \
T(DataCloneDeserializationError, "Unable to deserialize cloned data.") \
T(DataCloneDeserializationVersionError, \
"Unable to deserialize cloned data due to invalid or unsupported " \
"version.")
class MessageTemplate { class MessageTemplate {
public: public:
......
...@@ -682,8 +682,11 @@ Maybe<bool> ValueDeserializer::ReadHeader() { ...@@ -682,8 +682,11 @@ Maybe<bool> ValueDeserializer::ReadHeader() {
if (position_ < end_ && if (position_ < end_ &&
*position_ == static_cast<uint8_t>(SerializationTag::kVersion)) { *position_ == static_cast<uint8_t>(SerializationTag::kVersion)) {
ReadTag().ToChecked(); ReadTag().ToChecked();
if (!ReadVarint<uint32_t>().To(&version_)) return Nothing<bool>(); if (!ReadVarint<uint32_t>().To(&version_) || version_ > kLatestVersion) {
if (version_ > kLatestVersion) return Nothing<bool>(); isolate_->Throw(*isolate_->factory()->NewError(
MessageTemplate::kDataCloneDeserializationVersionError));
return Nothing<bool>();
}
} }
return Just(true); return Just(true);
} }
...@@ -804,6 +807,11 @@ MaybeHandle<Object> ValueDeserializer::ReadObject() { ...@@ -804,6 +807,11 @@ MaybeHandle<Object> ValueDeserializer::ReadObject() {
result = ReadJSArrayBufferView(Handle<JSArrayBuffer>::cast(object)); result = ReadJSArrayBufferView(Handle<JSArrayBuffer>::cast(object));
} }
if (result.is_null() && !isolate_->has_pending_exception()) {
isolate_->Throw(*isolate_->factory()->NewError(
MessageTemplate::kDataCloneDeserializationError));
}
return result; return result;
} }
...@@ -1299,8 +1307,7 @@ static Maybe<bool> SetPropertiesFromKeyValuePairs(Isolate* isolate, ...@@ -1299,8 +1307,7 @@ static Maybe<bool> SetPropertiesFromKeyValuePairs(Isolate* isolate,
MaybeHandle<Object> MaybeHandle<Object>
ValueDeserializer::ReadObjectUsingEntireBufferForLegacyFormat() { ValueDeserializer::ReadObjectUsingEntireBufferForLegacyFormat() {
if (version_ > 0) return MaybeHandle<Object>(); DCHECK_EQ(version_, 0);
HandleScope scope(isolate_); HandleScope scope(isolate_);
std::vector<Handle<Object>> stack; std::vector<Handle<Object>> stack;
while (position_ < end_) { while (position_ < end_) {
...@@ -1362,9 +1369,12 @@ ValueDeserializer::ReadObjectUsingEntireBufferForLegacyFormat() { ...@@ -1362,9 +1369,12 @@ ValueDeserializer::ReadObjectUsingEntireBufferForLegacyFormat() {
new_object = js_array; new_object = js_array;
break; break;
} }
case SerializationTag::kEndDenseJSArray: case SerializationTag::kEndDenseJSArray: {
// This was already broken in Chromium, and apparently wasn't missed. // This was already broken in Chromium, and apparently wasn't missed.
isolate_->Throw(*isolate_->factory()->NewError(
MessageTemplate::kDataCloneDeserializationError));
return MaybeHandle<Object>(); return MaybeHandle<Object>();
}
default: default:
if (!ReadObject().ToHandle(&new_object)) return MaybeHandle<Object>(); if (!ReadObject().ToHandle(&new_object)) return MaybeHandle<Object>();
break; break;
...@@ -1380,7 +1390,11 @@ ValueDeserializer::ReadObjectUsingEntireBufferForLegacyFormat() { ...@@ -1380,7 +1390,11 @@ ValueDeserializer::ReadObjectUsingEntireBufferForLegacyFormat() {
#endif #endif
position_ = end_; position_ = end_;
if (stack.size() != 1) return MaybeHandle<Object>(); if (stack.size() != 1) {
isolate_->Throw(*isolate_->factory()->NewError(
MessageTemplate::kDataCloneDeserializationError));
return MaybeHandle<Object>();
}
return scope.CloseAndEscape(stack[0]); return scope.CloseAndEscape(stack[0]);
} }
......
...@@ -96,7 +96,7 @@ class ValueSerializerTest : public TestWithIsolate { ...@@ -96,7 +96,7 @@ class ValueSerializerTest : public TestWithIsolate {
static_cast<int>(data.size())); static_cast<int>(data.size()));
deserializer.SetSupportsLegacyWireFormat(true); deserializer.SetSupportsLegacyWireFormat(true);
BeforeDecode(&deserializer); BeforeDecode(&deserializer);
ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false)); ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
Local<Value> result; Local<Value> result;
ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result)); ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result));
ASSERT_FALSE(result.IsEmpty()); ASSERT_FALSE(result.IsEmpty());
...@@ -119,7 +119,7 @@ class ValueSerializerTest : public TestWithIsolate { ...@@ -119,7 +119,7 @@ class ValueSerializerTest : public TestWithIsolate {
static_cast<int>(data.size())); static_cast<int>(data.size()));
deserializer.SetSupportsLegacyWireFormat(true); deserializer.SetSupportsLegacyWireFormat(true);
BeforeDecode(&deserializer); BeforeDecode(&deserializer);
ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false)); ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
ASSERT_EQ(0, deserializer.GetWireFormatVersion()); ASSERT_EQ(0, deserializer.GetWireFormatVersion());
Local<Value> result; Local<Value> result;
ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result)); ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result));
...@@ -141,10 +141,14 @@ class ValueSerializerTest : public TestWithIsolate { ...@@ -141,10 +141,14 @@ class ValueSerializerTest : public TestWithIsolate {
static_cast<int>(data.size())); static_cast<int>(data.size()));
deserializer.SetSupportsLegacyWireFormat(true); deserializer.SetSupportsLegacyWireFormat(true);
BeforeDecode(&deserializer); BeforeDecode(&deserializer);
Maybe<bool> header_result = deserializer.ReadHeader(); Maybe<bool> header_result = deserializer.ReadHeader(context);
if (header_result.IsNothing()) return; if (header_result.IsNothing()) {
EXPECT_TRUE(try_catch.HasCaught());
return;
}
ASSERT_TRUE(header_result.ToChecked()); ASSERT_TRUE(header_result.ToChecked());
ASSERT_TRUE(deserializer.ReadValue(context).IsEmpty()); ASSERT_TRUE(deserializer.ReadValue(context).IsEmpty());
EXPECT_TRUE(try_catch.HasCaught());
} }
Local<Value> EvaluateScriptForInput(const char* utf8_source) { Local<Value> EvaluateScriptForInput(const char* utf8_source) {
......
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