protected virtual void CreateLiteralShortcuts() { if (m_literalMap != null) { // get a reference to the first function scope in the chain // might be this, might be a parent FunctionScope functionScope = null; ActivationObject scope = this; while (scope != null && (functionScope = scope as FunctionScope) == null) { scope = scope.Parent; } // if we didn't find a parent function scope, then don't do any combining // because the literals are globals if (functionScope != null) { // for each value in our literal map foreach (string constantString in m_literalMap.Keys) { LiteralReference literalReference = m_literalMap[constantString]; // if the child scope isn't null, then we don't reference the literal // and only one of our child scopes does, so we don't want to add the // shortcut here. // OR if there are no constant wrappers left in the list, then we've already // replaced them all and there's nothing left to do. // BUT if the child scope is null, either we reference it, or more than // one child references it. So if there are any constant wrappers in the list, // then we want to add the shortcut and replace all the constants if (literalReference.ChildScope == null && literalReference.ConstantWrapperList.Count > 0) { // AND we only want to do it if it will be worthwhile. // (and a constant of length 1 is never worthwhile) int constantLength = constantString.Length; if (constantLength > 1) { int minCount = (constantLength + 7) / (constantLength - 1); if (literalReference.Count > minCount) { // create a special name that won't collide with any other variable names string specialName = string.Format(CultureInfo.InvariantCulture, "[literal:{0}]", ++s_literalCounter); // add a generated var statement at the top of the function block that // is equal to the literal value (just use the first constant wrapper as a model) ConstantWrapper modelConstant = literalReference.ConstantWrapperList[0]; // by default we will use the value of the first instance as the generated variable's value object generatedValue = modelConstant.Value; // BUT.... // if this is a numeric value, then we need to determine whether we should use a // positive or negative version of this value to minimize the number of minus operators in the results if (modelConstant.IsNumericLiteral) { // first we need to go through the existing references and count how many negative values there are var numberOfNegatives = 0; foreach (ConstantWrapper constantWrapper in literalReference.ConstantWrapperList) { // since the model us numeric, we shouldn't have any problems calling the // ToNumber method on the others (which should all also be numeric) if (constantWrapper.ToNumber() < 0) { ++numberOfNegatives; } } // now if more than half of the references are negative, we will want the generated value // to also be negative! Otherwise we want to force it to Positive. var absoluteValue = Math.Abs((double)generatedValue); if (numberOfNegatives > literalReference.ConstantWrapperList.Count / 2) { // force it to negative generatedValue = -absoluteValue; } else { // force it to positive generatedValue = absoluteValue; } } // add the generated variable to the function scope functionScope.FunctionObject.AddGeneratedVar( specialName, new ConstantWrapper( generatedValue, modelConstant.PrimitiveType, modelConstant.Context, Parser), true); // walk the list of constant wrappers backwards (because we'll be removing them // as we go along) and replace each one with a lookup for the generated variable. // Don't forget to analyze the lookup. for (int ndx = literalReference.ConstantWrapperList.Count - 1; ndx >= 0; --ndx) { ConstantWrapper constantWrapper = literalReference.ConstantWrapperList[ndx]; // create the lookup based on the thisliteral context Lookup lookup = new Lookup(specialName, constantWrapper.Context, Parser); // indicate this is generated by our code, not the user lookup.IsGenerated = true; // by default, we're just going to replace the constant with the lookup AstNode replacement = lookup; // if the constant wrapper is a numeric value that is the NEGATIVE of the // combined numeric value (which would happen if the literal was subsequently // combined with a unary minus operator), then we need to change this to a unary-minus // operator on the lookup, not just the lookup. if (constantWrapper.IsNumericLiteral) { // since the constant wrapper is numeric, we shouldn't have any problems // calling ToNumber if ((double)generatedValue == -constantWrapper.ToNumber()) { // it has been negated! Change the replacement to a unary minus operator // with the lookup as its operand replacement = new NumericUnary( constantWrapper.Context, Parser, lookup, JSToken.Minus); } } // replace the this literal with the appropriate node constantWrapper.Parent.ReplaceChild(constantWrapper, replacement); // set up the lookup's outer local field using the scope of the // original constant wrapper lookup.SetOuterLocalField(constantWrapper.EnclosingScope); // and remove it from the list. This is so child scopes don't also try to // add a shortcut -- the list will be empty. literalReference.ConstantWrapperList.RemoveAt(ndx); } } } } } } } }
internal override void AnalyzeNode() { if (Parser.Settings.StripDebugStatements && Parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements)) { if (TrueBlock != null && TrueBlock.IsDebuggerStatement) { TrueBlock = null; } if (FalseBlock != null && FalseBlock.IsDebuggerStatement) { FalseBlock = null; } } // recurse.... base.AnalyzeNode(); // now check to see if the two branches are now empty. // if they are, null them out. if (TrueBlock != null && TrueBlock.Count == 0) { TrueBlock = null; } if (FalseBlock != null && FalseBlock.Count == 0) { FalseBlock = null; } // if there is no true branch but a false branch, then // put a not on the condition and move the false branch to the true branch. if (TrueBlock == null && FalseBlock != null && Parser.Settings.IsModificationAllowed(TreeModifications.IfConditionFalseToIfNotConditionTrue)) { // check to see if not-ing the condition produces a quick and easy // version first AstNode nottedCondition = Condition.LogicalNot(); if (nottedCondition != null) { // it does -- use it Condition = nottedCondition; } else { // it doesn't. Just wrap it. Condition = new NumericUnary( null, Parser, Condition, JSToken.LogicalNot ); } // don't forget to set the parent Condition.Parent = this; // and swap the branches TrueBlock = FalseBlock; FalseBlock = null; } else if (TrueBlock == null && FalseBlock == null && Parser.Settings.IsModificationAllowed(TreeModifications.IfEmptyToExpression)) { // NEITHER branches have anything now! // something we can do in the future: as long as the condition doesn't // contain calls or assignments, we should be able to completely delete // the statement altogether rather than changing it to an expression // statement on the condition. // I'm just not doing it yet because I don't // know what the effect will be on the iteration of block statements. // if we're on item, 5, for instance, and we delete it, will the next // item be item 6, or will it return the NEW item 5 (since the old item // 5 was deleted and everything shifted up)? // We don't know what it is and what the side-effects may be, so // just change this statement into an expression statement by replacing us with // the expression Parent.ReplaceChild(this, Condition); // no need to analyze -- we already recursed } // if this statement is now of the pattern "if (condtion) callNode" then // we can further reduce it by changing it to "condition && callNode". if (TrueBlock != null && FalseBlock == null && TrueBlock.Count == 1 && Parser.Settings.IsModificationAllowed(TreeModifications.IfConditionCallToConditionAndCall)) { // BUT we don't want to replace the statement if the true branch is a // call to an onXXXX method of an object. This is because of an IE bug // that throws an error if you pass any parameter to onclick or onfocus or // any of those event handlers directly from within an and-expression -- // although expression-statements seem to work just fine. CallNode callNode = TrueBlock[0] as CallNode; if (callNode != null) { Member callMember = callNode.Function as Member; if (callMember == null || !callMember.Name.StartsWith("on", StringComparison.Ordinal) || callNode.Arguments.Count == 0) { // we're good -- go ahead and replace it BinaryOperator binaryOp = new BinaryOperator( Context, Parser, Condition, TrueBlock, JSToken.LogicalAnd ); // we don't need to analyse this new node because we've already analyzed // the pieces parts as part of the if. And the AnalyzeNode for the BinaryOperator // doesn't really do anything else. Just replace our current node with this // new node Parent.ReplaceChild(this, binaryOp); } } } }
//--------------------------------------------------------------------------------------- // ParseUnaryExpression // // UnaryExpression : // PostfixExpression | // 'delete' UnaryExpression | // 'void' UnaryExpression | // 'typeof' UnaryExpression | // '++' UnaryExpression | // '--' UnaryExpression | // '+' UnaryExpression | // '-' UnaryExpression | // '~' UnaryExpression | // '!' UnaryExpression // //--------------------------------------------------------------------------------------- private AstNode ParseUnaryExpression(out bool isLeftHandSideExpr, bool isMinus) { AstNode ast = null; isLeftHandSideExpr = false; bool dummy = false; Context exprCtx = null; AstNode expr = null; switch (m_currentToken.Token) { case JSToken.Void: exprCtx = m_currentToken.Clone(); GetNextToken(); expr = ParseUnaryExpression(out dummy, false); exprCtx.UpdateWith(expr.Context); ast = new VoidNode(exprCtx, this, expr); break; case JSToken.TypeOf: exprCtx = m_currentToken.Clone(); GetNextToken(); expr = ParseUnaryExpression(out dummy, false); exprCtx.UpdateWith(expr.Context); ast = new TypeOfNode(exprCtx, this, expr); break; case JSToken.Plus: exprCtx = m_currentToken.Clone(); GetNextToken(); expr = ParseUnaryExpression(out dummy, false); exprCtx.UpdateWith(expr.Context); ast = new NumericUnary(exprCtx, this, expr, JSToken.Plus); break; case JSToken.Minus: exprCtx = m_currentToken.Clone(); GetNextToken(); expr = ParseUnaryExpression(out dummy, true); exprCtx.UpdateWith(expr.Context); ast = new NumericUnary(exprCtx, this, expr, JSToken.Minus); break; case JSToken.BitwiseNot: exprCtx = m_currentToken.Clone(); GetNextToken(); expr = ParseUnaryExpression(out dummy, false); exprCtx.UpdateWith(expr.Context); ast = new NumericUnary(exprCtx, this, expr, JSToken.BitwiseNot); break; case JSToken.LogicalNot: exprCtx = m_currentToken.Clone(); GetNextToken(); expr = ParseUnaryExpression(out dummy, false); exprCtx.UpdateWith(expr.Context); ast = new NumericUnary(exprCtx, this, expr, JSToken.LogicalNot); break; case JSToken.Delete: exprCtx = m_currentToken.Clone(); GetNextToken(); expr = ParseUnaryExpression(out dummy, false); exprCtx.UpdateWith(expr.Context); ast = new Delete(exprCtx, this, expr); break; case JSToken.Increment: exprCtx = m_currentToken.Clone(); GetNextToken(); expr = ParseUnaryExpression(out dummy, false); exprCtx.UpdateWith(expr.Context); ast = new PostOrPrefixOperator(exprCtx, this, expr, m_currentToken.Token, PostOrPrefix.PrefixIncrement); break; case JSToken.Decrement: exprCtx = m_currentToken.Clone(); GetNextToken(); expr = ParseUnaryExpression(out dummy, false); exprCtx.UpdateWith(expr.Context); ast = new PostOrPrefixOperator(exprCtx, this, expr, m_currentToken.Token, PostOrPrefix.PrefixDecrement); break; default: m_noSkipTokenSet.Add(NoSkipTokenSet.s_PostfixExpressionNoSkipTokenSet); try { ast = ParseLeftHandSideExpression(isMinus); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_PostfixExpressionNoSkipTokenSet, exc) == -1) { throw; } else { if (exc._partiallyComputedNode == null) SkipTokensAndThrow(); else ast = exc._partiallyComputedNode; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_PostfixExpressionNoSkipTokenSet); } ast = ParsePostfixExpression(ast, out isLeftHandSideExpr); break; } return ast; }
internal override void AnalyzeNode() { // see if this is a member (we'll need it for a couple checks) Member member = m_func as Member; if (Parser.Settings.StripDebugStatements && Parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements)) { // if this is a member, and it's a debugger object, and it's a constructor.... if (member != null && member.IsDebuggerStatement && m_isConstructor) { // we need to replace our debugger object with a generic Object m_func = new Lookup("Object", m_func.Context, Parser); // and make sure the node list is empty if (m_args != null && m_args.Count > 0) { m_args = new AstNodeList(m_args.Context, Parser); } } } // if this is a constructor and we want to collapse // some of them to literals... if (m_isConstructor && Parser.Settings.CollapseToLiteral) { // see if this is a lookup, and if so, if it's pointing to one // of the two constructors we want to collapse Lookup lookup = m_func as Lookup; if (lookup != null) { if (lookup.Name == "Object" && Parser.Settings.IsModificationAllowed(TreeModifications.NewObjectToObjectLiteral)) { // no arguments -- the Object constructor with no arguments is the exact same as an empty // object literal if (m_args == null || m_args.Count == 0) { // replace our node with an object literal ObjectLiteral objLiteral = new ObjectLiteral(Context, Parser, null, null); if (Parent.ReplaceChild(this, objLiteral)) { // and bail now. No need to recurse -- it's an empty literal return; } } else if (m_args.Count == 1) { // one argument // check to see if it's an object literal. ObjectLiteral objectLiteral = m_args[0] as ObjectLiteral; if (objectLiteral != null) { // the Object constructor with an argument that is a JavaScript object merely returns the // argument. Since the argument is an object literal, it is by definition a JavaScript object // and therefore we can replace the constructor call with the object literal Parent.ReplaceChild(this, objectLiteral); // don't forget to recurse the object now objectLiteral.AnalyzeNode(); // and then bail -- we don't want to process this call // operation any more; we've gotten rid of it return; } } } else if (lookup.Name == "Array" && Parser.Settings.IsModificationAllowed(TreeModifications.NewArrayToArrayLiteral)) { // Array is trickier. // If there are no arguments, then just use []. // if there are multiple arguments, then use [arg0,arg1...argN]. // but if there is one argument and it's numeric, we can't crunch it. // also can't crunch if it's a function call or a member or something, since we won't // KNOW whether or not it's numeric. // // so first see if it even is a single-argument constant wrapper. ConstantWrapper constWrapper = (m_args != null && m_args.Count == 1 ? m_args[0] as ConstantWrapper : null); // if the argument count is not one, then we crunch. // if the argument count IS one, we only crunch if we have a constant wrapper, // AND it's not numeric. if (m_args == null || m_args.Count != 1 || (constWrapper != null && !constWrapper.IsNumericLiteral)) { // create the new array literal object ArrayLiteral arrayLiteral = new ArrayLiteral(Context, Parser, m_args); // replace ourself within our parent if (Parent.ReplaceChild(this, arrayLiteral)) { // recurse arrayLiteral.AnalyzeNode(); // and bail -- we don't want to recurse this node any more return; } } } } } // if we are replacing resource references with strings generated from resource files // and this is a brackets call: lookup[args] ResourceStrings resourceStrings = Parser.ResourceStrings; if (m_inBrackets && resourceStrings != null && resourceStrings.Count > 0) { // see if the root object is a lookup that corresponds to the // global value (not a local field) for our resource object // (same name) Lookup rootLookup = m_func as Lookup; if (rootLookup != null && rootLookup.LocalField == null && string.CompareOrdinal(rootLookup.Name, resourceStrings.Name) == 0) { // we're going to replace this node with a string constant wrapper // but first we need to make sure that this is a valid lookup. // if the parameter contains anything that would vary at run-time, // then we need to throw an error. // the parser will always have either one or zero nodes in the arguments // arg list. We're not interested in zero args, so just make sure there is one if (m_args.Count == 1) { // must be a constant wrapper ConstantWrapper argConstant = m_args[0] as ConstantWrapper; if (argConstant != null) { string resourceName = argConstant.Value.ToString(); // get the localized string from the resources object ConstantWrapper resourceLiteral = new ConstantWrapper( resourceStrings[resourceName], PrimitiveType.String, Context, Parser); // replace this node with localized string, analyze it, and bail // so we don't anaylze the tree we just replaced Parent.ReplaceChild(this, resourceLiteral); resourceLiteral.AnalyzeNode(); return; } else { // error! must be a constant Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } else { // error! can only be a single constant argument to the string resource object. // the parser will only have zero or one arguments, so this must be zero // (since the parser won't pass multiple args to a [] operator) Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } } // and finally, if this is a backets call and the argument is a constantwrapper that can // be an identifier, just change us to a member node: obj["prop"] to obj.prop. // but ONLY if the string value is "safe" to be an identifier. Even though the ECMA-262 // spec says certain Unicode categories are okay, in practice the various major browsers // all seem to have problems with certain characters in identifiers. Rather than risking // some browsers breaking when we change this syntax, don't do it for those "danger" categories. if (m_inBrackets && m_args != null) { // see if there is a single, constant argument string argText = m_args.SingleConstantArgument; if (argText != null) { // see if we want to replace the name string newName; if (Parser.Settings.HasRenamePairs && Parser.Settings.ManualRenamesProperties && Parser.Settings.IsModificationAllowed(TreeModifications.PropertyRenaming) && !string.IsNullOrEmpty(newName = Parser.Settings.GetNewName(argText))) { // yes -- we are going to replace the name, either as a string literal, or by converting // to a member-dot operation. // See if we can't turn it into a dot-operator. If we can't, then we just want to replace the operator with // a new constant wrapper. Otherwise we'll just replace the operator with a new constant wrapper. if (Parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(newName) && !JSScanner.IsKeyword(newName)) { // the new name is safe to convert to a member-dot operator. // but we don't want to convert the node to the NEW name, because we still need to Analyze the // new member node -- and it might convert the new name to something else. So instead we're // just going to convert this existing string to a member node WITH THE OLD STRING, // and THEN analyze it (which will convert the old string to newName) Member replacementMember = new Member(Context, Parser, m_func, argText); Parent.ReplaceChild(this, replacementMember); // this analyze call will convert the old-name member to the newName value replacementMember.AnalyzeNode(); return; } else { // nope; can't convert to a dot-operator. // we're just going to replace the first argument with a new string literal // and continue along our merry way. m_args.ReplaceChild(m_args[0], new ConstantWrapper(newName, PrimitiveType.String, m_args[0].Context, Parser)); } } else if (Parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(argText) && !JSScanner.IsKeyword(argText)) { // not a replacement, but the string literal is a safe identifier. So we will // replace this call node with a Member-dot operation Member replacementMember = new Member(Context, Parser, m_func, argText); Parent.ReplaceChild(this, replacementMember); replacementMember.AnalyzeNode(); return; } } } // call the base class to recurse base.AnalyzeNode(); // call this AFTER recursing to give the fields a chance to resolve, because we only // want to make this replacement if we are working on the global Date object. if (!m_inBrackets && !m_isConstructor && (m_args == null || m_args.Count == 0) && member != null && string.CompareOrdinal(member.Name, "getTime") == 0 && Parser.Settings.IsModificationAllowed(TreeModifications.DateGetTimeToUnaryPlus)) { // this is not a constructor and it's not a brackets call, and there are no arguments. // if the function is a member operation to "getTime" and the object of the member is a // constructor call to the global "Date" object (not a local), then we want to replace the call // with a unary plus on the Date constructor. Converting to numeric type is the same as // calling getTime, so it's the equivalent with much fewer bytes. CallNode dateConstructor = member.Root as CallNode; if (dateConstructor != null && dateConstructor.IsConstructor) { // lookup for the predifined (not local) "Date" field Lookup lookup = dateConstructor.Function as Lookup; if (lookup != null && string.CompareOrdinal(lookup.Name, "Date") == 0 && lookup.LocalField == null) { // this is in the pattern: (new Date()).getTime() // we want to replace it with +new Date // use the same date constructor node as the operand NumericUnary unary = new NumericUnary(Context, Parser, dateConstructor, JSToken.Plus); // replace us (the call to the getTime method) with this unary operator Parent.ReplaceChild(this, unary); // don't need to AnalyzeNode on the unary operator. The operand has already // been analyzed when we recursed, and the unary operator wouldn't do anything // special anyway (since the operand is not a numeric constant) } } } else if (Parser.Settings.EvalTreatment != EvalTreatment.Ignore) { // if this is a window.eval call, then we need to mark this scope as unknown just as // we would if this was a regular eval call. // (unless, of course, the parser settings say evals are safe) // call AFTER recursing so we know the left-hand side properties have had a chance to // lookup their fields to see if they are local or global if (member != null && string.CompareOrdinal(member.Name, "eval") == 0) { if (member.LeftHandSide.IsWindowLookup) { // this is a call to window.eval() // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } else { CallNode callNode = m_func as CallNode; if (callNode != null && callNode.InBrackets && callNode.LeftHandSide.IsWindowLookup && callNode.Arguments.IsSingleConstantArgument("eval")) { // this is a call to window["eval"] // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } } /* REVIEW: may be too late. lookups may alread have been analyzed and * found undefined * // check to see if this is an assignment to a window["prop"] structure * BinaryOperator binaryOp = Parent as BinaryOperator; * if (binaryOp != null && binaryOp.IsAssign * && m_inBrackets * && m_func.IsWindowLookup * && m_args != null) * { * // and IF the property is a non-empty constant that isn't currently * // a global field... * string propertyName = m_args.SingleConstantArgument; * if (!string.IsNullOrEmpty(propertyName) * && Parser.GlobalScope[propertyName] == null) * { * // we want to also add it to the global fields so it's not undefined * Parser.GlobalScope.DeclareField(propertyName, null, 0); * } * } */ }
public override void Visit(CallNode node) { if (node != null) { // see if this is a member (we'll need it for a couple checks) Member member = node.Function as Member; if (m_parser.Settings.StripDebugStatements && m_parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements)) { // if this is a member, and it's a debugger object, and it's a constructor.... if (member != null && member.IsDebuggerStatement && node.IsConstructor) { // we need to replace our debugger object with a generic Object node.ReplaceChild(node.Function, new Lookup("Object", node.Function.Context, m_parser)); // and make sure the node list is empty if (node.Arguments != null && node.Arguments.Count > 0) { node.ReplaceChild(node.Arguments, new AstNodeList(node.Arguments.Context, m_parser)); } } } // if this is a constructor and we want to collapse // some of them to literals... if (node.IsConstructor && m_parser.Settings.CollapseToLiteral) { // see if this is a lookup, and if so, if it's pointing to one // of the two constructors we want to collapse Lookup lookup = node.Function as Lookup; if (lookup != null) { if (lookup.Name == "Object" && m_parser.Settings.IsModificationAllowed(TreeModifications.NewObjectToObjectLiteral)) { // no arguments -- the Object constructor with no arguments is the exact same as an empty // object literal if (node.Arguments == null || node.Arguments.Count == 0) { // replace our node with an object literal ObjectLiteral objLiteral = new ObjectLiteral(node.Context, m_parser, null, null); if (node.Parent.ReplaceChild(node, objLiteral)) { // and bail now. No need to recurse -- it's an empty literal return; } } else if (node.Arguments.Count == 1) { // one argument // check to see if it's an object literal. ObjectLiteral objectLiteral = node.Arguments[0] as ObjectLiteral; if (objectLiteral != null) { // the Object constructor with an argument that is a JavaScript object merely returns the // argument. Since the argument is an object literal, it is by definition a JavaScript object // and therefore we can replace the constructor call with the object literal node.Parent.ReplaceChild(node, objectLiteral); // don't forget to recurse the object now objectLiteral.Accept(this); // and then bail -- we don't want to process this call // operation any more; we've gotten rid of it return; } } } else if (lookup.Name == "Array" && m_parser.Settings.IsModificationAllowed(TreeModifications.NewArrayToArrayLiteral)) { // Array is trickier. // If there are no arguments, then just use []. // if there are multiple arguments, then use [arg0,arg1...argN]. // but if there is one argument and it's numeric, we can't crunch it. // also can't crunch if it's a function call or a member or something, since we won't // KNOW whether or not it's numeric. // // so first see if it even is a single-argument constant wrapper. ConstantWrapper constWrapper = (node.Arguments != null && node.Arguments.Count == 1 ? node.Arguments[0] as ConstantWrapper : null); // if the argument count is not one, then we crunch. // if the argument count IS one, we only crunch if we have a constant wrapper, // AND it's not numeric. if (node.Arguments == null || node.Arguments.Count != 1 || (constWrapper != null && !constWrapper.IsNumericLiteral)) { // create the new array literal object ArrayLiteral arrayLiteral = new ArrayLiteral(node.Context, m_parser, node.Arguments); // replace ourself within our parent if (node.Parent.ReplaceChild(node, arrayLiteral)) { // recurse arrayLiteral.Accept(this); // and bail -- we don't want to recurse this node any more return; } } } } } // if we are replacing resource references with strings generated from resource files // and this is a brackets call: lookup[args] ResourceStrings resourceStrings = m_parser.ResourceStrings; if (node.InBrackets && resourceStrings != null && resourceStrings.Count > 0) { // see if the root object is a lookup that corresponds to the // global value (not a local field) for our resource object // (same name) Lookup rootLookup = node.Function as Lookup; if (rootLookup != null && rootLookup.LocalField == null && string.CompareOrdinal(rootLookup.Name, resourceStrings.Name) == 0) { // we're going to replace this node with a string constant wrapper // but first we need to make sure that this is a valid lookup. // if the parameter contains anything that would vary at run-time, // then we need to throw an error. // the parser will always have either one or zero nodes in the arguments // arg list. We're not interested in zero args, so just make sure there is one if (node.Arguments.Count == 1) { // must be a constant wrapper ConstantWrapper argConstant = node.Arguments[0] as ConstantWrapper; if (argConstant != null) { string resourceName = argConstant.Value.ToString(); // get the localized string from the resources object ConstantWrapper resourceLiteral = new ConstantWrapper( resourceStrings[resourceName], PrimitiveType.String, node.Context, m_parser); // replace this node with localized string, analyze it, and bail // so we don't anaylze the tree we just replaced node.Parent.ReplaceChild(node, resourceLiteral); resourceLiteral.Accept(this); return; } else { // error! must be a constant node.Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } else { // error! can only be a single constant argument to the string resource object. // the parser will only have zero or one arguments, so this must be zero // (since the parser won't pass multiple args to a [] operator) node.Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } } // and finally, if this is a backets call and the argument is a constantwrapper that can // be an identifier, just change us to a member node: obj["prop"] to obj.prop. // but ONLY if the string value is "safe" to be an identifier. Even though the ECMA-262 // spec says certain Unicode categories are okay, in practice the various major browsers // all seem to have problems with certain characters in identifiers. Rather than risking // some browsers breaking when we change this syntax, don't do it for those "danger" categories. if (node.InBrackets && node.Arguments != null) { // see if there is a single, constant argument string argText = node.Arguments.SingleConstantArgument; if (argText != null) { // see if we want to replace the name string newName; if (m_parser.Settings.HasRenamePairs && m_parser.Settings.ManualRenamesProperties && m_parser.Settings.IsModificationAllowed(TreeModifications.PropertyRenaming) && !string.IsNullOrEmpty(newName = m_parser.Settings.GetNewName(argText))) { // yes -- we are going to replace the name, either as a string literal, or by converting // to a member-dot operation. // See if we can't turn it into a dot-operator. If we can't, then we just want to replace the operator with // a new constant wrapper. Otherwise we'll just replace the operator with a new constant wrapper. if (m_parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(newName) && !JSScanner.IsKeyword(newName, node.EnclosingScope.UseStrict)) { // the new name is safe to convert to a member-dot operator. // but we don't want to convert the node to the NEW name, because we still need to Analyze the // new member node -- and it might convert the new name to something else. So instead we're // just going to convert this existing string to a member node WITH THE OLD STRING, // and THEN analyze it (which will convert the old string to newName) Member replacementMember = new Member(node.Context, m_parser, node.Function, argText, node.Arguments[0].Context); node.Parent.ReplaceChild(node, replacementMember); // this analyze call will convert the old-name member to the newName value replacementMember.Accept(this); return; } else { // nope; can't convert to a dot-operator. // we're just going to replace the first argument with a new string literal // and continue along our merry way. node.Arguments.ReplaceChild(node.Arguments[0], new ConstantWrapper(newName, PrimitiveType.String, node.Arguments[0].Context, m_parser)); } } else if (m_parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(argText) && !JSScanner.IsKeyword(argText, node.EnclosingScope.UseStrict)) { // not a replacement, but the string literal is a safe identifier. So we will // replace this call node with a Member-dot operation Member replacementMember = new Member(node.Context, m_parser, node.Function, argText, node.Arguments[0].Context); node.Parent.ReplaceChild(node, replacementMember); replacementMember.Accept(this); return; } } } // call the base class to recurse base.Visit(node); // might have changed member = node.Function as Member; // call this AFTER recursing to give the fields a chance to resolve, because we only // want to make this replacement if we are working on the global Date object. if (!node.InBrackets && !node.IsConstructor && (node.Arguments == null || node.Arguments.Count == 0) && member != null && string.CompareOrdinal(member.Name, "getTime") == 0 && m_parser.Settings.IsModificationAllowed(TreeModifications.DateGetTimeToUnaryPlus)) { // this is not a constructor and it's not a brackets call, and there are no arguments. // if the function is a member operation to "getTime" and the object of the member is a // constructor call to the global "Date" object (not a local), then we want to replace the call // with a unary plus on the Date constructor. Converting to numeric type is the same as // calling getTime, so it's the equivalent with much fewer bytes. CallNode dateConstructor = member.Root as CallNode; if (dateConstructor != null && dateConstructor.IsConstructor) { // lookup for the predifined (not local) "Date" field Lookup lookup = dateConstructor.Function as Lookup; if (lookup != null && string.CompareOrdinal(lookup.Name, "Date") == 0 && lookup.LocalField == null) { // this is in the pattern: (new Date()).getTime() // we want to replace it with +new Date // use the same date constructor node as the operand NumericUnary unary = new NumericUnary(node.Context, m_parser, dateConstructor, JSToken.Plus); // replace us (the call to the getTime method) with this unary operator node.Parent.ReplaceChild(node, unary); // don't need to recurse on the unary operator. The operand has already // been analyzed when we recursed, and the unary operator wouldn't do anything // special anyway (since the operand is not a numeric constant) } } } else { var isEval = false; var lookup = node.Function as Lookup; if (lookup != null && string.CompareOrdinal(lookup.Name, "eval") == 0 && lookup.VariableField is JSPredefinedField) { // call to predefined eval function isEval = true; } else if (member != null && string.CompareOrdinal(member.Name, "eval") == 0) { // if this is a window.eval call, then we need to mark this scope as unknown just as // we would if this was a regular eval call. // (unless, of course, the parser settings say evals are safe) // call AFTER recursing so we know the left-hand side properties have had a chance to // lookup their fields to see if they are local or global if (member.LeftHandSide.IsWindowLookup) { // this is a call to window.eval() isEval = true; } } else { CallNode callNode = node.Function as CallNode; if (callNode != null && callNode.InBrackets && callNode.LeftHandSide.IsWindowLookup && callNode.Arguments.IsSingleConstantArgument("eval")) { // this is a call to window["eval"] isEval = true; } } if (isEval) { if (m_parser.Settings.EvalTreatment != EvalTreatment.Ignore) { // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } } } }
public override void Visit(BinaryOperator node) { if (node != null) { base.Visit(node); // see if this operation is subtracting zero from a lookup -- that is typically done to // coerce a value to numeric. There's a simpler way: unary plus operator. if (node.OperatorToken == JSToken.Minus && m_parser.Settings.IsModificationAllowed(TreeModifications.SimplifyStringToNumericConversion)) { Lookup lookup = node.Operand1 as Lookup; if (lookup != null) { ConstantWrapper right = node.Operand2 as ConstantWrapper; if (right != null && right.IsIntegerLiteral && right.ToNumber() == 0) { // okay, so we have "lookup - 0" // this is done frequently to force a value to be numeric. // There is an easier way: apply the unary + operator to it. NumericUnary unary = new NumericUnary(node.Context, m_parser, lookup, JSToken.Plus); node.Parent.ReplaceChild(node, unary); // because we recursed at the top of this function, we don't need to Analyze // the new Unary node. This visitor's method for NumericUnary only does something // if the operand is a constant -- and this one is a Lookup. And we already analyzed // the lookup. } } } else if ((node.OperatorToken == JSToken.StrictEqual || node.OperatorToken == JSToken.StrictNotEqual) && m_parser.Settings.IsModificationAllowed(TreeModifications.ReduceStrictOperatorIfTypesAreSame)) { PrimitiveType leftType = node.Operand1.FindPrimitiveType(); if (leftType != PrimitiveType.Other) { PrimitiveType rightType = node.Operand2.FindPrimitiveType(); if (leftType == rightType) { // the are the same known types. We can reduce the operators node.OperatorToken = node.OperatorToken == JSToken.StrictEqual ? JSToken.Equal : JSToken.NotEqual; } else if (rightType != PrimitiveType.Other) { // they are not the same, but they are both known. We can completely remove the operator // and replace it with true (!==) or false (===). node.Context.HandleError(JSError.StrictComparisonIsAlwaysTrueOrFalse, false); node.Parent.ReplaceChild( node, new ConstantWrapper(node.OperatorToken == JSToken.StrictNotEqual, PrimitiveType.Boolean, node.Context, m_parser)); } } } else if (node.IsAssign && ScopeStack.Peek().UseStrict) { // strict mode cannot assign to lookup "eval" or "arguments" var lookup = node.Operand1 as Lookup; if (lookup != null) { if (lookup.VariableField is JSArgumentsField || (lookup.VariableField is JSPredefinedField && string.CompareOrdinal(lookup.Name, "eval") == 0)) { node.Operand1.Context.HandleError(JSError.StrictModeInvalidAssign, true); } } } } }
public override void Visit(NumericUnary node) { if (node != null) { // recurse first, then check to see if the unary is still needed base.Visit(node); // if the operand is a numeric literal ConstantWrapper constantWrapper = node.Operand as ConstantWrapper; if (constantWrapper != null && constantWrapper.IsNumericLiteral) { // get the value of the constant. We've already screened it for numeric, so // we don't have to worry about catching any errors double doubleValue = constantWrapper.ToNumber(); // if this is a unary minus... if (node.OperatorToken == JSToken.Minus && m_parser.Settings.IsModificationAllowed(TreeModifications.ApplyUnaryMinusToNumericLiteral)) { // negate the value constantWrapper.Value = -doubleValue; // replace us with the negated constant if (node.Parent.ReplaceChild(node, constantWrapper)) { // the context for the minus will include the number (its operand), // but the constant will just be the number. Update the context on // the constant to be a copy of the context on the operator constantWrapper.Context = node.Context.Clone(); } } else if (node.OperatorToken == JSToken.Plus && m_parser.Settings.IsModificationAllowed(TreeModifications.RemoveUnaryPlusOnNumericLiteral)) { // +NEG is still negative, +POS is still positive, and +0 is still 0. // so just get rid of the unary operator altogether if (node.Parent.ReplaceChild(node, constantWrapper)) { // the context for the unary will include the number (its operand), // but the constant will just be the number. Update the context on // the constant to be a copy of the context on the operator constantWrapper.Context = node.Context.Clone(); } } } } }
// // IVisitor implementations // public override void Visit(BinaryOperator node) { if (node != null) { // depth-first base.Visit(node); if (m_parser.Settings.EvalLiteralExpressions) { // if this is an assign operator, an in, or an instanceof, then we won't // try to evaluate it if (!node.IsAssign && node.OperatorToken != JSToken.In && node.OperatorToken != JSToken.InstanceOf) { if (node.OperatorToken == JSToken.StrictEqual || node.OperatorToken == JSToken.StrictNotEqual) { // the operator is a strict equality (or not-equal). // check the primitive types of the two operands -- if they are known but not the same, we can // shortcut the whole process by just replacing this node with a boolean literal. var leftType = node.Operand1.FindPrimitiveType(); if (leftType != PrimitiveType.Other) { var rightType = node.Operand2.FindPrimitiveType(); if (rightType != PrimitiveType.Other) { // both sides are known if (leftType != rightType) { // they are not the same type -- replace with a boolean and bail node.Parent.ReplaceChild( node, new ConstantWrapper(node.OperatorToken == JSToken.StrictEqual ? false : true, PrimitiveType.Boolean, node.Context, m_parser)); return; } // they are the same type -- we can change the operator to simple equality/not equality node.OperatorToken = node.OperatorToken == JSToken.StrictEqual ? JSToken.Equal : JSToken.NotEqual; } } } // see if the left operand is a literal number, boolean, string, or null ConstantWrapper left = node.Operand1 as ConstantWrapper; if (left != null) { if (node.OperatorToken == JSToken.Comma) { // the comma operator evaluates the left, then evaluates the right and returns it. // but if the left is a literal, evaluating it doesn't DO anything, so we can replace the // entire operation with the right-hand operand ConstantWrapper rightConstant = node.Operand2 as ConstantWrapper; if (rightConstant != null) { // we'll replace the operator with the right-hand operand, BUT it's a constant, too. // first check to see if replacing this node with a constant will result in creating // a member-bracket operator that can be turned into a member-dot. If it is, then that // method will handle the replacement. But if it doesn't, then we should just replace // the comma with the right-hand operand. if (!ReplaceMemberBracketWithDot(node, rightConstant)) { node.Parent.ReplaceChild(node, rightConstant); } } else { // replace the comma operator with the right-hand operand node.Parent.ReplaceChild(node, node.Operand2); } } else { // see if the right operand is a literal number, boolean, string, or null ConstantWrapper right = node.Operand2 as ConstantWrapper; if (right != null) { // then they are both constants and we can evaluate the operation EvalThisOperator(node, left, right); } else { // see if the right is a binary operator that can be combined with ours BinaryOperator rightBinary = node.Operand2 as BinaryOperator; if (rightBinary != null) { ConstantWrapper rightLeft = rightBinary.Operand1 as ConstantWrapper; if (rightLeft != null) { // eval our left and the right-hand binary's left and put the combined operation as // the child of the right-hand binary EvalToTheRight(node, left, rightLeft, rightBinary); } else { ConstantWrapper rightRight = rightBinary.Operand2 as ConstantWrapper; if (rightRight != null) { EvalFarToTheRight(node, left, rightRight, rightBinary); } } } } } } else { // left is not a constantwrapper. See if the right is ConstantWrapper right = node.Operand2 as ConstantWrapper; if (right != null) { // the right is a constant. See if the the left is a binary operator... BinaryOperator leftBinary = node.Operand1 as BinaryOperator; if (leftBinary != null) { // ...with a constant on the right, and the operators can be combined ConstantWrapper leftRight = leftBinary.Operand2 as ConstantWrapper; if (leftRight != null) { EvalToTheLeft(node, right, leftRight, leftBinary); } else { ConstantWrapper leftLeft = leftBinary.Operand1 as ConstantWrapper; if (leftLeft != null) { EvalFarToTheLeft(node, right, leftLeft, leftBinary); } } } else if (m_parser.Settings.IsModificationAllowed(TreeModifications.SimplifyStringToNumericConversion)) { // see if it's a lookup and this is a minus operation and the constant is a zero Lookup lookup = node.Operand1 as Lookup; if (lookup != null && node.OperatorToken == JSToken.Minus && right.IsIntegerLiteral && right.ToNumber() == 0) { // okay, so we have "lookup - 0" // this is done frequently to force a value to be numeric. // There is an easier way: apply the unary + operator to it. NumericUnary unary = new NumericUnary(node.Context, m_parser, lookup, JSToken.Plus); node.Parent.ReplaceChild(node, unary); } } } // TODO: shouldn't we check if they BOTH are binary operators? (a*6)*(5*b) ==> a*30*b (for instance) } } } } }
public override void Visit(NumericUnary node) { if (node != null) { // depth-first base.Visit(node); if (m_parser.Settings.IsModificationAllowed(TreeModifications.EvaluateNumericExpressions)) { // see if our operand is a ConstantWrapper ConstantWrapper literalOperand = node.Operand as ConstantWrapper; if (literalOperand != null) { // must be number, boolean, string, or null switch (node.OperatorToken) { case JSToken.Plus: try { // replace with a constant representing operand.ToNumber, node.Parent.ReplaceChild(node, new ConstantWrapper(literalOperand.ToNumber(), PrimitiveType.Number, node.Context, m_parser)); } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } break; case JSToken.Minus: try { // replace with a constant representing the negative of operand.ToNumber node.Parent.ReplaceChild(node, new ConstantWrapper(-literalOperand.ToNumber(), PrimitiveType.Number, node.Context, m_parser)); } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } break; case JSToken.BitwiseNot: try { // replace with a constant representing the bitwise-not of operant.ToInt32 node.Parent.ReplaceChild(node, new ConstantWrapper(Convert.ToDouble(~literalOperand.ToInt32()), PrimitiveType.Number, node.Context, m_parser)); } catch (InvalidCastException) { // some kind of casting in ToNumber caused a situation where we don't want // to perform the combination on these operands } break; case JSToken.LogicalNot: // replace with a constant representing the opposite of operand.ToBoolean try { node.Parent.ReplaceChild(node, new ConstantWrapper(!literalOperand.ToBoolean(), PrimitiveType.Boolean, node.Context, m_parser)); } catch (InvalidCastException) { // ignore any invalid cast exceptions } break; } } } } }
public override void Visit(NumericUnary node) { if (node != null) { // if this is a unary logical-not operator, then we will just remove the // logical-not operation if (node.OperatorToken == JSToken.LogicalNot) { if (m_measure) { // measure // removes the not operator character, but also might remove parens that we would // no longer need. --m_delta; if (node.Operand is BinaryOperator || node.Operand is Conditional) { // those operators are lesser-precedence than the logical-not coperator and would've // added parens that we now don't need m_delta -= 2; } } else { // convert // just replace the not with its own operand node.Parent.ReplaceChild(node, node.Operand); } } else { // same logic as most nodes for the other operators TypicalHandler(node); } } }