| 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 | 
|---|