/* * Transforms obj["foo"] into obj.foo whenever possible, saving 3 bytes. */ public void optimizeObjectMemberAccess() { String tv; int i, length; JavaScriptToken token; for (i = 0, length = tokens.size(); i < length; i++) { if (tokens.get(i).getType() == Token.LB && i > 0 && i < length - 2 && tokens.get(i - 1).getType() == Token.NAME && tokens.get(i + 1).getType() == Token.STRING && tokens.get(i + 2).getType() == Token.RB) { token = tokens.get(i + 1); tv = token.getValue(); tv = tv.substring(1, tv.length() - 1); if (isValidIdentifier(tv)) { tokens.set(i, new JavaScriptToken(Token.DOT, ".")); tokens.set(i + 1, new JavaScriptToken(Token.NAME, tv)); tokens.remove(i + 2); i = i + 2; length = length - 1; } } } }
private String getDebugString(int max) { assert max > 0; StringBuffer result = new StringBuffer(); int start = Math.max(offset - max, 0); int end = Math.min(offset + max, tokens.size()); for (int i = start; i < end; i++) { JavaScriptToken token = tokens.get(i); if (i == offset - 1) { result.append(" ---> "); } result.append(token.getValue()); if (i == offset - 1) { result.append(" <--- "); } } return result.toString(); }
/* * Transforms 'foo': ... into foo: ... whenever possible, saving 2 bytes. */ public void optimizeObjLitMemberDecl() { String tv; int i, length; JavaScriptToken token; for (i = 0, length = tokens.size(); i < length; i++) { if (tokens.get(i).getType() == Token.OBJECTLIT && i > 0 && tokens.get(i - 1).getType() == Token.STRING) { token = tokens.get(i - 1); tv = token.getValue(); tv = tv.substring(1, tv.length() - 1); if (isValidIdentifier(tv)) { tokens.set(i - 1, new JavaScriptToken(Token.NAME, tv)); } } } }
private void parseCatch() { String symbol; JavaScriptToken token; ScriptOrFnScope currentScope; JavaScriptIdentifier identifier; token = getToken(-1); assert token.getType() == Token.CATCH; token = consumeToken(); assert token.getType() == Token.LP; token = consumeToken(); assert token.getType() == Token.NAME; symbol = token.getValue(); currentScope = getCurrentScope(); if (mode == BUILDING_SYMBOL_TREE) { // We must declare the exception identifier in the containing // function // scope to avoid errors related to the obfuscation process. No need // to // display a warning if the symbol was already declared here... currentScope.declareIdentifier(symbol, false); } else { identifier = getIdentifier(symbol, currentScope); identifier.incrementRefcount(); } token = consumeToken(); assert token.getType() == Token.RP; }
private void parseScope(ScriptOrFnScope scope) { String symbol; JavaScriptToken token; JavaScriptIdentifier identifier; int length = tokens.size(); enterScope(scope); // 进入范围(将scope压入栈scopes中) while (offset < length) { // 在此函数调用前已设置为0 token = consumeToken(); // 消费tokens,offset自增 switch (token.getType()) { case Token.VAR: // 注意后面没有break; if (mode == BUILDING_SYMBOL_TREE) { scope.incrementVarCount(); // 当前范围内的var数量加1 } /* FALLSTHROUGH(falls through) 失败 */ case Token.CONST: // The var keyword is followed by at least one symbol name. // If several symbols follow, they are comma(逗号) separated. for (; ; ) { token = consumeToken(); assert token.getType() == Token.NAME; // 判断关键字var、const后的变量是否已经定义,未定义则定义,已定义则输出警告 if (mode == BUILDING_SYMBOL_TREE) { symbol = token.getValue(); if (scope.getIdentifier(symbol) == null) { scope.declareIdentifier(symbol, true); // 声明标识符 } else { warn("变量" + symbol + "已经在相同的作用域中声明", true); } } token = getToken(0); // offset不自增,token.getValue()的值一般为'='或';' assert token.getType() == Token.SEMI || token.getType() == Token.ASSIGN || token.getType() == Token.COMMA || token.getType() == Token.IN; if (token.getType() == Token.IN) { break; } else { parseExpression(); // 解释表达式(例如解释var a = 'u';或var a;) token = getToken(-1); // 得到的是';' if (token.getType() == Token.SEMI) { // 变量声明完成 break; } } } break; case Token.FUNCTION: parseFunctionDeclaration(); break; case Token.LC: braceNesting++; break; case Token.RC: braceNesting--; assert braceNesting >= scope.getBraceNesting(); if (braceNesting == scope.getBraceNesting()) { // 说明要离开当前作用域了 leaveCurrentScope(); // 离开当前作用域 return; } break; case Token.WITH: if (mode == BUILDING_SYMBOL_TREE) { // Inside a 'with' block, it is impossible to figure out // statically whether a symbol is a local variable or an // object member. As a consequence, the only thing we can // do is turn the obfuscation off for the highest scope // containing the 'with' block. protectScopeFromObfuscation(scope); warn("不推荐使用with关键字。" + (munge ? " 使用with会降低压缩水平" : ""), true); } break; case Token.CATCH: parseCatch(); break; case Token.CONDCOMMENT: if (mode == BUILDING_SYMBOL_TREE) { protectScopeFromObfuscation(scope); warn("不推荐使用JScript条件注释。" + (munge ? "使用JScript条件解释会降低压缩水平。" : ""), true); } break; case Token.NAME: symbol = token.getValue(); if (mode == BUILDING_SYMBOL_TREE) { if (symbol.equals("eval")) { protectScopeFromObfuscation(scope); warn("不推荐使用'eval'关键字。" + (munge ? "使用'eval'会降低压缩水平!" : ""), true); } } else if (mode == CHECKING_SYMBOL_TREE) { if ((offset < 2 || getToken(-2).getType() != Token.DOT) && getToken(0).getType() != Token.OBJECTLIT) { identifier = getIdentifier(symbol, scope); if (identifier == null) { if (symbol.length() <= 3 && !builtin.contains(symbol)) { // Here, we found an undeclared and // un-namespaced symbol that is // 3 characters or less in length. Declare it in // the global scope. // We don't need to declare longer symbols since // they won't cause // any conflict with other munged symbols. globalScope.declareIdentifier(symbol, false); // warn("Found an undeclared symbol: " + symbol, // true); } } else { identifier.incrementRefcount(); } } } break; } } }
private void parseExpression() { // Parse the expression until we encounter a comma or a semi-colon // in the same brace nesting, bracket nesting(方括号嵌套) and paren nesting. // Parse functions if any... String symbol; JavaScriptToken token; ScriptOrFnScope currentScope; JavaScriptIdentifier identifier; int expressionBraceNesting = braceNesting; // 花括号嵌套 int bracketNesting = 0; // 方括号嵌套 int parensNesting = 0; // int length = tokens.size(); while (offset < length) { token = consumeToken(); currentScope = getCurrentScope(); // return scopes.peek(); switch (token.getType()) { case Token.SEMI: case Token.COMMA: if (braceNesting == expressionBraceNesting && bracketNesting == 0 && parensNesting == 0) { return; } break; case Token.FUNCTION: parseFunctionDeclaration(); break; case Token.LC: braceNesting++; break; case Token.RC: braceNesting--; assert braceNesting >= expressionBraceNesting; break; case Token.LB: bracketNesting++; break; case Token.RB: bracketNesting--; break; case Token.LP: parensNesting++; break; case Token.RP: parensNesting--; break; case Token.CONDCOMMENT: if (mode == BUILDING_SYMBOL_TREE) { protectScopeFromObfuscation(currentScope); warn("不推荐使用JScript条件注释。" + (munge ? "使用JScript条件注释会降低压缩水平!" : ""), true); } break; case Token.NAME: symbol = token.getValue(); if (mode == BUILDING_SYMBOL_TREE) { if (symbol.equals("eval")) { protectScopeFromObfuscation(currentScope); warn("不推荐使用'eval'关键字。" + (munge ? "使用'eval'会降低压缩水平!" : ""), true); } } else if (mode == CHECKING_SYMBOL_TREE) { if ((offset < 2 || (getToken(-2).getType() != Token.DOT && getToken(-2).getType() != Token.GET && getToken(-2).getType() != Token.SET)) && getToken(0).getType() != Token.OBJECTLIT) { identifier = getIdentifier(symbol, currentScope); if (identifier == null) { if (symbol.length() <= 3 && !builtin.contains(symbol)) { // Here, we found an undeclared and // un-namespaced symbol that is // 3 characters or less in length. Declare it in // the global scope. // We don't need to declare longer symbols since // they won't cause // any conflict with other munged symbols. globalScope.declareIdentifier(symbol, false); // I removed the warning since was only being // done when // for identifiers 3 chars or less, and was just // causing // noise for people who happen to rely on an // externally // declared variable that happen to be that // short. We either // should always warn or never warn -- the fact // that we // declare the short symbols in the global space // doesn't // change anything. // warn("Found an undeclared symbol: " + symbol, // true); } } else { identifier.incrementRefcount(); } } } break; } } }
private void parseFunctionDeclaration() { String symbol; JavaScriptToken token; ScriptOrFnScope currentScope, fnScope; JavaScriptIdentifier identifier; currentScope = getCurrentScope(); // 得到此函数声明所在的域 token = consumeToken(); // 如果是函数名(说明这种函数的声明方式是:function funName(){}) // 否则是:var varName = function(){} if (token.getType() == Token.NAME) { if (mode == BUILDING_SYMBOL_TREE) { // Get the name of the function and declare it in the current // scope. symbol = token.getValue(); // 得到的是函数名 if (currentScope.getIdentifier(symbol) != null) { warn("函数" + symbol + "已经在相同的作用域中声明。", true); } currentScope.declareIdentifier(symbol, false); // 在本作用域中声明标识符 } token = consumeToken(); // 得到左圆括号 } assert token.getType() == Token.LP; if (mode == BUILDING_SYMBOL_TREE) { fnScope = new ScriptOrFnScope(braceNesting, currentScope); // 新建一个作用域,并把当前域传递过去最为父作用域 indexedScopes.put(new Integer(offset), fnScope); // 圆括号的下一个索引映射到函数作用域 } else { fnScope = indexedScopes.get(new Integer(offset)); } // Parse function arguments.解释函数参数 int argpos = 0; while ((token = consumeToken()).getType() != Token.RP) { assert token.getType() == Token.NAME || token.getType() == Token.COMMA; if (token.getType() == Token.NAME && mode == BUILDING_SYMBOL_TREE) { symbol = token.getValue(); identifier = fnScope.declareIdentifier(symbol, false); if (symbol.equals("$super") && argpos == 0) { // Exception for Prototype 1.6... identifier.preventMunging(); } argpos++; } } token = consumeToken(); // 得到函数的左花括号 assert token.getType() == Token.LC; braceNesting++; token = getToken(0); if (token.getType() == Token.STRING && getToken(1).getType() == Token.SEMI) { // This is a hint. Hints are empty statements that look like // "localvar1:nomunge, localvar2:nomunge"; They allow developers // to prevent specific symbols from getting obfuscated (some heretic // implementations, such as Prototype 1.6, require specific variable // names, such as $super for example, in order to work // appropriately. // Note: right now, only "nomunge" is supported in the right hand // side // of a hint. However, in the future, the right hand side(右边) may // contain // other values. consumeToken(); String hints = token.getValue(); // Remove the leading and trailing quotes... hints = hints.substring(1, hints.length() - 1).trim(); StringTokenizer st1 = new StringTokenizer(hints, ","); // Tokenizer分词器,以','为分词界限 while (st1.hasMoreTokens()) { String hint = st1.nextToken(); int idx = hint.indexOf(':'); if (idx <= 0 || idx >= hint.length() - 1) { if (mode == BUILDING_SYMBOL_TREE) { // No need to report the error twice, hence the test... warn("提示语句有语法错误:" + hint, true); } break; } String variableName = hint.substring(0, idx).trim(); String variableType = hint.substring(idx + 1).trim(); if (mode == BUILDING_SYMBOL_TREE) { fnScope.addHint(variableName, variableType); } else if (mode == CHECKING_SYMBOL_TREE) { identifier = fnScope.getIdentifier(variableName); if (identifier != null) { if (variableType.equals("nomunge")) { identifier.preventMunging(); } else { warn("不支持的提示指令: " + hint, true); } } else { warn("提示指令指向未知的标识符: " + hint, true); } } } } parseScope(fnScope); // 解释函数域 }
// 此函数是将表达式优化,形式参数中的tokens是诸如'+','-','*'等单字符的操作符(当然也是关键字)和NAME,REGEXP,STRING等类型 public void processStringLiterals(boolean merge) { String tv; int i, length = tokens.size(); JavaScriptToken token, prevToken, nextToken; if (merge) { // 如果优化 // Concatenate string literals(连接字符串常量) that are being appended // wherever // it is safe to do so. Note that we take care of the case: // "a" + "b".toUpperCase() for (i = 0; i < length; i++) { token = tokens.get(i); switch (token.getType()) { case Token.ADD: if (i > 0 && i < length) { prevToken = tokens.get(i - 1); nextToken = tokens.get(i + 1); if (prevToken.getType() == Token.STRING && nextToken.getType() == Token.STRING && (i == length - 1 || tokens.get(i + 2).getType() != Token.DOT)) { tokens.set( i - 1, new JavaScriptToken( Token.STRING, prevToken.getValue() + nextToken.getValue())); // 设置对应索引处的值 tokens.remove(i + 1); // 移除之前的值 tokens.remove(i); // 移除之前的值 i = i - 1; length = length - 2; break; } } } } } // Second // pass...Token中保存的STRING类型形如abc,经以下处理后变为"abc",a'b'c-->"a'b'c",a"b"c-->'a"b"c' for (i = 0; i < length; i++) { token = tokens.get(i); if (token.getType() == Token.STRING) { tv = token.getValue(); // Finally, add the quoting characters(引号字符) and escape the // string(滤过字符串). We use // the quoting character that minimizes the amount of escaping // to save // a few additional bytes. char quotechar; int singleQuoteCount = countChar(tv, '\''); // 计算单引号数量 int doubleQuoteCount = countChar(tv, '"'); // 计算双引号数量 if (doubleQuoteCount <= singleQuoteCount) { quotechar = '"'; } else { quotechar = '\''; } tv = quotechar + escapeString(tv, quotechar) + quotechar; // String concatenation transforms the old script scheme: // '<scr'+'ipt ...><'+'/script>' // into the following: // '<script ...></script>' // which breaks if this code is embedded inside an HTML // document. // Since this is not the right way to do this, let's fix the // code by // transforming all "</script" into "<\/script" if (tv.indexOf("</script") >= 0) { tv = tv.replaceAll("<\\/script", "<\\\\/script"); } tokens.set(i, new JavaScriptToken(Token.STRING, tv)); } } }
public StringBuffer printSymbolTree(int linebreakpos, boolean preserveAllSemiColons) throws IOException { offset = 0; braceNesting = 0; scopes.clear(); String symbol; JavaScriptToken token; // begin if (tokens.size() == 0) { StringBuffer result = new StringBuffer(); return result; } // end JavaScriptToken lastToken = getToken(0); ScriptOrFnScope currentScope; JavaScriptIdentifier identifier; int length = tokens.size(); // 文本的长度 StringBuffer result = new StringBuffer(); int linestartpos = 0; enterScope(globalScope); // 将globalScope压入栈scopes中 while (offset < length) { token = consumeToken(); symbol = token.getValue(); currentScope = getCurrentScope(); switch (token.getType()) { case Token.GET: case Token.SET: lastToken = token; // 注意没有break; case Token.NAME: if (offset >= 2 && getToken(-2).getType() == Token.DOT || getToken(0).getType() == Token.OBJECTLIT) { result.append(symbol); } else { identifier = getIdentifier(symbol, currentScope); if (identifier != null) { if (identifier.getMungedValue() != null) { result.append(identifier.getMungedValue()); } else { result.append(symbol); } if (currentScope != globalScope // 全局域中的变量可能被HTML文档使用,不需要发出警告 && identifier.getRefcount() == 0) { warn("标识符" + symbol + "已经声明但未使用 ", true); } } else { result.append(symbol); } } break; case Token.REGEXP: case Token.STRING: result.append(symbol); break; case Token.NUMBER: if (getToken(0).getType() == Token.DOT) { // calling methods on int requires a leading dot so JS // doesn't // treat the method as the decimal component of a float result.append('('); result.append(symbol); result.append(')'); } else { result.append(symbol); } break; case Token.ADD: case Token.SUB: result.append(literals.get(new Integer(token.getType()))); if (offset < length) { token = getToken(0); if (token.getType() == Token.INC || token.getType() == Token.DEC || token.getType() == Token.ADD || token.getType() == Token.DEC) { // Handle the case x +/- ++/-- y // We must keep a white space here. Otherwise, x +++ y // would be // interpreted as x ++ + y by the compiler, which is a // bug (due // to the implicit(隐式的) assignment being done on the // wrong variable) result.append(' '); } else if (token.getType() == Token.POS && getToken(-1).getType() == Token.ADD || token.getType() == Token.NEG && getToken(-1).getType() == Token.SUB) { // Handle the case x + + y and x - - y result.append(' '); } } break; case Token.FUNCTION: if (lastToken.getType() != Token.GET && lastToken.getType() != Token.SET) { result.append("function"); } lastToken = token; token = consumeToken(); if (token.getType() == Token.NAME) { result.append(' '); symbol = token.getValue(); identifier = getIdentifier(symbol, currentScope); assert identifier != null; if (identifier.getMungedValue() != null) { result.append(identifier.getMungedValue()); } else { result.append(symbol); } if (currentScope != globalScope && identifier.getRefcount() == 0) { warn("标识符" + symbol + "已经声明但未使用", true); } token = consumeToken(); } assert token.getType() == Token.LP; result.append('('); currentScope = indexedScopes.get(new Integer(offset)); // 根据左圆括号的下一个索引映射得到函数作用域 enterScope(currentScope); while ((token = consumeToken()).getType() != Token.RP) { assert token.getType() == Token.NAME || token.getType() == Token.COMMA; if (token.getType() == Token.NAME) { symbol = token.getValue(); identifier = getIdentifier(symbol, currentScope); assert identifier != null; if (identifier.getMungedValue() != null) { result.append(identifier.getMungedValue()); } else { result.append(symbol); } } else if (token.getType() == Token.COMMA) { result.append(','); } } result.append(')'); token = consumeToken(); // 得到左花括号 assert token.getType() == Token.LC; result.append("{"); // nomodify braceNesting++; token = getToken(0); if (token.getType() == Token.STRING && getToken(1).getType() == Token.SEMI) { // This is a hint. Skip it! consumeToken(); consumeToken(); } break; case Token.RETURN: case Token.TYPEOF: result.append(literals.get(new Integer(token.getType()))); // No space needed after 'return' and 'typeof' when followed // by '(', '[', '{', a string or a regexp. if (offset < length) { token = getToken(0); if (token.getType() != Token.LP && token.getType() != Token.LB && token.getType() != Token.LC && token.getType() != Token.STRING && token.getType() != Token.REGEXP && token.getType() != Token.SEMI) { result.append(' '); } } break; case Token.CASE: case Token.THROW: result.append(literals.get(new Integer(token.getType()))); // White-space needed after 'case' and 'throw' when not followed // by a string. if (offset < length && getToken(0).getType() != Token.STRING) { result.append(' '); } break; case Token.BREAK: case Token.CONTINUE: result.append(literals.get(new Integer(token.getType()))); if (offset < length && getToken(0).getType() != Token.SEMI) { // If 'break' or 'continue' is not followed by a // semi-colon(分号), it must // be followed by a label, hence(因此) the need for a white // space. result.append(' '); } break; case Token.LC: result.append("{"); // nomodify braceNesting++; break; case Token.RC: result.append('}'); braceNesting--; assert braceNesting >= currentScope.getBraceNesting(); if (braceNesting == currentScope.getBraceNesting()) { leaveCurrentScope(); } break; case Token.SEMI: // No need to output a semi-colon if the next character is a // right-curly... if (preserveAllSemiColons || offset < length && getToken(0).getType() != Token.RC) { result.append(';'); } if (linebreakpos >= 0 && result.length() - linestartpos > linebreakpos) { // Some source control tools don't like it when files // containing lines longer // than, say 8000 characters, are checked in. The linebreak // option is used in // that case to split long lines after a specific column. result.append('\n'); linestartpos = result.length(); } break; case Token.COMMA: // No need to output a comma if the next character is a // right-curly or a right-square bracket if (offset < length && getToken(0).getType() != Token.RC && getToken(0).getType() != Token.RB) { result.append(','); } break; case Token.CONDCOMMENT: case Token.KEEPCOMMENT: if (result.length() > 0 && result.charAt(result.length() - 1) != '\n') { result.append("\n"); } result.append("/*"); if (token.getType() == Token.KEEPCOMMENT) { result.append("!"); } result.append(symbol); result.append("*/\n"); break; // begin--这个分支用于实现压扁控制流 // 此分支将if(expression){...}else if(expression){...}else{...}转化为 // switch(expression){case true:原if块;break;case false:原else块;break;} case Token.IF: if (!EditOptionPanel.checkBoxFlatten.isSelected()) { result.append(symbol); break; } FlattenIF flattenIF = new FlattenIF(offset, length, tokens, currentScope); String switchBlock = flattenIF.flattenIF(); result.append(switchBlock); offset = flattenIF.offset; break; case Token.WHILE: if (!EditOptionPanel.checkBoxOpacity.isSelected()) { result.append(symbol); break; } result.append(symbol); OpacityPredicate opacityPredicateWhile = new OpacityPredicate(offset, tokens, currentScope); String whileBlock = opacityPredicateWhile.opacityPredicateWhile(); result.append(whileBlock); offset = opacityPredicateWhile.offset; break; case Token.DO: if (!EditOptionPanel.checkBoxOpacity.isSelected()) { result.append(symbol); break; } result.append(symbol); OpacityPredicate opacityPredicateDoWhile = new OpacityPredicate(offset, tokens, currentScope); String doWhileBlock = opacityPredicateDoWhile.opacityPredicateDoWhile(); result.append(doWhileBlock); offset = opacityPredicateDoWhile.offset; break; // end default: String literal = literals.get(new Integer(token.getType())); if (literal != null) { result.append(literal); } else { warn("此标志符不能被打印出来:" + symbol, true); } break; } } // Append a semi-colon at the end, even if unnecessary semi-colons are // supposed to be removed. This is especially useful when concatenating // several minified files (the absence of an ending semi-colon at the // end of one file may very likely cause a syntax error) /*if (!preserveAllSemiColons && result.length() > 0 && getToken(-1).getType() != Token.CONDCOMMENT && getToken(-1).getType() != Token.KEEPCOMMENT) { if (result.charAt(result.length() - 1) == '\n') { result.setCharAt(result.length() - 1, ';'); } else { result.append(';'); } }*/ // 暂时用不上,注释掉 return result; }