Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(223)

Side by Side Diff: dart/sdk/lib/_internal/compiler/implementation/lib/js_helper.dart

Issue 11973018: Improve decoding of JS TypeError. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge
Patch Set: Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | dart/sdk/lib/_internal/compiler/implementation/lib/regexp_helper.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library _js_helper; 5 library _js_helper;
6 6
7 import 'dart:collection'; 7 import 'dart:collection';
8 8
9 part 'constant_map.dart'; 9 part 'constant_map.dart';
10 part 'native_helper.dart'; 10 part 'native_helper.dart';
(...skipping 676 matching lines...) Expand 10 before | Expand all | Expand 10 after
687 if (object == null || object is bool || object is num || object is String) { 687 if (object == null || object is bool || object is num || object is String) {
688 throw new ArgumentError(object); 688 throw new ArgumentError(object);
689 } 689 }
690 JS('void', '#[#] = #', object, key, value); 690 JS('void', '#[#] = #', object, key, value);
691 } 691 }
692 692
693 static applyFunction(Function function, 693 static applyFunction(Function function,
694 List positionalArguments, 694 List positionalArguments,
695 Map<String, dynamic> namedArguments) { 695 Map<String, dynamic> namedArguments) {
696 int argumentCount = 0; 696 int argumentCount = 0;
697 StringBuffer buffer = new StringBuffer();
698 List arguments = []; 697 List arguments = [];
699 698
700 if (positionalArguments != null) { 699 if (positionalArguments != null) {
701 argumentCount += positionalArguments.length; 700 argumentCount += positionalArguments.length;
702 arguments.addAll(positionalArguments); 701 arguments.addAll(positionalArguments);
703 } 702 }
704 703
704 String selectorName = 'call\$$argumentCount';
705
705 // Sort the named arguments to get the right selector name and 706 // Sort the named arguments to get the right selector name and
706 // arguments order. 707 // arguments order.
707 if (namedArguments != null && !namedArguments.isEmpty) { 708 if (namedArguments != null && !namedArguments.isEmpty) {
708 // Call new List.from to make sure we get a JavaScript array. 709 // Call new List.from to make sure we get a JavaScript array.
709 List<String> listOfNamedArguments = 710 List<String> listOfNamedArguments =
710 new List<String>.from(namedArguments.keys); 711 new List<String>.from(namedArguments.keys);
711 argumentCount += namedArguments.length; 712 argumentCount += namedArguments.length;
712 // We're sorting on strings, and the behavior is the same between 713 // We're sorting on strings, and the behavior is the same between
713 // Dart string sort and JS string sort. To avoid needing the Dart 714 // Dart string sort and JS string sort. To avoid needing the Dart
714 // sort implementation, we use the JavaScript one instead. 715 // sort implementation, we use the JavaScript one instead.
715 JS('void', '#.sort()', listOfNamedArguments); 716 JS('void', '#.sort()', listOfNamedArguments);
716 listOfNamedArguments.forEach((String name) { 717 listOfNamedArguments.forEach((String name) {
717 buffer.add('\$$name'); 718 selectorName = '$selectorName\$$name';
718 arguments.add(namedArguments[name]); 719 arguments.add(namedArguments[name]);
719 }); 720 });
720 } 721 }
721 722
722 String selectorName = 'call\$$argumentCount$buffer';
723 var jsFunction = JS('var', '#[#]', function, selectorName); 723 var jsFunction = JS('var', '#[#]', function, selectorName);
724 if (jsFunction == null) { 724 if (jsFunction == null) {
725 throw new NoSuchMethodError(function, selectorName, arguments, {}); 725 throw new NoSuchMethodError(function, selectorName, arguments, {});
726 } 726 }
727 // We bound 'this' to [function] because of how we compile 727 // We bound 'this' to [function] because of how we compile
728 // closures: escaped local variables are stored and accessed through 728 // closures: escaped local variables are stored and accessed through
729 // [function]. 729 // [function].
730 return JS('var', '#.apply(#, #)', jsFunction, function, arguments); 730 return JS('var', '#.apply(#, #)', jsFunction, function, arguments);
731 } 731 }
732 732
(...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after
955 * in a subtype test. 955 * in a subtype test.
956 */ 956 */
957 throwMalformedSubtypeError(value, type, reasons) { 957 throwMalformedSubtypeError(value, type, reasons) {
958 throw new TypeErrorImplementation.malformedSubtype(value, type, reasons); 958 throw new TypeErrorImplementation.malformedSubtype(value, type, reasons);
959 } 959 }
960 960
961 throwAbstractClassInstantiationError(className) { 961 throwAbstractClassInstantiationError(className) {
962 throw new AbstractClassInstantiationError(className); 962 throw new AbstractClassInstantiationError(className);
963 } 963 }
964 964
965 class TypeErrorDecoder {
966 static final noSuchMethodPattern =
kasperl 2013/01/21 13:54:09 I find that this method is really hard to read and
sra1 2013/01/29 20:54:16 In addition to Kaspers suggestions, don't forget t
ahe 2013/01/29 22:06:46 Stephen, I'm sorry you spent so much time reviewin
967 extractPattern(JS('',
968 r'{ toString: function() { return "$receiver$"; } }'));
969 static final nullErrorPattern = extractPattern(JS('', 'null'));
970 static final undefinedErrorPattern = extractPattern(JS('', 'void 0'));
971
972 static extractPattern(expression) {
973 var function = JS('', r"""function($expr$) {
sra1 2013/01/29 20:54:16 Why are these names $name$? If they are not substi
ahe 2013/01/29 22:06:46 I haven't documented why I use "$name$", not "name
974 var $argumentsExpr$ = '$arguments$'
975 try {
976 $expr$.$method$($argumentsExpr$);
977 } catch (e) {
978 return (e.message"""
979 // Subtle bug/feature in V8: it no longer calls toString.
sra1 2013/01/29 20:54:16 it? Can you be more specific about what no longer
ahe 2013/01/29 22:06:46 I looked at my new comment. I can still clarify it
980 r"""
981 .replace(String({}), '$receiver$')
sra1 2013/01/29 20:54:16 Surely there are other receivers? Try with some DO
ahe 2013/01/29 22:06:46 I don't understand what you're saying.
982 .replace(new RegExp(#, 'g'), '\\$&'));
983 }
984 }""", ESCAPE_REGEXP);
985 String message = JS('String', '(#)(#)', function, expression);
986 List<String> match =
987 JS('=List', r"#.match(/\\\$[a-zA-Z]+\\\$/g)", message);
988 int arguments = JS('int', '#.indexOf(#)', match, r'\$arguments\$');
989 int argumentsExpr = JS('int', '#.indexOf(#)', match, r'\$argumentsExpr\$');
990 int expr = JS('int', '#.indexOf(#)', match, r'\$expr\$');
991 int method = JS('int', '#.indexOf(#)', match, r'\$method\$');
992 int receiver = JS('int', '#.indexOf(#)', match, r'\$receiver\$');
993
994 String pattern = JS('String',
995 r"#.replace('\\$arguments\\$', '(.*)')"
sra1 2013/01/29 20:54:16 It will be slightly better if emitted JavaScript s
ahe 2013/01/29 22:06:46 Why is that?
996 r".replace('\\$argumentsExpr\\$', '(.*)')"
997 r".replace('\\$expr\\$', '(.*)')"
998 r".replace('\\$method\\$', '(.*)')"
999 r".replace('\\$receiver\\$', '(.*)')",
1000 message);
1001
1002 return JS('',
1003 r'{ arguments: #, argumentsExpr: #, expr: #, method: #, '
1004 r'receiver: #, pattern: #, '
1005 r"""match: function(msg) {
1006 var match = new RegExp(this.pattern).exec(msg);
1007 if (!match) return void 0;
1008 var result = {};
1009 if (this.arguments != -1) result.arguments = match[this.arguments + 1];
1010 if (this.argumentsExpr != -1) result.argumentsExpr = match[this.argumentsExpr + 1];
1011 if (this.expr != -1) result.expr = match[this.expr + 1];
1012 if (this.method != -1) result.method = match[this.method + 1];
1013 if (this.receiver != -1) result.receiver = match[this.receiver + 1];
1014 return result;
1015 }}""",
1016 arguments,
1017 argumentsExpr,
1018 expr,
1019 method,
1020 receiver,
1021 pattern);
1022 }
1023 }
1024
1025 class NullError implements NoSuchMethodError {
1026 final String _message;
1027 final String _method;
1028
1029 NullError(this._message, match)
1030 : this._method = JS('', '#.method', match);
1031
1032 String toString() {
1033 if (_method == null) return 'NullError: $_message';
1034 return 'NullError: Cannot call "$_method" on null';
1035 }
1036 }
1037
1038 class JsNoSuchMethodError implements NoSuchMethodError {
1039 final String _message;
1040 final String _method;
1041 final String _receiver;
1042
1043 JsNoSuchMethodError(this._message, match)
1044 : this._method = JS('', '#.method', match),
1045 this._receiver = JS('', '#.receiver', match);
1046
1047 String toString() {
1048 if (_method == null) return 'NoSuchMethodError: $_message';
1049 if (_receiver == null) {
1050 return 'NoSuchMethodError: Cannot call "$_method" ($_message)';
1051 }
1052 return 'NoSuchMethodError: Cannot call "$_method" on "$_receiver" '
1053 '($_message)';
1054 }
1055 }
1056
965 /** 1057 /**
966 * Called from catch blocks in generated code to extract the Dart 1058 * Called from catch blocks in generated code to extract the Dart
967 * exception from the thrown value. The thrown value may have been 1059 * exception from the thrown value. The thrown value may have been
968 * created by [$throw] or it may be a 'native' JS exception. 1060 * created by [$throw] or it may be a 'native' JS exception.
969 * 1061 *
970 * Some native exceptions are mapped to new Dart instances, others are 1062 * Some native exceptions are mapped to new Dart instances, others are
971 * returned unmodified. 1063 * returned unmodified.
972 */ 1064 */
973 unwrapException(ex) { 1065 unwrapException(ex) {
974 // Note that we are checking if the object has the property. If it 1066 // Note that we are checking if the object has the property. If it
975 // has, it could be set to null if the thrown value is null. 1067 // has, it could be set to null if the thrown value is null.
976 if (JS('bool', r'"dartException" in #', ex)) { 1068 if (JS('bool', r'"dartException" in #', ex)) {
977 return JS('', r'#.dartException', ex); 1069 return JS('', r'#.dartException', ex);
978 } 1070 }
979 1071
980 // Grab hold of the exception message. This field is available on 1072 // Grab hold of the exception message. This field is available on
981 // all supported browsers. 1073 // all supported browsers.
982 var message = JS('var', r'#.message', ex); 1074 var message = JS('var', r'#.message', ex);
983 1075
984 if (JS('bool', r'# instanceof TypeError', ex)) { 1076 if (JS('bool', r'# instanceof TypeError', ex)) {
985 // The type and arguments fields are Chrome specific but they 1077 var match;
986 // allow us to get very detailed information about what kind of 1078 var nsme = TypeErrorDecoder.noSuchMethodPattern;
987 // exception occurred. 1079 var nul = TypeErrorDecoder.nullErrorPattern;
988 var type = JS('var', r'#.type', ex); 1080 var undef = TypeErrorDecoder.undefinedErrorPattern;
989 var name = JS('var', r'#.arguments ? #.arguments[0] : ""', ex, ex); 1081 if ((match = JS('', '#.match(#)', nsme, message)) != null) {
ahe 2013/01/17 01:08:05 These fields have been removed from V8 (and Chrome
990 if (contains(message, 'JSNull') || 1082 return new JsNoSuchMethodError(message, match);
991 type == 'property_not_function' || 1083 } else if ((match = JS('', '#.match(#)', nul, message)) != null ||
992 type == 'called_non_callable' || 1084 (match = JS('', '#.match(#)', undef, message)) != null) {
993 type == 'non_object_property_call' || 1085 return new NullError(message, match);
994 type == 'non_object_property_load') {
995 return new NoSuchMethodError(null, name, [], {});
996 } else if (type == 'undefined_method') {
997 return new NoSuchMethodError('', name, [], {});
998 } 1086 }
999 1087
1000 var ieErrorCode = JS('int', '#.number & 0xffff', ex); 1088 var ieErrorCode = JS('int', '#.number & 0xffff', ex);
1001 var ieFacilityNumber = JS('int', '#.number>>16 & 0x1FFF', ex); 1089 var ieFacilityNumber = JS('int', '#.number>>16 & 0x1FFF', ex);
1002 // If we cannot use [type] to determine what kind of exception 1090 if (ieErrorCode == 438 && ieFacilityNumber == 10) {
1003 // we're dealing with we fall back on looking at the exception 1091 // Object doesn't support property or method 'foo' which sets the error
1004 // message if it is available and a string. 1092 // code 438 in IE.
1005 if (message is String) { 1093 return new NoSuchMethodError('', '<unknown>', [], {});
1006 if (message.endsWith('is null') ||
1007 message.endsWith('is undefined') ||
1008 message.endsWith('is null or undefined') ||
1009 message.endsWith('of undefined') ||
1010 message.endsWith('of null')) {
1011 return new NoSuchMethodError(null, '<unknown>', [], {});
1012 } else if (contains(message, ' has no method ') ||
1013 contains(message, ' is not a function') ||
1014 (ieErrorCode == 438 && ieFacilityNumber == 10)) {
1015 // Examples:
1016 // x.foo is not a function
1017 // 'undefined' is not a function (evaluating 'x.foo(1,2,3)')
1018 // Object doesn't support property or method 'foo' which sets the error
1019 // code 438 in IE.
1020 // TODO(kasperl): Compute the right name if possible.
1021 return new NoSuchMethodError('', '<unknown>', [], {});
1022 }
1023 } 1094 }
1024 1095
1025 // If we cannot determine what kind of error this is, we fall back 1096 // If we cannot determine what kind of error this is, we fall back
1026 // to reporting this as a generic exception. It's probably better 1097 // to reporting this as a generic exception. It's probably better
1027 // than nothing. 1098 // than nothing.
1028 return new Exception(message is String ? message : ''); 1099 return new Exception(message is String ? message : '');
1029 } 1100 }
1030 1101
1031 if (JS('bool', r'# instanceof RangeError', ex)) { 1102 if (JS('bool', r'# instanceof RangeError', ex)) {
1032 if (message is String && contains(message, 'call stack')) { 1103 if (message is String && contains(message, 'call stack')) {
(...skipping 615 matching lines...) Expand 10 before | Expand all | Expand 10 after
1648 // A list representing a type with arguments. 1719 // A list representing a type with arguments.
1649 return getTypeArgumentAsString(type); 1720 return getTypeArgumentAsString(type);
1650 } else { 1721 } else {
1651 // A reference to the constructor. 1722 // A reference to the constructor.
1652 return getConstructorName(type); 1723 return getConstructorName(type);
1653 } 1724 }
1654 } 1725 }
1655 1726
1656 String joinArguments(var types, int startIndex) { 1727 String joinArguments(var types, int startIndex) {
1657 bool firstArgument = true; 1728 bool firstArgument = true;
1658 StringBuffer buffer = new StringBuffer(); 1729 String result = '';
1659 for (int index = startIndex; index < types.length; index++) { 1730 for (int index = startIndex; index < types.length; index++) {
1660 if (firstArgument) { 1731 if (firstArgument) {
1661 firstArgument = false; 1732 firstArgument = false;
1662 } else { 1733 } else {
1663 buffer. add(', '); 1734 result = '$result, ';
1664 } 1735 }
1665 var argument = types[index]; 1736 var argument = types[index];
1666 buffer.add(runtimeTypeToString(argument)); 1737 result = '$result${runtimeTypeToString(argument)}';
1667 } 1738 }
1668 return buffer.toString(); 1739 return result;
1669 } 1740 }
1670 1741
1671 String getRuntimeTypeString(var object) { 1742 String getRuntimeTypeString(var object) {
1672 String className = isJsArray(object) ? 'List' : getClassName(object); 1743 String className = isJsArray(object) ? 'List' : getClassName(object);
1673 var typeInfo = JS('var', r'#.builtin$typeInfo', object); 1744 var typeInfo = JS('var', r'#.builtin$typeInfo', object);
1674 if (typeInfo == null) return className; 1745 if (typeInfo == null) return className;
1675 return "$className<${joinArguments(typeInfo, 0)}>"; 1746 return "$className<${joinArguments(typeInfo, 0)}>";
1676 } 1747 }
1677 1748
1678 /** 1749 /**
(...skipping 29 matching lines...) Expand all
1708 if (len != t.length) return false; 1779 if (len != t.length) return false;
1709 for (int i = 1; i < len; i++) { 1780 for (int i = 1; i < len; i++) {
1710 if (!isSubtype(s[i], t[i])) { 1781 if (!isSubtype(s[i], t[i])) {
1711 return false; 1782 return false;
1712 } 1783 }
1713 } 1784 }
1714 return true; 1785 return true;
1715 } 1786 }
1716 1787
1717 createRuntimeType(String name) => new TypeImpl(name); 1788 createRuntimeType(String name) => new TypeImpl(name);
OLDNEW
« no previous file with comments | « no previous file | dart/sdk/lib/_internal/compiler/implementation/lib/regexp_helper.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698