Rewrite Error.prototype.toString in C++.

This avoids many back-and-forth calls to the runtime.

This also slightly changes the way we avoid getters. Previously, we circumvent getting the name property of ReferenceError, SyntaxError and TypeError due to crbug/69187 (in order to avoid leaking information from those errors through a 'name' getter installed on their prototypes). Now we do that for all errors created by V8.

R=jkummerow@chromium.org, rossberg@chromium.org
BUG=crbug:513472, crbug:69187
LOG=N

Review URL: https://codereview.chromium.org/1281833002

Cr-Commit-Position: refs/heads/master@{#30105}
This commit is contained in:
yangguo 2015-08-11 02:15:27 -07:00 committed by Commit bot
parent a68ad56c50
commit 2e2765a6eb
10 changed files with 209 additions and 65 deletions

View File

@ -2742,6 +2742,15 @@ bool Genesis::InstallSpecialObjects(Handle<Context> native_context) {
factory->stack_trace_symbol(), NONE),
false);
// Expose the internal error symbol to native JS
RETURN_ON_EXCEPTION_VALUE(isolate,
JSObject::SetOwnPropertyIgnoreAttributes(
handle(native_context->builtins(), isolate),
factory->InternalizeOneByteString(
STATIC_CHAR_VECTOR("$internalErrorSymbol")),
factory->internal_error_symbol(), NONE),
false);
// Expose the debug global object in global if a name for it is specified.
if (FLAG_expose_debug_as != NULL && strlen(FLAG_expose_debug_as) != 0) {
// If loading fails we just bail out without installing the

View File

@ -324,7 +324,8 @@ namespace internal {
V(class_end_position_symbol) \
V(error_start_pos_symbol) \
V(error_end_pos_symbol) \
V(error_script_symbol)
V(error_script_symbol) \
V(internal_error_symbol)
#define PUBLIC_SYMBOL_LIST(V) \
V(has_instance_symbol, symbolHasInstance, Symbol.hasInstance) \

View File

@ -23,6 +23,7 @@
#include "src/handles.h"
#include "src/hashmap.h"
#include "src/heap/heap.h"
#include "src/messages.h"
#include "src/optimizing-compile-dispatcher.h"
#include "src/regexp-stack.h"
#include "src/runtime/runtime.h"
@ -994,6 +995,10 @@ class Isolate {
date_cache_ = date_cache;
}
ErrorToStringHelper* error_tostring_helper() {
return &error_tostring_helper_;
}
Map* get_initial_js_array_map(ElementsKind kind,
Strength strength = Strength::WEAK);
@ -1296,6 +1301,7 @@ class Isolate {
regexp_macro_assembler_canonicalize_;
RegExpStack* regexp_stack_;
DateCache* date_cache_;
ErrorToStringHelper error_tostring_helper_;
unibrow::Mapping<unibrow::Ecma262Canonicalize> interp_canonicalize_mapping_;
CallInterfaceDescriptorData* call_descriptor_data_;
base::RandomNumberGenerator* random_number_generator_;

View File

@ -367,5 +367,97 @@ MaybeHandle<String> MessageTemplate::FormatMessage(int template_index,
return builder.Finish();
}
MaybeHandle<String> ErrorToStringHelper::Stringify(Isolate* isolate,
Handle<JSObject> error) {
VisitedScope scope(this, error);
if (scope.has_visited()) return isolate->factory()->empty_string();
Handle<String> name;
Handle<String> message;
Handle<Name> internal_key = isolate->factory()->internal_error_symbol();
Handle<String> message_string =
isolate->factory()->NewStringFromStaticChars("message");
Handle<String> name_string = isolate->factory()->name_string();
LookupIterator internal_error_lookup(
error, internal_key, LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR);
LookupIterator message_lookup(
error, message_string, LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR);
LookupIterator name_lookup(error, name_string,
LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR);
// Find out whether an internally created error object is on the prototype
// chain. If the name property is found on a holder prior to the internally
// created error object, use that name property. Otherwise just use the
// constructor name to avoid triggering possible side effects.
// Similar for the message property. If the message property shadows the
// internally created error object, use that message property. Otherwise
// use empty string as message.
if (internal_error_lookup.IsFound()) {
if (!ShadowsInternalError(isolate, &name_lookup, &internal_error_lookup)) {
Handle<JSObject> holder = internal_error_lookup.GetHolder<JSObject>();
name = Handle<String>(holder->constructor_name());
}
if (!ShadowsInternalError(isolate, &message_lookup,
&internal_error_lookup)) {
message = isolate->factory()->empty_string();
}
}
if (name.is_null()) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, name,
GetStringifiedProperty(isolate, &name_lookup,
isolate->factory()->Error_string()),
String);
}
if (message.is_null()) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, message,
GetStringifiedProperty(isolate, &message_lookup,
isolate->factory()->empty_string()),
String);
}
if (name->length() == 0) return message;
if (message->length() == 0) return name;
IncrementalStringBuilder builder(isolate);
builder.AppendString(name);
builder.AppendCString(": ");
builder.AppendString(message);
return builder.Finish();
}
bool ErrorToStringHelper::ShadowsInternalError(
Isolate* isolate, LookupIterator* property_lookup,
LookupIterator* internal_error_lookup) {
Handle<JSObject> holder = property_lookup->GetHolder<JSObject>();
// It's fine if the property is defined on the error itself.
if (holder.is_identical_to(property_lookup->GetReceiver())) return true;
PrototypeIterator it(isolate, holder, PrototypeIterator::START_AT_RECEIVER);
while (true) {
if (it.IsAtEnd()) return false;
if (it.IsAtEnd(internal_error_lookup->GetHolder<JSObject>())) return true;
it.AdvanceIgnoringProxies();
}
}
MaybeHandle<String> ErrorToStringHelper::GetStringifiedProperty(
Isolate* isolate, LookupIterator* property_lookup,
Handle<String> default_value) {
if (!property_lookup->IsFound()) return default_value;
Handle<Object> obj;
ASSIGN_RETURN_ON_EXCEPTION(isolate, obj, Object::GetProperty(property_lookup),
String);
if (obj->IsUndefined()) return default_value;
if (!obj->IsString()) {
ASSIGN_RETURN_ON_EXCEPTION(isolate, obj, Execution::ToString(isolate, obj),
String);
}
return Handle<String>::cast(obj);
}
} // namespace internal
} // namespace v8

View File

@ -457,6 +457,46 @@ class MessageHandler {
static base::SmartArrayPointer<char> GetLocalizedMessage(Isolate* isolate,
Handle<Object> data);
};
class ErrorToStringHelper {
public:
ErrorToStringHelper() : visited_(0) {}
MUST_USE_RESULT MaybeHandle<String> Stringify(Isolate* isolate,
Handle<JSObject> error);
private:
class VisitedScope {
public:
VisitedScope(ErrorToStringHelper* helper, Handle<JSObject> error)
: helper_(helper), has_visited_(false) {
for (const Handle<JSObject>& visited : helper->visited_) {
if (visited.is_identical_to(error)) {
has_visited_ = true;
break;
}
}
helper->visited_.Add(error);
}
~VisitedScope() { helper_->visited_.RemoveLast(); }
bool has_visited() { return has_visited_; }
private:
ErrorToStringHelper* helper_;
bool has_visited_;
};
static bool ShadowsInternalError(Isolate* isolate,
LookupIterator* property_lookup,
LookupIterator* internal_error_lookup);
static MUST_USE_RESULT MaybeHandle<String> GetStringifiedProperty(
Isolate* isolate, LookupIterator* property_lookup,
Handle<String> default_value);
List<Handle<JSObject> > visited_;
};
} } // namespace v8::internal
#endif // V8_MESSAGES_H_

View File

@ -6,6 +6,7 @@
var $errorToString;
var $getStackTraceLine;
var $internalErrorSymbol;
var $messageGetPositionInLine;
var $messageGetLineNumber;
var $messageGetSourceLine;
@ -181,7 +182,9 @@ function ToDetailString(obj) {
function MakeGenericError(constructor, type, arg0, arg1, arg2) {
if (IS_UNDEFINED(arg0) && IS_STRING(type)) arg0 = [];
return new constructor(FormatMessage(type, arg0, arg1, arg2));
var error = new constructor(FormatMessage(type, arg0, arg1, arg2));
error[$internalErrorSymbol] = true;
return error;
}
@ -1000,66 +1003,12 @@ GlobalURIError = DefineError(global, function URIError() { });
%AddNamedProperty(GlobalError.prototype, 'message', '', DONT_ENUM);
// Global list of error objects visited during ErrorToString. This is
// used to detect cycles in error toString formatting.
var visited_errors = new InternalArray();
var cyclic_error_marker = new GlobalObject();
function GetPropertyWithoutInvokingMonkeyGetters(error, name) {
var current = error;
// Climb the prototype chain until we find the holder.
while (current && !%HasOwnProperty(current, name)) {
current = %_GetPrototype(current);
}
if (IS_NULL(current)) return UNDEFINED;
if (!IS_OBJECT(current)) return error[name];
// If the property is an accessor on one of the predefined errors that can be
// generated statically by the compiler, don't touch it. This is to address
// http://code.google.com/p/chromium/issues/detail?id=69187
var desc = %GetOwnProperty(current, name);
if (desc && desc[IS_ACCESSOR_INDEX]) {
var isName = name === "name";
if (current === GlobalReferenceError.prototype)
return isName ? "ReferenceError" : UNDEFINED;
if (current === GlobalSyntaxError.prototype)
return isName ? "SyntaxError" : UNDEFINED;
if (current === GlobalTypeError.prototype)
return isName ? "TypeError" : UNDEFINED;
}
// Otherwise, read normally.
return error[name];
}
function ErrorToStringDetectCycle(error) {
if (!%PushIfAbsent(visited_errors, error)) throw cyclic_error_marker;
try {
var name = GetPropertyWithoutInvokingMonkeyGetters(error, "name");
name = IS_UNDEFINED(name) ? "Error" : TO_STRING_INLINE(name);
var message = GetPropertyWithoutInvokingMonkeyGetters(error, "message");
message = IS_UNDEFINED(message) ? "" : TO_STRING_INLINE(message);
if (name === "") return message;
if (message === "") return name;
return name + ": " + message;
} finally {
visited_errors.length = visited_errors.length - 1;
}
}
function ErrorToString() {
if (!IS_SPEC_OBJECT(this)) {
throw MakeTypeError(kCalledOnNonObject, "Error.prototype.toString");
}
try {
return ErrorToStringDetectCycle(this);
} catch(e) {
// If this error message was encountered already return the empty
// string for it instead of recursively formatting it.
if (e === cyclic_error_marker) {
return '';
}
throw e;
}
return %ErrorToStringRT(this);
}
utils.InstallFunctions(GlobalError.prototype, DONT_ENUM,

View File

@ -253,6 +253,18 @@ RUNTIME_FUNCTION(Runtime_MessageGetScript) {
}
RUNTIME_FUNCTION(Runtime_ErrorToStringRT) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, error, 0);
Handle<String> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result,
isolate->error_tostring_helper()->Stringify(isolate, error));
return *result;
}
RUNTIME_FUNCTION(Runtime_FormatMessageString) {
HandleScope scope(isolate);
DCHECK(args.length() == 4);

View File

@ -322,6 +322,7 @@ namespace internal {
F(RenderCallSite, 0, 1) \
F(MessageGetStartPosition, 1, 1) \
F(MessageGetScript, 1, 1) \
F(ErrorToStringRT, 1, 1) \
F(FormatMessageString, 4, 1) \
F(CallSiteGetFileNameRT, 3, 1) \
F(CallSiteGetFunctionNameRT, 3, 1) \

View File

@ -69,26 +69,53 @@ assertTrue(e.hasOwnProperty('stack'));
// compiler errors. This is not specified, but allowing interception
// through a getter can leak error objects from different
// script tags in the same context in a browser setting.
var errors = [SyntaxError, ReferenceError, TypeError];
var errors = [SyntaxError, ReferenceError, TypeError, RangeError, URIError];
var error_triggers = ["syntax error",
"var error = reference",
"undefined()",
"String.fromCodePoint(0xFFFFFF)",
"decodeURI('%F')"];
for (var i in errors) {
var name = errors[i].prototype.toString();
var name = errors[i].name;
// Monkey-patch prototype.
var props = ["name", "message", "stack"];
for (var j in props) {
errors[i].prototype.__defineGetter__(props[j], fail);
}
// String conversion should not invoke monkey-patched getters on prototype.
var e = new errors[i];
assertEquals(name, e.toString());
var error;
try {
eval(error_triggers[i]);
} catch (e) {
error = e;
}
assertTrue(error.toString().startsWith(name));
// Deleting message on the error (exposing the getter) is fine.
delete error.message;
assertEquals(name, error.toString());
// Custom properties shadowing the name are fine.
var myerror = { name: "myerror", message: "mymessage"};
myerror.__proto__ = error;
assertEquals("myerror: mymessage", myerror.toString());
// Custom getters in actual objects are welcome.
e.__defineGetter__("name", function() { return "mine"; });
assertEquals("mine", e.toString());
error.__defineGetter__("name", function() { return "mine"; });
assertEquals("mine", error.toString());
// Custom properties shadowing the name are fine.
var myerror2 = { message: "mymessage"};
myerror2.__proto__ = error;
assertEquals("mine: mymessage", myerror2.toString());
}
// Monkey-patching non-static errors should still be observable.
// Monkey-patching non-internal errors should still be observable.
function MyError() {}
MyError.prototype = new Error;
var errors = [Error, RangeError, EvalError, URIError, MyError];
var errors = [Error, RangeError, EvalError, URIError,
SyntaxError, ReferenceError, TypeError, MyError];
for (var i in errors) {
errors[i].prototype.__defineGetter__("name", function() { return "my"; });
errors[i].prototype.__defineGetter__("message", function() { return "moo"; });

View File

@ -0,0 +1,7 @@
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
this.__proto__ = Error();
assertThrows(function() { NaN = 1; });