public JavaScriptIdentifier(string value, ScriptOrFunctionScope declaredScope) : base(Token.NAME, value) { MarkedForMunging = true; DeclaredScope = declaredScope; }
public ScriptOrFunctionScope(int braceNesting, ScriptOrFunctionScope parentScope) { BraceNesting = braceNesting; ParentScope = parentScope; SubScopes = new ArrayList(); if (parentScope != null) { parentScope.SubScopes.Add(this); } }
public ScriptOrFunctionScope(int braceNesting, ScriptOrFunctionScope parentScope) { this.BraceNesting = braceNesting; this.ParentScope = parentScope; this.SubScopes = new ArrayList(); if (parentScope != null) { parentScope.SubScopes.Add(this); } }
private ArrayList GetAllUsedSymbols() { var result = new ArrayList(); ScriptOrFunctionScope scope = this; while (scope != null) { result.AddRange(scope.GetUsedSymbols()); scope = scope.ParentScope; } return(result); }
private void ParseFunctionDeclaration() { string symbol; ScriptOrFunctionScope functionScope; JavaScriptIdentifier identifier; ScriptOrFunctionScope currentScope = GetCurrentScope(); JavaScriptToken token = ConsumeToken(); if (token.TokenType == Token.NAME) { if (_mode == BUILDING_SYMBOL_TREE) { // Get the name of the function and declare it in the current scope. symbol = token.Value; if (currentScope.GetIdentifier(symbol) != null) { Warn("The function " + symbol + " has already been declared in the same scope...", true); } currentScope.DeclareIdentifier(symbol); } token = ConsumeToken(); } if (token.TokenType != Token.LP) { throw new InvalidOperationException(); } if (_mode == BUILDING_SYMBOL_TREE) { functionScope = new ScriptOrFunctionScope(_braceNesting, currentScope); _indexedScopes.Add(_offset, functionScope); } else { functionScope = (ScriptOrFunctionScope) _indexedScopes[_offset]; } // Parse function arguments. int argpos = 0; while ((token = ConsumeToken()).TokenType != Token.RP) { if (token.TokenType != Token.NAME && token.TokenType != Token.COMMA) { throw new InvalidOperationException(); } if (token.TokenType == Token.NAME && _mode == BUILDING_SYMBOL_TREE) { symbol = token.Value; identifier = functionScope.DeclareIdentifier(symbol); if (symbol.Equals("$super", StringComparison.OrdinalIgnoreCase) && argpos == 0) { // Exception for Prototype 1.6... identifier.MarkedForMunging = false; } argpos++; } } token = ConsumeToken(); if (token.TokenType != Token.LC) { throw new InvalidOperationException(); } _braceNesting++; token = GetToken(0); if (token.TokenType == Token.STRING && GetToken(1).TokenType == 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.Value; // Remove the leading and trailing quotes... hints = hints.Substring(1, hints.Length - 1).Trim(); foreach (string hint in hints.Split(',')) { 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("Invalid hint syntax: " + hint, true); } break; } string variableName = hint.Substring(0, idx).Trim(); string variableType = hint.Substring(idx + 1).Trim(); if (_mode == BUILDING_SYMBOL_TREE) { functionScope.AddHint(variableName, variableType); } else if (_mode == CHECKING_SYMBOL_TREE) { identifier = functionScope.GetIdentifier(variableName); if (identifier != null) { if (variableType.Equals("nomunge", StringComparison.OrdinalIgnoreCase)) { identifier.MarkedForMunging = false; } else { Warn("Unsupported hint value: " + hint, true); } } else { Warn("Hint refers to an unknown identifier: " + hint, true); } } } } ParseScope(functionScope); }
/* * If either 'eval' or 'with' is used in a local scope, we must make * sure that all containing local scopes don't get munged. Otherwise, * the obfuscation would potentially introduce bugs. */ private void ProtectScopeFromObfuscation(ScriptOrFunctionScope scope) { if (scope == null) { throw new ArgumentNullException("scope"); } if (scope == _globalScope) { // The global scope does not get obfuscated, // so we don't need to worry about it... return; } // Find the highest local scope containing the specified scope. while (scope.ParentScope != _globalScope) { scope = scope.ParentScope; } if (scope.ParentScope != _globalScope) { throw new InvalidOperationException(); } scope.PreventMunging(); }
/* * Returns the identifier for the specified symbol defined in * the specified scope or in any scope above it. Returns null * if this symbol does not have a corresponding identifier. */ private static JavaScriptIdentifier GetIdentifier(string symbol, ScriptOrFunctionScope scope) { while (scope != null) { JavaScriptIdentifier identifier = scope.GetIdentifier(symbol); if (identifier != null) { return identifier; } scope = scope.ParentScope; } return null; }
private void EnterScope(ScriptOrFunctionScope scope) { _scopes.Push(scope); }
private void ParseScope(ScriptOrFunctionScope scope) { string symbol; JavaScriptToken token; JavaScriptIdentifier identifier; int length = _tokens.Count; EnterScope(scope); while (_offset < length) { token = ConsumeToken(); switch (token.TokenType) { case Token.VAR: case Token.CONST: if (token.TokenType == Token.VAR) { if (_mode == BUILDING_SYMBOL_TREE && scope.VarCount++ > 1) { Warn("Try to use a single 'var' statement per scope.", true); } } // The var keyword is followed by at least one symbol name. // If several symbols follow, they are comma separated. //for (; ;) while (true) { token = ConsumeToken(); if (token.TokenType != Token.NAME) { throw new InvalidOperationException(); } if (_mode == BUILDING_SYMBOL_TREE) { symbol = token.Value; if (scope.GetIdentifier(symbol) == null) { scope.DeclareIdentifier(symbol); } else { Warn( "The variable " + symbol + " has already been declared in the same scope...", true); } } token = GetToken(0); if (token.TokenType != Token.SEMI && token.TokenType != Token.ASSIGN && token.TokenType != Token.COMMA && token.TokenType != Token.IN) { throw new InvalidOperationException(); } if (token.TokenType == Token.IN) { break; } ParseExpression(); token = GetToken(-1); if (token.TokenType == Token.SEMI) { break; } } break; case Token.FUNCTION: ParseFunctionDeclaration(); break; case Token.LC: _braceNesting++; break; case Token.RC: _braceNesting--; if (_braceNesting < scope.BraceNesting) { throw new InvalidOperationException(); } if (_braceNesting == scope.BraceNesting) { 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( "Using 'with' is not recommended." + (_munge ? " Moreover, using 'with' reduces the level of compression!" : ""), true); } break; case Token.CATCH: ParseCatch(); break; case Token.CONDCOMMENT: if (_mode == BUILDING_SYMBOL_TREE) { ProtectScopeFromObfuscation(scope); Warn( "Using JScript conditional comments is not recommended." + (_munge ? " Moreover, using JScript conditional comments reduces the level of compression." : ""), true); } break; case Token.NAME: symbol = token.Value; if (_mode == BUILDING_SYMBOL_TREE) { if (!_isEvalIgnored && symbol.Equals("eval", StringComparison.OrdinalIgnoreCase)) { ProtectScopeFromObfuscation(scope); Warn( "Using 'eval' is not recommended." + (_munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true); } } else if (_mode == CHECKING_SYMBOL_TREE) { if ((_offset < 2 || GetToken(-2).TokenType != Token.DOT) && GetToken(0).TokenType != 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); Warn("Found an undeclared symbol: " + symbol, true); } } else { identifier.RefCount++; } } } break; } } }
protected override string DoCompress(string source) { if (ErrorReporter == null) { ErrorReporter = new CustomErrorReporter(LoggingType); } var originalCultureInfo = Thread.CurrentThread.CurrentCulture; var originalUiCulture = Thread.CurrentThread.CurrentUICulture; try { // Change the current Thread Culture if the user has asked for something specific. // Reference: http://www.codeplex.com/YUICompressor/WorkItem/View.aspx?WorkItemId=3219 Thread.CurrentThread.CurrentCulture = ThreadCulture; Thread.CurrentThread.CurrentUICulture = ThreadCulture; var memoryStream = new MemoryStream(Encoding.GetBytes(source)); _tokens = Parse(new StreamReader(memoryStream, Encoding), ErrorReporter); ProcessStringLiterals(_tokens, !DisableOptimizations); if (!DisableOptimizations) { OptimizeObjectMemberAccess(_tokens); OptimizeObjLitMemberDecl(_tokens); } _globalScope = new ScriptOrFunctionScope(-1, null); BuildSymbolTree(); MungeSymboltree(); var result = PrintSymbolTree(LineBreakPosition, PreserveAllSemicolons); return result.ToString(); } finally { Thread.CurrentThread.CurrentCulture = originalCultureInfo; Thread.CurrentThread.CurrentUICulture = originalUiCulture; } }
public void Munge() { if (!this._markedForMunging) { // Stop right here if this scope was flagged as unsafe for munging. return; } int pickFromSet = 1; // Do not munge symbols in the global scope! if (this.ParentScope != null) { ArrayList freeSymbols = new ArrayList(); freeSymbols.AddRange(JavaScriptCompressor.Ones); foreach (string symbol in this.GetAllUsedSymbols()) { freeSymbols.Remove(symbol); } if (freeSymbols.Count == 0) { pickFromSet = 2; freeSymbols.AddRange(JavaScriptCompressor.Twos); foreach (string symbol in this.GetAllUsedSymbols()) { freeSymbols.Remove(symbol); } } if (freeSymbols.Count == 0) { pickFromSet = 3; freeSymbols.AddRange(JavaScriptCompressor.Threes); foreach (string symbol in this.GetAllUsedSymbols()) { freeSymbols.Remove(symbol); } } if (freeSymbols.Count == 0) { throw new InvalidOperationException("The YUI Compressor ran out of symbols. Aborting..."); } foreach (JavaScriptIdentifier identifier in this._identifiers.Values) { if (freeSymbols.Count == 0) { pickFromSet++; if (pickFromSet == 2) { freeSymbols.AddRange(JavaScriptCompressor.Twos); } else if (pickFromSet == 3) { freeSymbols.AddRange(JavaScriptCompressor.Threes); } else { throw new InvalidOperationException("The YUI Compressor ran out of symbols. Aborting..."); } // It is essential to remove the symbols already used in // the containing scopes, or some of the variables declared // in the containing scopes will be redeclared, which can // lead to errors. foreach (string symbol in this.GetAllUsedSymbols()) { freeSymbols.Remove(symbol); } } string mungedValue; if (identifier.MarkedForMunging) { mungedValue = (string)freeSymbols[0]; freeSymbols.RemoveAt(0); } else { mungedValue = identifier.Value; } identifier.MungedValue = mungedValue; } } for (int i = 0; i < this.SubScopes.Count; i++) { ScriptOrFunctionScope scope = (ScriptOrFunctionScope)this.SubScopes[i]; scope.Munge(); } }