コード例 #1
0
  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;
  }
コード例 #2
0
  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;
      }
    }
  }
コード例 #3
0
  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;
      }
    }
  }
コード例 #4
0
  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); // 解释函数域
  }
コード例 #5
0
  // 此函数是将表达式优化,形式参数中的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));
      }
    }
  }
コード例 #6
0
  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;
  }