dart2js runtime: Harden objectTypeName.

1. Avoid crashing on short minified names.
2. Try to find a better name for external JavaScript classes.
3. Try to find a better name for undistingushed native classes.

BUG= http://dartbug.com/24393

This is related to http://dartbug.com/19137

R=sigmund@google.com

Review URL: https://codereview.chromium.org//1362553002 .
This commit is contained in:
Stephen Adams 2015-09-22 14:46:14 -07:00
parent 4fe661995d
commit 5612c202a0
2 changed files with 229 additions and 12 deletions

View file

@ -49,6 +49,7 @@ import 'dart:_foreign_helper' show
JS_EMBEDDED_GLOBAL,
JS_GET_FLAG,
JS_GET_NAME,
JS_INTERCEPTOR_CONSTANT,
JS_STRING_CONCAT,
RAW_DART_FUNCTION_REF;
@ -845,24 +846,71 @@ class Primitives {
///
/// In minified mode, uses the unminified names if available.
static String objectTypeName(Object object) {
String name = constructorNameFallback(getInterceptor(object));
if (name == 'Object') {
// Try to decompile the constructor by turning it into a string and get
// the name out of that. If the decompiled name is a string containing an
// identifier, we use that instead of the very generic 'Object'.
var decompiled =
JS('var', r'#.match(/^\s*function\s*([\w$]*)\s*\(/)[1]',
JS('var', r'String(#.constructor)', object));
if (decompiled is String)
if (JS('bool', r'/^\w+$/.test(#)', decompiled))
name = decompiled;
return formatType(_objectRawTypeName(object), getRuntimeTypeInfo(object));
}
static String _objectRawTypeName(Object object) {
var interceptor = getInterceptor(object);
// The interceptor is either an object (self-intercepting plain Dart class),
// the prototype of the constructor for an Interceptor class (like
// `JSString.prototype`, `JSNull.prototype`), or an Interceptor object
// instance (`const JSString()`, should use `JSString.prototype`).
//
// These all should have a `constructor` property with a `name` property.
String name;
var interceptorConstructor = JS('', '#.constructor', interceptor);
if (JS('bool', 'typeof # == "function"', interceptorConstructor)) {
var interceptorConstructorName = JS('', '#.name', interceptorConstructor);
if (interceptorConstructorName is String) {
name = interceptorConstructorName;
}
}
if (name == null ||
identical(interceptor,
JS_INTERCEPTOR_CONSTANT(UnknownJavaScriptObject)) ||
identical(interceptor, JS_INTERCEPTOR_CONSTANT(Interceptor))) {
// Try to do better. If we do not find something better, leave the name
// as 'UnknownJavaScriptObject' or 'Interceptor' (or the minified name).
//
// When we get here via the UnknownJavaScriptObject test (for JavaScript
// objects from outside the program), the object's constructor has a
// better name that 'UnknownJavaScriptObject'.
//
// When we get here the Interceptor test (for Native classes that are
// declared in the Dart program but have been 'folded' into Interceptor),
// the native class's constructor name is better than the generic
// 'Interceptor' (an abstract class).
// Try the [constructorNameFallback]. This gets the constructor name for
// any browser (used by [getNativeInterceptor]).
String dispatchName = constructorNameFallback(object);
if (name == null) name = dispatchName;
if (dispatchName == 'Object') {
// Try to decompile the constructor by turning it into a string and get
// the name out of that. If the decompiled name is a string containing
// an identifier, we use that instead of the very generic 'Object'.
var objectConstructor = JS('', '#.constructor', object);
if (JS('bool', 'typeof # == "function"', objectConstructor)) {
var decompiledName =
JS('var', r'#.match(/^\s*function\s*([\w$]*)\s*\(/)[1]',
JS('var', r'String(#)', objectConstructor));
if (decompiledName is String &&
JS('bool', r'/^\w+$/.test(#)', decompiledName)) {
name = decompiledName;
}
}
} else {
name = dispatchName;
}
}
// TODO(kasperl): If the namer gave us a fresh global name, we may
// want to remove the numeric suffix that makes it unique too.
if (name.length > 1 && identical(name.codeUnitAt(0), DOLLAR_CHAR_VALUE)) {
name = name.substring(1);
}
return formatType(name, getRuntimeTypeInfo(object));
return name;
}
/// In minified mode, uses the unminified names if available.

View file

@ -0,0 +1,169 @@
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "package:expect/expect.dart";
import 'dart:_foreign_helper' show JS_INTERCEPTOR_CONSTANT, JS;
import 'dart:_js_helper' show Native, Creates;
import 'dart:_interceptors' show
Interceptor,
JavaScriptObject,
PlainJavaScriptObject,
UnknownJavaScriptObject;
// Test for safe formatting of JavaScript objects by Error.safeToString.
@Native('PPPP')
class Purple {}
@Native('QQQQ')
class Q {}
@Native('RRRR')
class Rascal {
toString() => 'RRRRRRRR';
}
makeA() native;
makeB() native;
makeC() native;
makeD() native;
makeE() native;
makeP() native;
makeQ() native;
makeR() native;
void setup() native r"""
makeA = function(){return {hello: 123};};
function BB(){}
makeB = function(){return new BB();};
function CC(){}
makeC = function(){
var x = new CC();
x.constructor = null; // Foils constructor lookup.
return x;
};
function DD(){}
makeD = function(){
var x = new DD();
x.constructor = {name: 'DDxxx'}; // Foils constructor lookup.
return x;
};
function EE(){}
makeE = function(){
var x = new EE();
x.constructor = function Liar(){}; // Looks like a legitimate constructor.
return x;
};
function PPPP(){}
makeP = function(){return new PPPP();};
function QQQQ(){}
makeQ = function(){return new QQQQ();};
function RRRR(){}
makeR = function(){return new RRRR();};
""";
expectTypeName(expectedName, s) {
var m = new RegExp(r"Instance of '(.*)'").firstMatch(s);
Expect.isNotNull(m);
var name = m.group(1);
Expect.isTrue(expectedName == name || name.length <= 3,
"Is '$expectedName' or minified: '$name'");
}
final plainJsString =
Error.safeToString(JS_INTERCEPTOR_CONSTANT(PlainJavaScriptObject));
final unknownJsString =
Error.safeToString(JS_INTERCEPTOR_CONSTANT(UnknownJavaScriptObject));
final interceptorString =
Error.safeToString(JS_INTERCEPTOR_CONSTANT(Interceptor));
testDistinctInterceptors() {
// Test invariants needed for the other tests.
Expect.notEquals(plainJsString, unknownJsString);
Expect.notEquals(plainJsString, interceptorString);
Expect.notEquals(unknownJsString, interceptorString);
expectTypeName('PlainJavaScriptObject', plainJsString);
expectTypeName('UnknownJavaScriptObject', unknownJsString);
expectTypeName('Interceptor', interceptorString);
// Sometimes interceptor *objects* are used instead of the prototypes. Check
// these work too.
var plain2 = Error.safeToString(const PlainJavaScriptObject());
Expect.equals(plainJsString, plain2);
var unk2 = Error.safeToString(const UnknownJavaScriptObject());
Expect.equals(unknownJsString, unk2);
}
testExternal() {
var x = makeA();
Expect.equals(plainJsString, Error.safeToString(x));
x = makeB();
// Gets name from constructor, regardless of minification.
Expect.equals("Instance of 'BB'", Error.safeToString(x));
x = makeC();
Expect.equals(unknownJsString, Error.safeToString(x));
x = makeD();
Expect.equals(unknownJsString, Error.safeToString(x));
x = makeE();
Expect.equals("Instance of 'Liar'", Error.safeToString(x));
}
testNative() {
var x = makeP();
Expect.isTrue(x is Purple); // This test forces Purple to be distinguished.
Expect.notEquals(plainJsString, Error.safeToString(x));
Expect.notEquals(unknownJsString, Error.safeToString(x));
Expect.notEquals(interceptorString, Error.safeToString(x));
// And not the native class constructor.
Expect.notEquals("Instance of 'PPPP'", Error.safeToString(x));
expectTypeName('Purple', Error.safeToString(x));
x = makeQ();
print('Q: $x ${Error.safeToString(x)}');
// We are going to get either the general interceptor or the JavaScript
// constructor.
Expect.isTrue(
"Instance of 'QQQQ'" == Error.safeToString(x) ||
interceptorString == Error.safeToString(x));
x = makeR();
// Rascal overrides 'toString'. The toString() call causes Rascal to be
// distinguished.
x.toString();
Expect.notEquals(plainJsString, Error.safeToString(x));
Expect.notEquals(unknownJsString, Error.safeToString(x));
Expect.notEquals(interceptorString, Error.safeToString(x));
// And not the native class constructor.
Expect.notEquals("Instance of 'RRRR'", Error.safeToString(x));
expectTypeName('Rascal', Error.safeToString(x));
}
main() {
setup();
testDistinctInterceptors();
testExternal();
testNative();
}