예제 #1
0
        public override void Visit(Conditional node)
        {
            if (node != null)
            {
                // we have two choices for the conditional. Either:
                //  1. we wrap the whole thing in a logical-not operator, which means we also need to
                //     add parentheses, since conditional is lower-precedence than the logicial not, or
                //  2. apply the logical-not to both the true- and false-branches.
                // The first is guaranteed 3 additional characters. We have to check the delta for
                // each branch and add them together to know how much the second would cost. If it's
                // greater than 3, then we just want to not the whole thing.
                var notTrue         = new LogicalNot(node.TrueExpression, m_parser);
                var notFalse        = new LogicalNot(node.FalseExpression, m_parser);
                var costNottingBoth = notTrue.Measure() + notFalse.Measure();

                if (m_measure)
                {
                    // we're just measuring -- adjust the delta accordingly
                    // (the lesser of the two options)
                    m_delta += (costNottingBoth > 3) ? 3 : costNottingBoth;
                }
                else if (costNottingBoth > 3)
                {
                    // just wrap the whole thing
                    WrapWithLogicalNot(node);
                }
                else
                {
                    // less bytes to wrap each branch separately
                    node.TrueExpression.Accept(this);
                    node.FalseExpression.Accept(this);
                }
            }
        }
        public override void Visit(Conditional node)
        {
            if (node != null)
            {
                // we have two choices for the conditional. Either:
                //  1. we wrap the whole thing in a logical-not operator, which means we also need to
                //     add parentheses, since conditional is lower-precedence than the logicial not, or
                //  2. apply the logical-not to both the true- and false-branches.
                // The first is guaranteed 3 additional characters. We have to check the delta for
                // each branch and add them together to know how much the second would cost. If it's 
                // greater than 3, then we just want to not the whole thing.
                var notTrue = new LogicalNot(node.TrueExpression, m_parser);
                var notFalse = new LogicalNot(node.FalseExpression, m_parser);
                var costNottingBoth = notTrue.Measure() + notFalse.Measure();

                if (m_measure)
                {
                    // we're just measuring -- adjust the delta accordingly
                    // (the lesser of the two options)
                    m_delta += (costNottingBoth > 3) ? 3 : costNottingBoth;
                }
                else if (costNottingBoth > 3)
                {
                    // just wrap the whole thing
                    WrapWithLogicalNot(node);
                }
                else
                {
                    // less bytes to wrap each branch separately
                    node.TrueExpression.Accept(this);
                    node.FalseExpression.Accept(this);
                }
            }
        }
        private void IfConditionExpressionToExpression(IfNode ifNode, AstNode expression)
        {
            // but first -- which operator to use? if(a)b --> a&&b, and if(!a)b --> a||b
            // so determine which one is smaller: a or !a
            // assume we'll use the logical-and, since that doesn't require changing the condition
            var newOperator = JSToken.LogicalAnd;
            var logicalNot = new LogicalNot(ifNode.Condition, m_parser);
            if (logicalNot.Measure() < 0)
            {
                // !a is smaller, so apply it and use the logical-or operator
                logicalNot.Apply();
                newOperator = JSToken.LogicalOr;
            }

            // because the true block is an expression, we know it must only have
            // ONE statement in it, so we can just dereference it directly.
            var binaryOp = new BinaryOperator(ifNode.Context, m_parser)
                {
                    Operand1 = ifNode.Condition,
                    Operand2 = expression,
                    OperatorToken = newOperator,
                };

            // we don't need to analyse this new node because we've already analyzed
            // the pieces parts as part of the if. And this visitor's method for the BinaryOperator
            // doesn't really do anything else. Just replace our current node with this
            // new node
            ifNode.Parent.ReplaceChild(ifNode, binaryOp);
        }
        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. 
                            // transform: lookup - 0   => +lookup
                            var unary = new UnaryOperator(node.Context, m_parser)
                                {
                                    Operand = lookup,
                                    OperatorToken = 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 UnaryOperator 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 (===).
                            // transform: x !== y   =>   true
                            // transform: x === y   =>   false
                            node.Context.HandleError(JSError.StrictComparisonIsAlwaysTrueOrFalse, false);
                            node.Parent.ReplaceChild(
                                node,
                                new ConstantWrapper(node.OperatorToken == JSToken.StrictNotEqual, PrimitiveType.Boolean, node.Context, m_parser));

                            // because we are essentially removing the node from the AST, be sure to detach any references
                            DetachReferences.Apply(node);
                        }
                    }
                }
                else if (node.IsAssign)
                {
                    var lookup = node.Operand1 as Lookup;
                    if (lookup != null)
                    {
                        if (lookup.VariableField != null && lookup.VariableField.InitializationOnly)
                        {
                            // the field is an initialization-only field -- we should NOT be assigning to it
                            lookup.Context.HandleError(JSError.AssignmentToConstant, true);
                        }
                        else if (m_scopeStack.Peek().UseStrict)
                        {
                            if (lookup.VariableField == null || lookup.VariableField.FieldType == FieldType.UndefinedGlobal)
                            {
                                // strict mode cannot assign to undefined fields
                                node.Operand1.Context.HandleError(JSError.StrictModeUndefinedVariable, true);
                            }
                            else if(lookup.VariableField.FieldType == FieldType.Arguments
                                || (lookup.VariableField.FieldType == FieldType.Predefined && string.CompareOrdinal(lookup.Name, "eval") == 0))
                            {
                                // strict mode cannot assign to lookup "eval" or "arguments"
                                node.Operand1.Context.HandleError(JSError.StrictModeInvalidAssign, true);
                            }
                        }
                    }
                }
                else if ((node.Parent is Block || (node.Parent is CommaOperator && node.Parent.Parent is Block))
                    && (node.OperatorToken == JSToken.LogicalOr || node.OperatorToken == JSToken.LogicalAnd))
                {
                    // this is an expression statement where the operator is || or && -- basically
                    // it's a shortcut for an if-statement:
                    // expr1&&expr2; ==> if(expr1)expr2;
                    // expr1||expr2; ==> if(!expr1)expr2;
                    // let's check to see if the not of expr1 is smaller. If so, we can not the expression
                    // and change the operator
                    var logicalNot = new LogicalNot(node.Operand1, node.Parser);
                    if (logicalNot.Measure() < 0)
                    {
                        // it would be smaller! Change it.
                        // transform: expr1&&expr2 => !expr1||expr2
                        // transform: expr1||expr2 => !expr1&&expr2
                        logicalNot.Apply();
                        node.OperatorToken = node.OperatorToken == JSToken.LogicalAnd ? JSToken.LogicalOr : JSToken.LogicalAnd;
                    }
                }
            }
        }
        public override void Visit(IfNode node)
        {
            if (node != null)
            {
                if (m_parser.Settings.StripDebugStatements
                     && m_parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements))
                {
                    if (node.TrueBlock != null && node.TrueBlock.IsDebuggerStatement)
                    {
                        node.TrueBlock = null;
                    }

                    if (node.FalseBlock != null && node.FalseBlock.IsDebuggerStatement)
                    {
                        node.FalseBlock = null;
                    }
                }

                // recurse....
                base.Visit(node);

                // now check to see if the two branches are now empty.
                // if they are, null them out.
                if (node.TrueBlock != null && node.TrueBlock.Count == 0)
                {
                    node.TrueBlock = null;
                }
                if (node.FalseBlock != null && node.FalseBlock.Count == 0)
                {
                    node.FalseBlock = null;
                }

                if (node.TrueBlock != null && node.FalseBlock != null)
                {
                    // neither true block nor false block is null.
                    // if they're both expressions, convert them to a condition operator
                    if (node.TrueBlock.IsExpression && node.FalseBlock.IsExpression
                        && m_parser.Settings.IsModificationAllowed(TreeModifications.IfExpressionsToExpression))
                    {
                        // if this statement has both true and false blocks, and they are both expressions,
                        // then we can simplify this to a conditional expression.
                        // because the blocks are expressions, we know they only have ONE statement in them,
                        // so we can just dereference them directly.
                        Conditional conditional;
                        var logicalNot = new LogicalNot(node.Condition, m_parser);
                        if (logicalNot.Measure() < 0)
                        {
                            // applying a logical-not makes the condition smaller -- reverse the branches
                            logicalNot.Apply();
                            conditional = new Conditional(node.Context, m_parser)
                                {
                                    Condition = node.Condition,
                                    TrueExpression = node.FalseBlock[0],
                                    FalseExpression = node.TrueBlock[0]
                                };
                        }
                        else
                        {
                            // regular order
                            conditional = new Conditional(node.Context, m_parser)
                                {
                                    Condition = node.Condition,
                                    TrueExpression = node.TrueBlock[0],
                                    FalseExpression = node.FalseBlock[0]
                                };
                        }

                        node.Parent.ReplaceChild(
                            node,
                            conditional);

                        Optimize(conditional);
                    }
                    else
                    {
                        // see if logical-notting the condition produces something smaller
                        var logicalNot = new LogicalNot(node.Condition, m_parser);
                        if (logicalNot.Measure() < 0)
                        {
                            // it does -- not the condition and swap the branches
                            logicalNot.Apply();
                            node.SwapBranches();
                        }

                        // see if the true- and false-branches each contain only a single statement
                        if (node.TrueBlock.Count == 1 && node.FalseBlock.Count == 1)
                        {
                            // they do -- see if the true-branch's statement is a return-statement
                            var trueReturn = node.TrueBlock[0] as ReturnNode;
                            if (trueReturn != null && trueReturn.Operand != null)
                            {
                                // it is -- see if the false-branch is also a return statement
                                var falseReturn = node.FalseBlock[0] as ReturnNode;
                                if (falseReturn != null && falseReturn.Operand != null)
                                {
                                    // transform: if(cond)return expr1;else return expr2 to return cond?expr1:expr2
                                    var conditional = new Conditional(null, m_parser)
                                        {
                                            Condition = node.Condition,
                                            TrueExpression = trueReturn.Operand,
                                            FalseExpression = falseReturn.Operand
                                        };

                                    // create a new return node from the conditional and replace
                                    // our if-node with it
                                    var returnNode = new ReturnNode(node.Context, m_parser)
                                        {
                                            Operand = conditional
                                        };

                                    node.Parent.ReplaceChild(
                                        node,
                                        returnNode);

                                    Optimize(conditional);
                                }
                            }
                        }
                    }
                }
                else if (node.FalseBlock != null)
                {
                    // true block must be 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 (node.FalseBlock.IsExpression
                        && m_parser.Settings.IsModificationAllowed(TreeModifications.IfConditionCallToConditionAndCall))
                    {
                        // if (cond); else expr ==> cond || expr
                        // but first -- which operator to use? if(a);else b --> a||b, and if(!a);else b --> a&&b
                        // so determine which one is smaller: a or !a
                        // assume we'll use the logical-or, since that doesn't require changing the condition
                        var newOperator = JSToken.LogicalOr;
                        var logicalNot = new LogicalNot(node.Condition, m_parser);
                        if (logicalNot.Measure() < 0)
                        {
                            // !a is smaller, so apply it and use the logical-or operator
                            logicalNot.Apply();
                            newOperator = JSToken.LogicalAnd;
                        }

                        var binaryOp = new BinaryOperator(node.Context, m_parser)
                            {
                                Operand1 = node.Condition,
                                Operand2 = node.FalseBlock[0],
                                OperatorToken = newOperator,
                            };

                        // we don't need to analyse this new node because we've already analyzed
                        // the pieces parts as part of the if. And this visitor's method for the BinaryOperator
                        // doesn't really do anything else. Just replace our current node with this
                        // new node
                        node.Parent.ReplaceChild(node, binaryOp);
                    }
                    else if (m_parser.Settings.IsModificationAllowed(TreeModifications.IfConditionFalseToIfNotConditionTrue))
                    {
                        // logical-not the condition
                        // if(cond);else stmt ==> if(!cond)stmt
                        var logicalNot = new LogicalNot(node.Condition, m_parser);
                        logicalNot.Apply();

                        // and swap the branches
                        node.SwapBranches();
                    }
                }
                else if (node.TrueBlock != null)
                {
                    // false block must be null
                    if (node.TrueBlock.IsExpression
                        && m_parser.Settings.IsModificationAllowed(TreeModifications.IfConditionCallToConditionAndCall))
                    {
                        // convert the if-node to an expression
                        IfConditionExpressionToExpression(node, node.TrueBlock[0]);
                    }
                }
                else if (m_parser.Settings.IsModificationAllowed(TreeModifications.IfEmptyToExpression))
                {
                    // NEITHER branches have anything now!

                    // 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.
                    // but how do we KNOW there are no side-effects?
                    // if the condition is a constant or operations on constants, delete it.
                    // or if the condition itself is a debugger statement -- a call, lookup, or member.
                    var remove = node.Condition.IsConstant || node.Condition.IsDebuggerStatement;
                    if (remove)
                    {
                        // we're pretty sure there are no side-effects; remove it altogether
                        node.Parent.ReplaceChild(node, null);
                    }
                    else
                    {
                        // 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
                        // no need to analyze -- we already recursed
                        node.Parent.ReplaceChild(node, node.Condition);
                    }
                }

                if (node.FalseBlock == null
                    && node.TrueBlock != null
                    && node.TrueBlock.Count == 1
                    && m_parser.Settings.IsModificationAllowed(TreeModifications.CombineNestedIfs))
                {
                    var nestedIf = node.TrueBlock[0] as IfNode;
                    if (nestedIf != null && nestedIf.FalseBlock == null)
                    {
                        // we have nested if-blocks.
                        // transform if(cond1)if(cond2){...} to if(cond1&&cond2){...}
                        // change the first if-statement's condition to be cond1&&cond2
                        // move the nested if-statement's true block to the outer if-statement
                        node.Condition = new BinaryOperator(null, m_parser)
                            {
                                Operand1 = node.Condition,
                                Operand2 = nestedIf.Condition,
                                OperatorToken = JSToken.LogicalAnd
                            };
                        node.TrueBlock = nestedIf.TrueBlock;
                    }
                }
            }
        }