| | 78 | |
| | 79 | class TokenMapper { |
| | 80 | private ArrayList functionBracePositions = new ArrayList(); |
| | 81 | |
| | 82 | /** |
| | 83 | * Map of all replaced tokens |
| | 84 | */ |
| | 85 | private ArrayList replacedTokens = new ArrayList(); |
| | 86 | |
| | 87 | /** |
| | 88 | * Collection of Function nodes |
| | 89 | */ |
| | 90 | private static ObjArray funcObjects = new ObjArray(); |
| | 91 | |
| | 92 | /** |
| | 93 | * Map of each Function node and all the variables in its current function |
| | 94 | * scope, other variables found while traversing the prototype chain and |
| | 95 | * variables found in the top-level scope. |
| | 96 | */ |
| | 97 | private static ArrayList functionVarMappings = new ArrayList(); |
| | 98 | |
| | 99 | public int functionNum = 0; |
| | 100 | |
| | 101 | private int parentScope = 0; |
| | 102 | |
| | 103 | private int lastTokenCount = 0; |
| | 104 | |
| | 105 | /** |
| | 106 | * Reset the static members for the TokenMapper. |
| | 107 | */ |
| | 108 | public static void reset() { |
| | 109 | funcObjects = new ObjArray(); |
| | 110 | functionVarMappings = new ArrayList(); |
| | 111 | } |
| | 112 | |
| | 113 | /** |
| | 114 | * Generate new compressed tokens |
| | 115 | * <p> |
| | 116 | * |
| | 117 | * @param token |
| | 118 | * value of the string token |
| | 119 | * @param hasNewMapping |
| | 120 | * boolean value indicating a new variable binding |
| | 121 | * @return compressed token |
| | 122 | */ |
| | 123 | private String getMappedToken(String token, boolean hasNewMapping) { |
| | 124 | String newToken = null; |
| | 125 | HashMap tokens = null; |
| | 126 | String blank = new String(""); |
| | 127 | int localScope = functionBracePositions.size() - 1; |
| | 128 | |
| | 129 | String oldToken = getPreviousTokenMapping(token, hasNewMapping); |
| | 130 | |
| | 131 | if (!oldToken.equalsIgnoreCase(blank)) { |
| | 132 | return oldToken; |
| | 133 | } else if ((hasNewMapping || isInScopeChain(token))) { |
| | 134 | lastTokenCount++; |
| | 135 | newToken = new String("_" + Integer.toHexString(lastTokenCount)); |
| | 136 | if (newToken.length() >= token.length()) { |
| | 137 | newToken = token; |
| | 138 | } |
| | 139 | if (hasNewMapping) { |
| | 140 | tokens = (HashMap) replacedTokens.get(localScope); |
| | 141 | } else { |
| | 142 | tokens = (HashMap) replacedTokens.get(parentScope); |
| | 143 | } |
| | 144 | |
| | 145 | tokens.put(token, newToken); |
| | 146 | return newToken; |
| | 147 | } |
| | 148 | return token; |
| | 149 | } |
| | 150 | |
| | 151 | /** |
| | 152 | * Checks for variable names in prototype chain |
| | 153 | * <p> |
| | 154 | * |
| | 155 | * @param token |
| | 156 | * value of the string token |
| | 157 | * @return boolean value indicating if the token is present in the chained |
| | 158 | * scope |
| | 159 | */ |
| | 160 | private boolean isInScopeChain(String token) { |
| | 161 | int scope = functionBracePositions.size(); |
| | 162 | HashMap chainedScopeVars = (HashMap) functionVarMappings |
| | 163 | .get(functionNum); |
| | 164 | if (!chainedScopeVars.isEmpty()) { |
| | 165 | for (int i = scope; i > 0; i--) { |
| | 166 | if (chainedScopeVars.containsKey(new Integer(i))) { |
| | 167 | parentScope = i - 1; |
| | 168 | List temp = Arrays.asList((String[]) chainedScopeVars |
| | 169 | .get(new Integer(i))); |
| | 170 | if (temp.indexOf(token) != -1) { |
| | 171 | return true; |
| | 172 | } |
| | 173 | } |
| | 174 | } |
| | 175 | } |
| | 176 | return false; |
| | 177 | } |
| | 178 | |
| | 179 | /** |
| | 180 | * Checks previous token mapping |
| | 181 | * <p> |
| | 182 | * |
| | 183 | * @param token |
| | 184 | * value of the string token |
| | 185 | * @param hasNewMapping |
| | 186 | * boolean value indicating a new variable binding |
| | 187 | * @return string value of the previous token or blank string |
| | 188 | */ |
| | 189 | private String getPreviousTokenMapping(String token, boolean hasNewMapping) { |
| | 190 | String result = new String(""); |
| | 191 | int scope = replacedTokens.size() - 1; |
| | 192 | |
| | 193 | if (scope < 0) { |
| | 194 | return result; |
| | 195 | } |
| | 196 | |
| | 197 | if (hasNewMapping) { |
| | 198 | HashMap tokens = (HashMap) (replacedTokens.get(scope)); |
| | 199 | if (tokens.containsKey(token)) { |
| | 200 | result = (String) tokens.get(token); |
| | 201 | return result; |
| | 202 | } |
| | 203 | } else { |
| | 204 | for (int i = scope; i > -1; i--) { |
| | 205 | HashMap tokens = (HashMap) (replacedTokens.get(i)); |
| | 206 | if (tokens.containsKey(token)) { |
| | 207 | result = (String) tokens.get(token); |
| | 208 | return result; |
| | 209 | } |
| | 210 | } |
| | 211 | } |
| | 212 | return result; |
| | 213 | } |
| | 214 | |
| | 215 | /** |
| | 216 | * Generate mappings for each Function node and parameters and variables |
| | 217 | * names associated with it. |
| | 218 | * <p> |
| | 219 | * |
| | 220 | * @param parseTree |
| | 221 | * Mapping for each function node and corresponding parameters & |
| | 222 | * variables names |
| | 223 | */ |
| | 224 | private void collectFunctionMappings(ScriptOrFnNode parseTree) { |
| | 225 | int level = -1; |
| | 226 | collectFuncNodes(parseTree, level); |
| | 227 | } |
| | 228 | |
| | 229 | /** |
| | 230 | * Recursive method to traverse all Function nodes |
| | 231 | * <p> |
| | 232 | * |
| | 233 | * @param parseTree |
| | 234 | * Mapping for each function node and corresponding parameters & |
| | 235 | * variables names |
| | 236 | * @param level |
| | 237 | * scoping level |
| | 238 | */ |
| | 239 | private static void collectFuncNodes(ScriptOrFnNode parseTree, int level) { |
| | 240 | level++; |
| | 241 | functionVarMappings.add(new HashMap()); |
| | 242 | |
| | 243 | HashMap bindingNames = (HashMap) functionVarMappings |
| | 244 | .get(functionVarMappings.size() - 1); |
| | 245 | bindingNames.put(new Integer(level), parseTree.getParamAndVarNames()); |
| | 246 | funcObjects.add(parseTree); |
| | 247 | |
| | 248 | int nestedCount = parseTree.getFunctionCount(); |
| | 249 | for (int i = 0; i != nestedCount; ++i) { |
| | 250 | collectFuncNodes(parseTree.getFunctionNode(i), level); |
| | 251 | bindingNames = (HashMap) functionVarMappings |
| | 252 | .get(functionVarMappings.size() - 1); |
| | 253 | bindingNames.put(new Integer(level), parseTree |
| | 254 | .getParamAndVarNames()); |
| | 255 | } |
| | 256 | } |
| | 257 | |
| | 258 | /** |
| | 259 | * Compress the script |
| | 260 | * <p> |
| | 261 | * |
| | 262 | * @param encodedSource |
| | 263 | * encoded source string |
| | 264 | * @param offset |
| | 265 | * position within the encoded source |
| | 266 | * @param asQuotedString |
| | 267 | * boolean value indicating a quoted string |
| | 268 | * @param sb |
| | 269 | * String buffer reference |
| | 270 | * @param prevToken |
| | 271 | * Previous token in encoded source |
| | 272 | * @param inArgsList |
| | 273 | * boolean value indicating position inside arguments list |
| | 274 | * @param currentLevel |
| | 275 | * embeded function level |
| | 276 | * @param parseTree |
| | 277 | * Mapping of each function node and corresponding parameters & |
| | 278 | * variables names |
| | 279 | * @return compressed script |
| | 280 | */ |
| | 281 | public int sourceCompress(String encodedSource, int offset, |
| | 282 | boolean asQuotedString, StringBuffer sb, int prevToken, |
| | 283 | boolean inArgsList, int currentLevel, ScriptOrFnNode parseTree) { |
| | 284 | |
| | 285 | boolean hasNewMapping = false; |
| | 286 | |
| | 287 | if (functionVarMappings.isEmpty()) |
| | 288 | collectFunctionMappings(parseTree); |
| | 289 | |
| | 290 | int length = encodedSource.charAt(offset); |
| | 291 | ++offset; |
| | 292 | if ((0x8000 & length) != 0) { |
| | 293 | length = ((0x7FFF & length) << 16) | encodedSource.charAt(offset); |
| | 294 | ++offset; |
| | 295 | } |
| | 296 | if (sb != null) { |
| | 297 | String str = encodedSource.substring(offset, offset + length); |
| | 298 | String sourceStr = new String(str); |
| | 299 | if ((prevToken == Token.VAR) || (inArgsList)) { |
| | 300 | hasNewMapping = true; |
| | 301 | } |
| | 302 | if (((functionBracePositions.size() > 0) && (currentLevel >= (((Integer) functionBracePositions |
| | 303 | .get(functionBracePositions.size() - 1)).intValue()))) |
| | 304 | || (inArgsList)) { |
| | 305 | if (prevToken != Token.DOT) { |
| | 306 | str = this.getMappedToken(str, hasNewMapping); |
| | 307 | } |
| | 308 | } |
| | 309 | if ((!inArgsList) && (asQuotedString)) { |
| | 310 | if ((prevToken == Token.LC) || (prevToken == Token.COMMA)) { |
| | 311 | str = sourceStr; |
| | 312 | } |
| | 313 | } |
| | 314 | if (!asQuotedString) { |
| | 315 | sb.append(str); |
| | 316 | } else { |
| | 317 | sb.append('"'); |
| | 318 | sb.append(ScriptRuntime.escapeString(str)); |
| | 319 | sb.append('"'); |
| | 320 | } |
| | 321 | } |
| | 322 | return offset + length; |
| | 323 | } |
| | 324 | |
| | 325 | public void enterNestingLevel(int braceNesting) { |
| | 326 | functionBracePositions.add(new Integer(braceNesting + 1)); |
| | 327 | replacedTokens.add(new HashMap()); |
| | 328 | } |
| | 329 | |
| | 330 | public void leaveNestingLevel(int braceNesting) { |
| | 331 | Integer bn = new Integer(braceNesting); |
| | 332 | |
| | 333 | if ((functionBracePositions.contains(bn)) |
| | 334 | && (replacedTokens.size() > 0)) { |
| | 335 | // remove our mappings now! |
| | 336 | int scopedSize = replacedTokens.size(); |
| | 337 | replacedTokens.remove(scopedSize - 1); |
| | 338 | functionBracePositions.remove(bn); |
| | 339 | } |
| | 340 | } |
| | 341 | } |
| | 342 | |
| | 343 | |
| | 540 | //Used to be private, but making it public so we |
| | 541 | //can reset the token state between compression runs. |
| | 542 | //Not very pretty. |
| | 543 | public static TokenMapper tm = new TokenMapper(); |
| | 544 | |
| | 545 | /** |
| | 546 | * Compress the script |
| | 547 | * <p> |
| | 548 | * |
| | 549 | * @param encodedSource encoded source string |
| | 550 | * @param flags Flags specifying format of decompilation output |
| | 551 | * @param properties Decompilation properties |
| | 552 | * @param parseTree Mapping for each function node and corresponding parameters & variables names |
| | 553 | * @return compressed script |
| | 554 | */ |
| | 555 | public static String compress(String encodedSource, int flags, |
| | 556 | UintMap properties, ScriptOrFnNode parseTree){ |
| | 557 | |
| | 558 | int length = encodedSource.length(); |
| | 559 | if (length == 0) { return ""; } |
| | 560 | int indent = properties.getInt(INITIAL_INDENT_PROP, 0); |
| | 561 | if (indent < 0) throw new IllegalArgumentException(); |
| | 562 | int indentGap = properties.getInt(INDENT_GAP_PROP, 4); |
| | 563 | if (indentGap < 0) throw new IllegalArgumentException(); |
| | 564 | int caseGap = properties.getInt(CASE_GAP_PROP, 2); |
| | 565 | if (caseGap < 0) throw new IllegalArgumentException(); |
| | 566 | StringBuffer result = new StringBuffer(); |
| | 567 | boolean justFunctionBody = (0 != (flags & Decompiler.ONLY_BODY_FLAG)); |
| | 568 | boolean toSource = (0 != (flags & Decompiler.TO_SOURCE_FLAG)); |
| | 569 | // Spew tokens in source, for debugging. |
| | 570 | // as TYPE number char |
| | 571 | if (printSource) { |
| | 572 | System.err.println("length:" + length); |
| | 573 | for (int i = 0; i < length; ++i) { |
| | 574 | // Note that tokenToName will fail unless Context.printTrees |
| | 575 | // is true. |
| | 576 | String tokenname = null; |
| | 577 | if (Token.printNames) { |
| | 578 | tokenname = Token.name(encodedSource.charAt(i)); |
| | 579 | } |
| | 580 | if (tokenname == null) { |
| | 581 | tokenname = "---"; |
| | 582 | } |
| | 583 | String pad = tokenname.length() > 7 |
| | 584 | ? "\t" |
| | 585 | : "\t\t"; |
| | 586 | System.err.println |
| | 587 | (tokenname |
| | 588 | + pad + (int)encodedSource.charAt(i) |
| | 589 | + "\t'" + ScriptRuntime.escapeString |
| | 590 | (encodedSource.substring(i, i+1)) |
| | 591 | + "'"); |
| | 592 | } |
| | 593 | System.err.println(); |
| | 594 | } |
| | 595 | int braceNesting = 0; |
| | 596 | boolean afterFirstEOL = false; |
| | 597 | int i = 0; |
| | 598 | int prevToken = 0; |
| | 599 | boolean primeFunctionNesting = false; |
| | 600 | boolean inArgsList = false; |
| | 601 | boolean primeInArgsList = false; |
| | 602 | int topFunctionType; |
| | 603 | if (encodedSource.charAt(i) == Token.SCRIPT) { |
| | 604 | ++i; |
| | 605 | topFunctionType = -1; |
| | 606 | } else { |
| | 607 | topFunctionType = encodedSource.charAt(i + 1); |
| | 608 | } |
| | 609 | if (!toSource) { |
| | 610 | // add an initial newline to exactly match js. |
| | 611 | // result.append('\n'); |
| | 612 | for (int j = 0; j < indent; j++){ |
| | 613 | // result.append(' '); |
| | 614 | result.append(""); |
| | 615 | } |
| | 616 | } else { |
| | 617 | if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) { |
| | 618 | result.append('('); |
| | 619 | } |
| | 620 | } |
| | 621 | while (i < length) { |
| | 622 | if(i>0){ |
| | 623 | prevToken = encodedSource.charAt(i-1); |
| | 624 | } |
| | 625 | // System.out.println(Token.name(getNext(source, length, i))); |
| | 626 | switch(encodedSource.charAt(i)) { |
| | 627 | case Token.NAME: |
| | 628 | case Token.REGEXP: // re-wrapped in '/'s in parser... |
| | 629 | int jumpPos = getSourceStringEnd(encodedSource, i+1); |
| | 630 | if(Token.OBJECTLIT == encodedSource.charAt(jumpPos)){ |
| | 631 | i = printSourceString(encodedSource, i + 1, false, result); |
| | 632 | }else{ |
| | 633 | i = tm.sourceCompress( encodedSource, i + 1, false, result, prevToken, |
| | 634 | inArgsList, braceNesting, parseTree); |
| | 635 | } |
| | 636 | continue; |
| | 637 | case Token.STRING: |
| | 638 | i = printSourceString(encodedSource, i + 1, true, result); |
| | 639 | continue; |
| | 640 | case Token.NUMBER: |
| | 641 | i = printSourceNumber(encodedSource, i + 1, result); |
| | 642 | continue; |
| | 643 | case Token.TRUE: |
| | 644 | result.append("true"); |
| | 645 | break; |
| | 646 | case Token.FALSE: |
| | 647 | result.append("false"); |
| | 648 | break; |
| | 649 | case Token.NULL: |
| | 650 | result.append("null"); |
| | 651 | break; |
| | 652 | case Token.THIS: |
| | 653 | result.append("this"); |
| | 654 | break; |
| | 655 | case Token.FUNCTION: |
| | 656 | ++i; // skip function type |
| | 657 | tm.functionNum++; |
| | 658 | primeInArgsList = true; |
| | 659 | primeFunctionNesting = true; |
| | 660 | result.append("function"); |
| | 661 | if (Token.LP != getNext(encodedSource, length, i)) { |
| | 662 | result.append(' '); |
| | 663 | } |
| | 664 | break; |
| | 665 | case FUNCTION_END: |
| | 666 | // Do nothing |
| | 667 | break; |
| | 668 | case Token.COMMA: |
| | 669 | result.append(","); |
| | 670 | break; |
| | 671 | case Token.LC: |
| | 672 | ++braceNesting; |
| | 673 | if (Token.EOL == getNext(encodedSource, length, i)){ |
| | 674 | indent += indentGap; |
| | 675 | } |
| | 676 | result.append('{'); |
| | 677 | // // result.append('\n'); |
| | 678 | break; |
| | 679 | case Token.RC: { |
| | 680 | tm.leaveNestingLevel(braceNesting); |
| | 681 | --braceNesting; |
| | 682 | /* don't print the closing RC if it closes the |
| | 683 | * toplevel function and we're called from |
| | 684 | * decompileFunctionBody. |
| | 685 | */ |
| | 686 | if(justFunctionBody && braceNesting == 0){ |
| | 687 | break; |
| | 688 | } |
| | 689 | // // result.append('\n'); |
| | 690 | result.append('}'); |
| | 691 | // // result.append(' '); |
| | 692 | switch (getNext(encodedSource, length, i)) { |
| | 693 | case Token.EOL: |
| | 694 | case FUNCTION_END: |
| | 695 | indent -= indentGap; |
| | 696 | break; |
| | 697 | case Token.WHILE: |
| | 698 | case Token.ELSE: |
| | 699 | indent -= indentGap; |
| | 700 | // result.append(' '); |
| | 701 | result.append(""); |
| | 702 | break; |
| | 703 | } |
| | 704 | break; |
| | 705 | } |
| | 706 | case Token.LP: |
| | 707 | if(primeInArgsList){ |
| | 708 | inArgsList = true; |
| | 709 | primeInArgsList = false; |
| | 710 | } |