mirror of
https://github.com/dart-lang/sdk
synced 2024-09-20 05:31:31 +00:00
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:
parent
4fe661995d
commit
5612c202a0
|
@ -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.
|
||||
|
|
169
tests/compiler/dart2js_native/error_safeToString_test.dart
Normal file
169
tests/compiler/dart2js_native/error_safeToString_test.dart
Normal 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();
|
||||
}
|
Loading…
Reference in a new issue