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 |