| OLD | NEW |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, 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 part of js_backend; | 5 part of js_backend; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * Assigns JavaScript identifiers to Dart variables, class-names and members. | 8 * Assigns JavaScript identifiers to Dart variables, class-names and members. |
| 9 */ | 9 */ |
| 10 class MinifyNamer extends Namer with _MinifiedFieldNamer, | 10 class MinifyNamer extends Namer with _MinifiedFieldNamer, |
| (...skipping 16 matching lines...) Expand all Loading... |
| 27 final ALPHABET_CHARACTERS = 52; // a-zA-Z. | 27 final ALPHABET_CHARACTERS = 52; // a-zA-Z. |
| 28 final ALPHANUMERIC_CHARACTERS = 62; // a-zA-Z0-9. | 28 final ALPHANUMERIC_CHARACTERS = 62; // a-zA-Z0-9. |
| 29 | 29 |
| 30 /// You can pass an invalid identifier to this and unlike its non-minifying | 30 /// You can pass an invalid identifier to this and unlike its non-minifying |
| 31 /// counterpart it will never return the proposedName as the new fresh name. | 31 /// counterpart it will never return the proposedName as the new fresh name. |
| 32 /// | 32 /// |
| 33 /// [sanitizeForNatives] and [sanitizeForAnnotations] are ignored because the | 33 /// [sanitizeForNatives] and [sanitizeForAnnotations] are ignored because the |
| 34 /// minified names will always avoid clashing with annotated names or natives. | 34 /// minified names will always avoid clashing with annotated names or natives. |
| 35 @override | 35 @override |
| 36 String _generateFreshStringForName(String proposedName, | 36 String _generateFreshStringForName(String proposedName, |
| 37 Set<String> usedNames, | 37 NamingScope scope, |
| 38 Map<String, String> suggestedNames, | |
| 39 {bool sanitizeForNatives: false, | 38 {bool sanitizeForNatives: false, |
| 40 bool sanitizeForAnnotations: false}) { | 39 bool sanitizeForAnnotations: false}) { |
| 41 String freshName; | 40 String freshName; |
| 42 String suggestion = suggestedNames[proposedName]; | 41 String suggestion = scope.suggestName(proposedName); |
| 43 if (suggestion != null && !usedNames.contains(suggestion)) { | 42 if (suggestion != null && scope.isUnused(suggestion)) { |
| 44 freshName = suggestion; | 43 freshName = suggestion; |
| 45 } else { | 44 } else { |
| 46 freshName = _getUnusedName(proposedName, usedNames, | 45 freshName = _getUnusedName(proposedName, scope); |
| 47 suggestedNames.values); | |
| 48 } | 46 } |
| 49 usedNames.add(freshName); | 47 scope.registerUse(freshName); |
| 50 return freshName; | 48 return freshName; |
| 51 } | 49 } |
| 52 | 50 |
| 53 // From issue 7554. These should not be used on objects (as instance | 51 // From issue 7554. These should not be used on objects (as instance |
| 54 // variables) because they clash with names from the DOM. However, it is | 52 // variables) because they clash with names from the DOM. However, it is |
| 55 // OK to use them as fields, as we only access fields directly if we know | 53 // OK to use them as fields, as we only access fields directly if we know |
| 56 // the receiver type. | 54 // the receiver type. |
| 57 static const List<String> _reservedNativeProperties = const <String>[ | 55 static const List<String> _reservedNativeProperties = const <String>[ |
| 58 'a', 'b', 'c', 'd', 'e', 'f', 'r', 'x', 'y', 'z', 'Q', | 56 'a', 'b', 'c', 'd', 'e', 'f', 'r', 'x', 'y', 'z', 'Q', |
| 59 // 2-letter: | 57 // 2-letter: |
| (...skipping 18 matching lines...) Expand all Loading... |
| 78 'show', 'SINE', 'size', 'span', 'stat', 'step', 'stop', 'tags', 'text', | 76 'show', 'SINE', 'size', 'span', 'stat', 'step', 'stop', 'tags', 'text', |
| 79 'Text', 'time', 'type', 'view', 'warn', 'wrap', 'ZERO']; | 77 'Text', 'time', 'type', 'view', 'warn', 'wrap', 'ZERO']; |
| 80 | 78 |
| 81 void reserveBackendNames() { | 79 void reserveBackendNames() { |
| 82 for (String name in _reservedNativeProperties) { | 80 for (String name in _reservedNativeProperties) { |
| 83 if (name.length < 2) { | 81 if (name.length < 2) { |
| 84 // Ensure the 1-letter names are disambiguated to the same name. | 82 // Ensure the 1-letter names are disambiguated to the same name. |
| 85 String disambiguatedName = name; | 83 String disambiguatedName = name; |
| 86 reservePublicMemberName(name, disambiguatedName); | 84 reservePublicMemberName(name, disambiguatedName); |
| 87 } | 85 } |
| 88 usedInstanceNames.add(name); | 86 instanceScope.registerUse(name); |
| 89 // Getter and setter names are autogenerated by prepending 'g' and 's' to | 87 // Getter and setter names are autogenerated by prepending 'g' and 's' to |
| 90 // field names. Therefore there are some field names we don't want to | 88 // field names. Therefore there are some field names we don't want to |
| 91 // use. It is implicit in the next line that the banned prefix is | 89 // use. It is implicit in the next line that the banned prefix is |
| 92 // only one character. | 90 // only one character. |
| 93 if (_hasBannedPrefix(name)) usedInstanceNames.add(name.substring(1)); | 91 if (_hasBannedPrefix(name)) { |
| 92 instanceScope.registerUse(name.substring(1)); |
| 93 } |
| 94 } | 94 } |
| 95 | 95 |
| 96 // These popular names are present in most programs and deserve | 96 // These popular names are present in most programs and deserve |
| 97 // single character minified names. We could determine the popular names | 97 // single character minified names. We could determine the popular names |
| 98 // individually per program, but that would mean that the output of the | 98 // individually per program, but that would mean that the output of the |
| 99 // minifier was less stable from version to version of the program being | 99 // minifier was less stable from version to version of the program being |
| 100 // minified. | 100 // minified. |
| 101 _populateSuggestedNames( | 101 _populateSuggestedNames( |
| 102 suggestedInstanceNames, | 102 instanceScope, |
| 103 usedInstanceNames, | |
| 104 const <String>[ | 103 const <String>[ |
| 105 r'$add', r'add$1', r'$and', | 104 r'$add', r'add$1', r'$and', |
| 106 r'$or', | 105 r'$or', |
| 107 r'current', r'$shr', r'$eq', r'$ne', | 106 r'current', r'$shr', r'$eq', r'$ne', |
| 108 r'$index', r'$indexSet', | 107 r'$index', r'$indexSet', |
| 109 r'$xor', r'clone$0', | 108 r'$xor', r'clone$0', |
| 110 r'iterator', r'length', | 109 r'iterator', r'length', |
| 111 r'$lt', r'$gt', r'$le', r'$ge', | 110 r'$lt', r'$gt', r'$le', r'$ge', |
| 112 r'moveNext$0', r'node', r'on', r'$negate', r'push', r'self', | 111 r'moveNext$0', r'node', r'on', r'$negate', r'push', r'self', |
| 113 r'start', r'target', r'$shl', r'value', r'width', r'style', | 112 r'start', r'target', r'$shl', r'value', r'width', r'style', |
| 114 r'noSuchMethod$1', r'$mul', r'$div', r'$sub', r'$not', r'$mod', | 113 r'noSuchMethod$1', r'$mul', r'$div', r'$sub', r'$not', r'$mod', |
| 115 r'$tdiv', r'toString$0']); | 114 r'$tdiv', r'toString$0']); |
| 116 | 115 |
| 117 _populateSuggestedNames( | 116 _populateSuggestedNames( |
| 118 suggestedGlobalNames, | 117 globalScope, |
| 119 usedGlobalNames, | |
| 120 const <String>[ | 118 const <String>[ |
| 121 r'Object', 'wrapException', r'$eq', r'S', r'ioore', | 119 r'Object', 'wrapException', r'$eq', r'S', r'ioore', |
| 122 r'UnsupportedError$', r'length', r'$sub', | 120 r'UnsupportedError$', r'length', r'$sub', |
| 123 r'$add', r'$gt', r'$ge', r'$lt', r'$le', r'add', | 121 r'$add', r'$gt', r'$ge', r'$lt', r'$le', r'add', |
| 124 r'iae', | 122 r'iae', |
| 125 r'ArgumentError$', r'BoundClosure', r'Closure', r'StateError$', | 123 r'ArgumentError$', r'BoundClosure', r'Closure', r'StateError$', |
| 126 r'getInterceptor', r'max', r'$mul', | 124 r'getInterceptor', r'max', r'$mul', |
| 127 r'Map', r'Key_Key', r'$div', | 125 r'Map', r'Key_Key', r'$div', |
| 128 r'List_List$from', | 126 r'List_List$from', |
| 129 r'LinkedHashMap_LinkedHashMap$_empty', | 127 r'LinkedHashMap_LinkedHashMap$_empty', |
| 130 r'LinkedHashMap_LinkedHashMap$_literal', | 128 r'LinkedHashMap_LinkedHashMap$_literal', |
| 131 r'min', | 129 r'min', |
| 132 r'RangeError$value', r'JSString', r'JSNumber', | 130 r'RangeError$value', r'JSString', r'JSNumber', |
| 133 r'JSArray', r'createInvocationMirror', r'String', | 131 r'JSArray', r'createInvocationMirror', r'String', |
| 134 r'setRuntimeTypeInfo', r'createRuntimeType' | 132 r'setRuntimeTypeInfo', r'createRuntimeType' |
| 135 ]); | 133 ]); |
| 136 } | 134 } |
| 137 | 135 |
| 138 void _populateSuggestedNames(Map<String, String> suggestionMap, | 136 void _populateSuggestedNames(NamingScope scope, |
| 139 Set<String> used, | |
| 140 List<String> suggestions) { | 137 List<String> suggestions) { |
| 141 int c = $a - 1; | 138 int c = $a - 1; |
| 142 String letter; | 139 String letter; |
| 143 for (String name in suggestions) { | 140 for (String name in suggestions) { |
| 144 do { | 141 do { |
| 145 assert(c != $Z); | 142 assert(c != $Z); |
| 146 c = (c == $z) ? $A : c + 1; | 143 c = (c == $z) ? $A : c + 1; |
| 147 letter = new String.fromCharCodes([c]); | 144 letter = new String.fromCharCodes([c]); |
| 148 } while (_hasBannedPrefix(letter) || used.contains(letter)); | 145 } while (_hasBannedPrefix(letter) || scope.isUsed(letter)); |
| 149 assert(suggestionMap[name] == null); | 146 assert(!scope.hasSuggestion(name)); |
| 150 suggestionMap[name] = letter; | 147 scope.addSuggestion(name, letter); |
| 151 } | 148 } |
| 152 } | 149 } |
| 153 | 150 |
| 154 | 151 |
| 155 // This gets a minified name based on a hash of the proposed name. This | 152 // This gets a minified name based on a hash of the proposed name. This |
| 156 // is slightly less efficient than just getting the next name in a series, | 153 // is slightly less efficient than just getting the next name in a series, |
| 157 // but it means that small changes in the input program will give smallish | 154 // but it means that small changes in the input program will give smallish |
| 158 // changes in the output, which can be useful for diffing etc. | 155 // changes in the output, which can be useful for diffing etc. |
| 159 String _getUnusedName(String proposedName, Set<String> usedNames, | 156 String _getUnusedName(String proposedName, NamingScope scope) { |
| 160 Iterable<String> suggestions) { | |
| 161 int hash = _calculateHash(proposedName); | 157 int hash = _calculateHash(proposedName); |
| 162 // Avoid very small hashes that won't try many names. | 158 // Avoid very small hashes that won't try many names. |
| 163 hash = hash < 1000 ? hash * 314159 : hash; // Yes, it's prime. | 159 hash = hash < 1000 ? hash * 314159 : hash; // Yes, it's prime. |
| 164 | 160 |
| 165 // Try other n-character names based on the hash. We try one to three | 161 // Try other n-character names based on the hash. We try one to three |
| 166 // character identifiers. For each length we try around 10 different names | 162 // character identifiers. For each length we try around 10 different names |
| 167 // in a predictable order determined by the proposed name. This is in order | 163 // in a predictable order determined by the proposed name. This is in order |
| 168 // to make the renamer stable: small changes in the input should nornally | 164 // to make the renamer stable: small changes in the input should nornally |
| 169 // result in relatively small changes in the output. | 165 // result in relatively small changes in the output. |
| 170 for (int n = 1; n <= 3; n++) { | 166 for (int n = 1; n <= 3; n++) { |
| 171 int h = hash; | 167 int h = hash; |
| 172 while (h > 10) { | 168 while (h > 10) { |
| 173 List<int> codes = <int>[_letterNumber(h)]; | 169 List<int> codes = <int>[_letterNumber(h)]; |
| 174 int h2 = h ~/ ALPHABET_CHARACTERS; | 170 int h2 = h ~/ ALPHABET_CHARACTERS; |
| 175 for (int i = 1; i < n; i++) { | 171 for (int i = 1; i < n; i++) { |
| 176 codes.add(_alphaNumericNumber(h2)); | 172 codes.add(_alphaNumericNumber(h2)); |
| 177 h2 ~/= ALPHANUMERIC_CHARACTERS; | 173 h2 ~/= ALPHANUMERIC_CHARACTERS; |
| 178 } | 174 } |
| 179 final candidate = new String.fromCharCodes(codes); | 175 final candidate = new String.fromCharCodes(codes); |
| 180 if (!usedNames.contains(candidate) && | 176 if (scope.isUnused(candidate) && |
| 181 !jsReserved.contains(candidate) && | 177 !jsReserved.contains(candidate) && |
| 182 !_hasBannedPrefix(candidate) && | 178 !_hasBannedPrefix(candidate) && |
| 183 (n != 1 || !suggestions.contains(candidate))) { | 179 (n != 1 || scope.isSuggestion(candidate))) { |
| 184 return candidate; | 180 return candidate; |
| 185 } | 181 } |
| 186 // Try again with a slightly different hash. After around 10 turns | 182 // Try again with a slightly different hash. After around 10 turns |
| 187 // around this loop h is zero and we try a longer name. | 183 // around this loop h is zero and we try a longer name. |
| 188 h ~/= 7; | 184 h ~/= 7; |
| 189 } | 185 } |
| 190 } | 186 } |
| 191 return _badName(hash, usedNames); | 187 return _badName(hash, scope); |
| 192 } | 188 } |
| 193 | 189 |
| 194 /// Instance members starting with g and s are reserved for getters and | 190 /// Instance members starting with g and s are reserved for getters and |
| 195 /// setters. | 191 /// setters. |
| 196 static bool _hasBannedPrefix(String name) { | 192 static bool _hasBannedPrefix(String name) { |
| 197 int code = name.codeUnitAt(0); | 193 int code = name.codeUnitAt(0); |
| 198 return code == $g || code == $s; | 194 return code == $g || code == $s; |
| 199 } | 195 } |
| 200 | 196 |
| 201 int _calculateHash(String name) { | 197 int _calculateHash(String name) { |
| 202 int h = 0; | 198 int h = 0; |
| 203 for (int i = 0; i < name.length; i++) { | 199 for (int i = 0; i < name.length; i++) { |
| 204 h += name.codeUnitAt(i); | 200 h += name.codeUnitAt(i); |
| 205 h &= 0xffffffff; | 201 h &= 0xffffffff; |
| 206 h += h << 10; | 202 h += h << 10; |
| 207 h &= 0xffffffff; | 203 h &= 0xffffffff; |
| 208 h ^= h >> 6; | 204 h ^= h >> 6; |
| 209 h &= 0xffffffff; | 205 h &= 0xffffffff; |
| 210 } | 206 } |
| 211 return h; | 207 return h; |
| 212 } | 208 } |
| 213 | 209 |
| 214 /// Remember bad hashes to avoid using a the same character with long numbers | 210 /// Remember bad hashes to avoid using a the same character with long numbers |
| 215 /// for frequent hashes. For example, `closure` is a very common name. | 211 /// for frequent hashes. For example, `closure` is a very common name. |
| 216 Map<int, int> _badNames = new Map<int, int>(); | 212 Map<int, int> _badNames = new Map<int, int>(); |
| 217 | 213 |
| 218 /// If we can't find a hash based name in the three-letter space, then base | 214 /// If we can't find a hash based name in the three-letter space, then base |
| 219 /// the name on a letter and a counter. | 215 /// the name on a letter and a counter. |
| 220 String _badName(int hash, Set<String> usedNames) { | 216 String _badName(int hash, NamingScope scope) { |
| 221 int count = _badNames.putIfAbsent(hash, () => 0); | 217 int count = _badNames.putIfAbsent(hash, () => 0); |
| 222 String startLetter = | 218 String startLetter = |
| 223 new String.fromCharCodes([_letterNumber(hash + count)]); | 219 new String.fromCharCodes([_letterNumber(hash + count)]); |
| 224 _badNames[hash] = count + 1; | 220 _badNames[hash] = count + 1; |
| 225 String name; | 221 String name; |
| 226 int i = 0; | 222 int i = 0; |
| 227 do { | 223 do { |
| 228 name = "$startLetter${i++}"; | 224 name = "$startLetter${i++}"; |
| 229 } while (usedNames.contains(name)); | 225 } while (scope.isUsed(name)); |
| 230 // We don't need to check for banned prefix because the name is in the form | 226 // We don't need to check for banned prefix because the name is in the form |
| 231 // xnnn, where nnn is a number. There can be no getter or setter called | 227 // xnnn, where nnn is a number. There can be no getter or setter called |
| 232 // gnnn since that would imply a numeric field name. | 228 // gnnn since that would imply a numeric field name. |
| 233 return name; | 229 return name; |
| 234 } | 230 } |
| 235 | 231 |
| 236 int _letterNumber(int x) { | 232 int _letterNumber(int x) { |
| 237 if (x >= ALPHABET_CHARACTERS) x %= ALPHABET_CHARACTERS; | 233 if (x >= ALPHABET_CHARACTERS) x %= ALPHABET_CHARACTERS; |
| 238 if (x < 26) return $a + x; | 234 if (x < 26) return $a + x; |
| 239 return $A + x - 26; | 235 return $A + x - 26; |
| (...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 344 : selector.isSetter ? r"$set" : ""; | 340 : selector.isSetter ? r"$set" : ""; |
| 345 String callSuffix = | 341 String callSuffix = |
| 346 selector.isCall ? callSuffixForStructure(selector.callStructure).join() | 342 selector.isCall ? callSuffixForStructure(selector.callStructure).join() |
| 347 : ""; | 343 : ""; |
| 348 String suffix = suffixForGetInterceptor(classes); | 344 String suffix = suffixForGetInterceptor(classes); |
| 349 String fullName = "\$intercepted$prefix\$$root$callSuffix\$$suffix"; | 345 String fullName = "\$intercepted$prefix\$$root$callSuffix\$$suffix"; |
| 350 return _disambiguateInternalGlobal(fullName); | 346 return _disambiguateInternalGlobal(fullName); |
| 351 } | 347 } |
| 352 } | 348 } |
| 353 | 349 |
| OLD | NEW |