Home | History | Annotate | Download | only in compiler
      1 /*
      2  * Copyright (C) 2010 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.clearsilver.jsilver.compiler;
     18 
     19 import static com.google.clearsilver.jsilver.compiler.JavaExpression.BooleanLiteralExpression;
     20 import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn;
     21 import static com.google.clearsilver.jsilver.compiler.JavaExpression.string;
     22 import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
     23 import com.google.clearsilver.jsilver.syntax.node.AAddExpression;
     24 import com.google.clearsilver.jsilver.syntax.node.AAndExpression;
     25 import com.google.clearsilver.jsilver.syntax.node.ADecimalExpression;
     26 import com.google.clearsilver.jsilver.syntax.node.ADescendVariable;
     27 import com.google.clearsilver.jsilver.syntax.node.ADivideExpression;
     28 import com.google.clearsilver.jsilver.syntax.node.AEqExpression;
     29 import com.google.clearsilver.jsilver.syntax.node.AExistsExpression;
     30 import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression;
     31 import com.google.clearsilver.jsilver.syntax.node.AGtExpression;
     32 import com.google.clearsilver.jsilver.syntax.node.AGteExpression;
     33 import com.google.clearsilver.jsilver.syntax.node.AHexExpression;
     34 import com.google.clearsilver.jsilver.syntax.node.ALtExpression;
     35 import com.google.clearsilver.jsilver.syntax.node.ALteExpression;
     36 import com.google.clearsilver.jsilver.syntax.node.AModuloExpression;
     37 import com.google.clearsilver.jsilver.syntax.node.AMultiplyExpression;
     38 import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
     39 import com.google.clearsilver.jsilver.syntax.node.ANeExpression;
     40 import com.google.clearsilver.jsilver.syntax.node.ANegativeExpression;
     41 import com.google.clearsilver.jsilver.syntax.node.ANotExpression;
     42 import com.google.clearsilver.jsilver.syntax.node.ANumericAddExpression;
     43 import com.google.clearsilver.jsilver.syntax.node.ANumericEqExpression;
     44 import com.google.clearsilver.jsilver.syntax.node.ANumericExpression;
     45 import com.google.clearsilver.jsilver.syntax.node.ANumericNeExpression;
     46 import com.google.clearsilver.jsilver.syntax.node.AOrExpression;
     47 import com.google.clearsilver.jsilver.syntax.node.AStringExpression;
     48 import com.google.clearsilver.jsilver.syntax.node.ASubtractExpression;
     49 import com.google.clearsilver.jsilver.syntax.node.AVariableExpression;
     50 import com.google.clearsilver.jsilver.syntax.node.PExpression;
     51 
     52 import java.util.LinkedList;
     53 
     54 /**
     55  * Generates a JavaExpression to determine whether a given CS expression should be escaped before
     56  * displaying. If propagateEscapeStatus is enabled, string and numeric literals are not escaped, nor
     57  * is the output of an escaping function. If not, any expression that contains an escaping function
     58  * is not escaped. This maintains compatibility with the way ClearSilver works.
     59  */
     60 public class EscapingEvaluator extends DepthFirstAdapter {
     61 
     62   private JavaExpression currentEscapingExpression;
     63   private boolean propagateEscapeStatus;
     64   private final VariableTranslator variableTranslator;
     65 
     66   public EscapingEvaluator(VariableTranslator variableTranslator) {
     67     super();
     68     this.variableTranslator = variableTranslator;
     69   }
     70 
     71   /**
     72    * Returns a JavaExpression that can be used to decide whether a given variable should be escaped.
     73    *
     74    * @param expression variable expression to be evaluated.
     75    * @param propagateEscapeStatus Whether to propagate the variable's escape status.
     76    *
     77    * @return Returns a {@code JavaExpression} representing a boolean expression that evaluates to
     78    *         {@code true} if {@code expression} should be exempted from escaping and {@code false}
     79    *         otherwise.
     80    */
     81   public JavaExpression computeIfExemptFromEscaping(PExpression expression,
     82       boolean propagateEscapeStatus) {
     83     if (propagateEscapeStatus) {
     84       return computeForPropagateStatus(expression);
     85     }
     86     return computeEscaping(expression, propagateEscapeStatus);
     87   }
     88 
     89   private JavaExpression computeForPropagateStatus(PExpression expression) {
     90     // This function generates a boolean expression that evaluates to true
     91     // if the input should be exempt from escaping. As this should only be
     92     // called when PropagateStatus is enabled we must check EscapeMode as
     93     // well as isPartiallyEscaped.
     94     // The interpreter mode equivalent of this boolean expression would be :
     95     // ((value.getEscapeMode() != EscapeMode.ESCAPE_NONE) || value.isPartiallyEscaped())
     96 
     97     JavaExpression escapeMode = computeEscaping(expression, true);
     98     JavaExpression partiallyEscaped = computeEscaping(expression, false);
     99 
    100     JavaExpression escapeModeCheck =
    101         JavaExpression.infix(JavaExpression.Type.BOOLEAN, "!=", escapeMode, JavaExpression
    102             .symbol("EscapeMode.ESCAPE_NONE"));
    103 
    104     return JavaExpression.infix(JavaExpression.Type.BOOLEAN, "||", escapeModeCheck,
    105         partiallyEscaped);
    106   }
    107 
    108   /**
    109    * Compute the escaping applied to the given expression. Uses {@code propagateEscapeStatus} to
    110    * determine how to treat constants, and whether escaping is required on a part of the expression
    111    * or the whole expression.
    112    */
    113   public JavaExpression computeEscaping(PExpression expression, boolean propagateEscapeStatus) {
    114     try {
    115       assert currentEscapingExpression == null : "Not reentrant";
    116       this.propagateEscapeStatus = propagateEscapeStatus;
    117       expression.apply(this);
    118       assert currentEscapingExpression != null : "No escaping calculated";
    119       return currentEscapingExpression;
    120     } finally {
    121       currentEscapingExpression = null;
    122     }
    123   }
    124 
    125   private void setEscaping(JavaExpression escaping) {
    126     currentEscapingExpression = escaping;
    127   }
    128 
    129   /**
    130    * String concatenation. Do not escape the combined string, if either of the halves has been
    131    * escaped.
    132    */
    133   @Override
    134   public void caseAAddExpression(AAddExpression node) {
    135     node.getLeft().apply(this);
    136     JavaExpression left = currentEscapingExpression;
    137     node.getRight().apply(this);
    138     JavaExpression right = currentEscapingExpression;
    139 
    140     setEscaping(or(left, right));
    141   }
    142 
    143   /**
    144    * Process AST node for a function (e.g. dosomething(...)).
    145    */
    146   @Override
    147   public void caseAFunctionExpression(AFunctionExpression node) {
    148     LinkedList<PExpression> argsList = node.getArgs();
    149     PExpression[] args = argsList.toArray(new PExpression[argsList.size()]);
    150 
    151     // Because the function name may have dots in, the parser would have broken
    152     // it into a little node tree which we need to walk to reconstruct the
    153     // full name.
    154     final StringBuilder fullFunctionName = new StringBuilder();
    155     node.getName().apply(new DepthFirstAdapter() {
    156 
    157       @Override
    158       public void caseANameVariable(ANameVariable node11) {
    159         fullFunctionName.append(node11.getWord().getText());
    160       }
    161 
    162       @Override
    163       public void caseADescendVariable(ADescendVariable node12) {
    164         node12.getParent().apply(this);
    165         fullFunctionName.append('.');
    166         node12.getChild().apply(this);
    167       }
    168     });
    169 
    170     setEscaping(function(fullFunctionName.toString(), args));
    171   }
    172 
    173   /**
    174    * Do not escape the output of a function if either the function is an escaping function, or any
    175    * of its parameters have been escaped.
    176    */
    177   private JavaExpression function(String name, PExpression... csExpressions) {
    178     if (propagateEscapeStatus) {
    179       // context.isEscapingFunction("name") ? EscapeMode.ESCAPE_IS_CONSTANT : EscapeMode.ESCAPE_NONE
    180       return JavaExpression.inlineIf(JavaExpression.Type.UNKNOWN, callOn(
    181           JavaExpression.Type.BOOLEAN, TemplateTranslator.CONTEXT, "isEscapingFunction",
    182           string(name)), JavaExpression.symbol("EscapeMode.ESCAPE_IS_CONSTANT"), JavaExpression
    183           .symbol("EscapeMode.ESCAPE_NONE"));
    184     }
    185     JavaExpression finalExpression = BooleanLiteralExpression.FALSE;
    186     for (int i = 0; i < csExpressions.length; i++) {
    187       // Will put result in currentEscapingExpression.
    188       csExpressions[i].apply(this);
    189       finalExpression = or(finalExpression, currentEscapingExpression);
    190     }
    191     JavaExpression funcExpr =
    192         callOn(JavaExpression.Type.BOOLEAN, TemplateTranslator.CONTEXT, "isEscapingFunction",
    193             string(name));
    194     return or(finalExpression, funcExpr);
    195   }
    196 
    197   /*
    198    * This function tries to optimize the output expression where possible: instead of
    199    * "(false || context.isEscapingFunction())" it returns "context.isEscapingFunction()".
    200    */
    201   private JavaExpression or(JavaExpression first, JavaExpression second) {
    202     if (propagateEscapeStatus) {
    203       return JavaExpression.callOn(JavaExpression.symbol("EscapeMode"), "combineModes", first,
    204           second);
    205     }
    206 
    207     if (first instanceof BooleanLiteralExpression) {
    208       BooleanLiteralExpression expr = (BooleanLiteralExpression) first;
    209       if (expr.getValue()) {
    210         return expr;
    211       } else {
    212         return second;
    213       }
    214     }
    215     if (second instanceof BooleanLiteralExpression) {
    216       BooleanLiteralExpression expr = (BooleanLiteralExpression) second;
    217       if (expr.getValue()) {
    218         return expr;
    219       } else {
    220         return first;
    221       }
    222     }
    223     return JavaExpression.infix(JavaExpression.Type.BOOLEAN, "||", first, second);
    224   }
    225 
    226   /*
    227    * All the following operators have no effect on escaping, so just default to 'false'.
    228    */
    229 
    230   /**
    231    * Process AST node for a variable (e.g. a.b.c).
    232    */
    233   @Override
    234   public void caseAVariableExpression(AVariableExpression node) {
    235     if (propagateEscapeStatus) {
    236       JavaExpression varName = variableTranslator.translate(node.getVariable());
    237       setEscaping(callOn(TemplateTranslator.DATA_CONTEXT, "findVariableEscapeMode", varName));
    238     } else {
    239       setDefaultEscaping();
    240     }
    241   }
    242 
    243   private void setDefaultEscaping() {
    244     if (propagateEscapeStatus) {
    245       setEscaping(JavaExpression.symbol("EscapeMode.ESCAPE_IS_CONSTANT"));
    246     } else {
    247       setEscaping(BooleanLiteralExpression.FALSE);
    248     }
    249   }
    250 
    251   /**
    252    * Process AST node for a string (e.g. "hello").
    253    */
    254   @Override
    255   public void caseAStringExpression(AStringExpression node) {
    256     setDefaultEscaping();
    257   }
    258 
    259   /**
    260    * Process AST node for a decimal integer (e.g. 123).
    261    */
    262   @Override
    263   public void caseADecimalExpression(ADecimalExpression node) {
    264     setDefaultEscaping();
    265   }
    266 
    267   /**
    268    * Process AST node for a hex integer (e.g. 0x1AB).
    269    */
    270   @Override
    271   public void caseAHexExpression(AHexExpression node) {
    272     setDefaultEscaping();
    273   }
    274 
    275   @Override
    276   public void caseANumericExpression(ANumericExpression node) {
    277     setDefaultEscaping();
    278   }
    279 
    280   @Override
    281   public void caseANotExpression(ANotExpression node) {
    282     setDefaultEscaping();
    283   }
    284 
    285   @Override
    286   public void caseAExistsExpression(AExistsExpression node) {
    287     setDefaultEscaping();
    288   }
    289 
    290   @Override
    291   public void caseAEqExpression(AEqExpression node) {
    292     setDefaultEscaping();
    293   }
    294 
    295   @Override
    296   public void caseANumericEqExpression(ANumericEqExpression node) {
    297     setDefaultEscaping();
    298   }
    299 
    300   @Override
    301   public void caseANeExpression(ANeExpression node) {
    302     setDefaultEscaping();
    303   }
    304 
    305   @Override
    306   public void caseANumericNeExpression(ANumericNeExpression node) {
    307     setDefaultEscaping();
    308   }
    309 
    310   @Override
    311   public void caseALtExpression(ALtExpression node) {
    312     setDefaultEscaping();
    313   }
    314 
    315   @Override
    316   public void caseAGtExpression(AGtExpression node) {
    317     setDefaultEscaping();
    318   }
    319 
    320   @Override
    321   public void caseALteExpression(ALteExpression node) {
    322     setDefaultEscaping();
    323   }
    324 
    325   @Override
    326   public void caseAGteExpression(AGteExpression node) {
    327     setDefaultEscaping();
    328   }
    329 
    330   @Override
    331   public void caseAAndExpression(AAndExpression node) {
    332     setDefaultEscaping();
    333   }
    334 
    335   @Override
    336   public void caseAOrExpression(AOrExpression node) {
    337     setDefaultEscaping();
    338   }
    339 
    340   @Override
    341   public void caseANumericAddExpression(ANumericAddExpression node) {
    342     setDefaultEscaping();
    343   }
    344 
    345   @Override
    346   public void caseASubtractExpression(ASubtractExpression node) {
    347     setDefaultEscaping();
    348   }
    349 
    350   @Override
    351   public void caseAMultiplyExpression(AMultiplyExpression node) {
    352     setDefaultEscaping();
    353   }
    354 
    355   @Override
    356   public void caseADivideExpression(ADivideExpression node) {
    357     setDefaultEscaping();
    358   }
    359 
    360   @Override
    361   public void caseAModuloExpression(AModuloExpression node) {
    362     setDefaultEscaping();
    363   }
    364 
    365   @Override
    366   public void caseANegativeExpression(ANegativeExpression node) {
    367     setDefaultEscaping();
    368   }
    369 
    370 }
    371