Revision: 4678
Author:   [email protected]
Date:     Mon Dec 12 16:56:03 2011
Log:      more cajoler speedups
http://codereview.appspot.com/5486048

For my current testcase, this is about a 10% speedup to the cajoler.

- The rewriter relies extensively on a marker "synthetic", which
  is stored as a key-value pair in a HashMap.  The map lookups
  have a noticeable cost.

  This change adds a boolean "synthetic" to AbstractParseTreeNode
  and uses that instead of the HashMap.  This is the largest
  speedup in this change.

- Rewriter#expand will pre-filter rules in a ruleset based on
  specific nodetype.  So, it knows that when it's examining a
  Declaration node, it doesn't have to try running a rule that
  can only match AssignOperation.  It determines a rule's
  matchable nodetype by parsing the rule's "matches" attribute.

  Some rules do not have a parseable "matches", because the match
  condition is not expressible with quasi syntax, so these rules
  are always checked against every nodetype, even though they
  might apply to just a specific nodetype.

  This change adds another attribute "matchNode", which lets us
  specify a nodetype that the rule must match.

- The nodetype SpecialOperation represents several semantically
  different javascript operators, and this interferes with the
  rule filtering in Rewriter#expand.  For instance, a rule that
  matches "@v.@p" is a SpecialOperation nodetype, which means the
  rule is also tried against the "delete" operator, the comma
  operator, etc.

  This change adds another type of rule pre-filter that looks at
  the specific operator being matched.

[email protected]

http://code.google.com/p/google-caja/source/detail?r=4678

Added:
 /trunk/src/com/google/caja/parser/quasiliteral/NodeTypeFilter.java
 /trunk/src/com/google/caja/parser/quasiliteral/QuasiCache.java
 /trunk/src/com/google/caja/parser/quasiliteral/RuleFilter.java
 /trunk/src/com/google/caja/parser/quasiliteral/SpecialOpFilter.java
Modified:
 /trunk/build.xml
 /trunk/src/com/google/caja/lang/css/Partitions.java
 /trunk/src/com/google/caja/parser/AbstractParseTreeNode.java
 /trunk/src/com/google/caja/parser/js/Operator.java
 /trunk/src/com/google/caja/parser/js/ReturnStmt.java
 /trunk/src/com/google/caja/parser/js/SyntheticNodes.java
 /trunk/src/com/google/caja/parser/quasiliteral/ES53Rewriter.java
/trunk/src/com/google/caja/parser/quasiliteral/IllegalReferenceCheckRewriter.java
 /trunk/src/com/google/caja/parser/quasiliteral/NonAsciiCheckVisitor.java
 /trunk/src/com/google/caja/parser/quasiliteral/QuasiBuilder.java
 /trunk/src/com/google/caja/parser/quasiliteral/Rewriter.java
 /trunk/src/com/google/caja/parser/quasiliteral/Rule.java
 /trunk/src/com/google/caja/parser/quasiliteral/RuleChain.java
 /trunk/src/com/google/caja/parser/quasiliteral/RuleDescription.java
 /trunk/src/com/google/caja/parser/quasiliteral/Scope.java
 /trunk/src/com/google/caja/parser/quasiliteral/SimpleQuasiNode.java
 /trunk/src/com/google/caja/parser/quasiliteral/SyntheticRuleSet.java
 /trunk/src/com/google/caja/parser/quasiliteral/TrailingUnderscoresHole.java
 /trunk/tests/com/google/caja/parser/ParseTreeNodeTest.java

=======================================
--- /dev/null
+++ /trunk/src/com/google/caja/parser/quasiliteral/NodeTypeFilter.java Mon Dec 12 16:56:03 2011
@@ -0,0 +1,34 @@
+// Copyright (C) 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.caja.parser.quasiliteral;
+
+package com.google.caja.parser.quasiliteral;
+
+import com.google.caja.parser.ParseTreeNode;
+
+public class NodeTypeFilter extends RuleFilter<Class<? extends ParseTreeNode>> {
+
+  public NodeTypeFilter(RuleChain ruleChain) {
+    super(ruleChain);
+  }
+
+  @Override
+  Class<? extends ParseTreeNode> getKey(ParseTreeNode node) {
+    return QuasiBuilder.fuzzType(node.getClass());
+  }
+
+  @Override
+  boolean canMatch(Rule rule, Class<? extends ParseTreeNode> key) {
+    return rule.canMatch(key);
+  }
+}
=======================================
--- /dev/null
+++ /trunk/src/com/google/caja/parser/quasiliteral/QuasiCache.java Mon Dec 12 16:56:03 2011
@@ -0,0 +1,46 @@
+// Copyright (C) 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.caja.parser.quasiliteral;
+
+package com.google.caja.parser.quasiliteral;
+
+import com.google.caja.lexer.ParseException;
+import com.google.caja.util.Maps;
+
+import java.util.Map;
+
+/**
+ * {@code QuasiCache} caches the quasi trees generated by parsing strings.
+ * This is a separate class rather than built into {@code QuasiBuilder}
+ * because QuasiBuilder returns mutable quasi trees.
+ *
+ * <p>{@code QuasiCache} assumes callers will not mutate the returned quasi
+ * trees, but does not enforce immutability.
+ */
+public class QuasiCache {
+  private static Map<String, QuasiNode> cache = Maps.newHashMap();
+
+  public static QuasiNode parse(String pattern) {
+    if (cache.containsKey(pattern)) {
+      return cache.get(pattern);
+    }
+    try {
+      QuasiNode q = QuasiBuilder.parseQuasiNode(pattern);
+      cache.put(pattern, q);
+      return q;
+    } catch (ParseException e) {
+      cache.put(pattern, null);
+      return null;
+    }
+  }
+}
=======================================
--- /dev/null
+++ /trunk/src/com/google/caja/parser/quasiliteral/RuleFilter.java Mon Dec 12 16:56:03 2011
@@ -0,0 +1,81 @@
+// Copyright (C) 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.caja.parser.quasiliteral;
+
+package com.google.caja.parser.quasiliteral;
+
+import com.google.caja.parser.ParseTreeNode;
+import com.google.caja.util.Maps;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public abstract class RuleFilter<KeyT> {
+  private final RuleChain ruleChain;
+  private final Map<KeyT, List<Rule>> cache = Maps.newHashMap();
+
+  private static boolean debug = false;
+
+  public RuleFilter(RuleChain ruleChain) {
+    this.ruleChain = ruleChain;
+  }
+
+  public void reset() {
+    cache.clear();
+  }
+
+  public List<Rule> rulesFor(ParseTreeNode node) {
+    KeyT key = getKey(node);
+    if (key == null) {
+      return ruleChain.getAllRules();
+    }
+    List<Rule> someRules = cache.get(key);
+    if (someRules == null) {
+      someRules = computeRulesFor(node, key);
+      cache.put(key, someRules);
+      if (debug) { debugShowRules(someRules, node); }
+    }
+    return someRules;
+  }
+
+  private List<Rule> computeRulesFor(ParseTreeNode node, KeyT key) {
+    List<Rule> someRules = new ArrayList<Rule>();
+    for (Rule rule : ruleChain.getAllRules()) {
+      if (canMatch(rule, key)) {
+        someRules.add(rule);
+      }
+    }
+    return Collections.unmodifiableList(someRules);
+  }
+
+  private void debugShowRules(List<Rule> someRules, ParseTreeNode node) {
+    System.err.println(someRules.size() + " rules for " + node);
+    StringBuilder names = new StringBuilder("  ");
+    int ll = 2;
+    for (Rule rule: someRules) {
+      String name = rule.getName();
+      ll += 1 + name.length();
+      if (75 < ll) {
+        names.append("\n  ");
+        ll = 2 + 1 + name.length();
+      }
+      names.append(" ").append(name);
+    }
+    System.err.println(names);
+  }
+
+  abstract KeyT getKey(ParseTreeNode node);
+  abstract boolean canMatch(Rule rule, KeyT key);
+}
=======================================
--- /dev/null
+++ /trunk/src/com/google/caja/parser/quasiliteral/SpecialOpFilter.java Mon Dec 12 16:56:03 2011
@@ -0,0 +1,48 @@
+// Copyright (C) 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.caja.parser.quasiliteral;
+
+package com.google.caja.parser.quasiliteral;
+
+import com.google.caja.parser.ParseTreeNode;
+import com.google.caja.parser.js.Operation;
+import com.google.caja.parser.js.Operator;
+
+public class SpecialOpFilter extends RuleFilter<Operator> {
+
+  public SpecialOpFilter (RuleChain ruleChain) {
+    super(ruleChain);
+  }
+
+  @Override
+  Operator getKey(ParseTreeNode node) {
+    return ((Operation) node).getOperator();
+  }
+
+  @Override
+  boolean canMatch(Rule rule, Operator op) {
+    return ruleOperator(rule) == op || rule.canMatch(Operation.class);
+  }
+
+  private Operator ruleOperator(Rule rule) {
+    QuasiNode qp = QuasiCache.parse(rule.getRuleDescription().matches());
+    if (qp != null && qp instanceof SimpleQuasiNode) {
+      SimpleQuasiNode sqp = (SimpleQuasiNode) qp;
+      if (Operation.class.isAssignableFrom(sqp.getMatchedClass())) {
+        return (Operator) sqp.getValue();
+      }
+    }
+    return null;
+  }
+
+}
=======================================
--- /trunk/build.xml    Mon Dec 12 11:44:56 2011
+++ /trunk/build.xml    Mon Dec 12 16:56:03 2011
@@ -1770,7 +1770,7 @@
              classpathref="classpath.run" />
   </target>

-  <target name="precajole" depends="Precajoler">
+  <target name="precajole" depends="Precajoler" unless="precajole.skip">
     <precajole to="${lib.caja}/precajole/data">
       <source file="${3pp.jquery}/1.6.4/jquery-1.6.4.js">
         http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.js
=======================================
--- /trunk/src/com/google/caja/lang/css/Partitions.java Fri Nov 11 15:28:32 2011 +++ /trunk/src/com/google/caja/lang/css/Partitions.java Mon Dec 12 16:56:03 2011
@@ -14,6 +14,10 @@

 package com.google.caja.lang.css;

+import com.google.caja.util.Bag;
+import com.google.caja.util.Lists;
+import com.google.caja.util.Maps;
+
 import java.lang.reflect.Array;
 import java.util.Arrays;
 import java.util.BitSet;
@@ -22,10 +26,6 @@
 import java.util.Map;
 import java.util.Set;

-import com.google.caja.util.Bag;
-import com.google.caja.util.Lists;
-import com.google.caja.util.Maps;
-
 class Partitions {
   static class Partition<T> {
     /**
@@ -171,7 +171,6 @@
     for (int i = 0; i < nonZeroCols.count; ++i) { nonZeroCols.els[i] = i; }

     ISlice[] matrix = new ISlice[nSets];
-    int matrixSum = 0;
     {
       int i = 0;
       BitSet rowBits = new BitSet(universe.length);
@@ -181,7 +180,6 @@
           int index = elToIndex.get(el);
           if (!rowBits.get(index)) {
             rowBits.set(index);
-            ++matrixSum;
           }
         }
         ISlice row = new ISlice();
=======================================
--- /trunk/src/com/google/caja/parser/AbstractParseTreeNode.java Mon Nov 28 01:31:08 2011 +++ /trunk/src/com/google/caja/parser/AbstractParseTreeNode.java Mon Dec 12 16:56:03 2011
@@ -45,6 +45,7 @@
   private List<Token<?>> comments = Collections.<Token<?>>emptyList();
   private SyntheticAttributes attributes;
   private boolean immutable = false;
+  private boolean synthetic = false;

   @Override
   public boolean makeImmutable() {
@@ -57,6 +58,15 @@

   @Override
   public boolean isImmutable() { return immutable; }
+
+  public boolean isSynthetic() { return synthetic; }
+
+  public void setSynthetic(boolean value) {
+    if (immutable) {
+      throw new UnsupportedOperationException();
+    }
+    synthetic = value;
+  }

   /**
* The list of children. This can be appended to for efficient initialization
@@ -419,6 +429,7 @@
     if (attributes != null) {
       cloned.attributes = new SyntheticAttributes(attributes);
     }
+    cloned.synthetic = synthetic;
     return cloned;
   }

=======================================
--- /trunk/src/com/google/caja/parser/js/Operator.java Thu Dec 10 17:39:38 2009 +++ /trunk/src/com/google/caja/parser/js/Operator.java Mon Dec 12 16:56:03 2011
@@ -27,7 +27,6 @@
  * @author [email protected]
  */
 public enum Operator {
-
   // from precedence tables at
   // http://www.codehouse.com/javascript/precedence/
   SQUARE_BRACKET(OperatorType.BRACKET, OperatorCategory.SPECIAL,
=======================================
--- /trunk/src/com/google/caja/parser/js/ReturnStmt.java Fri Nov 18 12:29:44 2011 +++ /trunk/src/com/google/caja/parser/js/ReturnStmt.java Mon Dec 12 16:56:03 2011
@@ -15,7 +15,6 @@
 package com.google.caja.parser.js;

 import com.google.caja.lexer.FilePosition;
-import com.google.caja.parser.ParseTreeNode;
 import com.google.caja.reporting.RenderContext;
 import com.google.javascript.jscomp.jsonml.JsonML;
 import com.google.javascript.jscomp.jsonml.TagType;
=======================================
--- /trunk/src/com/google/caja/parser/js/SyntheticNodes.java Thu Jul 22 10:23:41 2010 +++ /trunk/src/com/google/caja/parser/js/SyntheticNodes.java Mon Dec 12 16:56:03 2011
@@ -14,8 +14,8 @@

 package com.google.caja.parser.js;

+import com.google.caja.parser.AbstractParseTreeNode;
 import com.google.caja.parser.ParseTreeNode;
-import com.google.caja.util.SyntheticAttributeKey;

 // TODO(erights,ihab): Remove the concept of "synthetic" entirely.
 /**
@@ -34,37 +34,36 @@
  * @author [email protected]
  */
 public final class SyntheticNodes {
-  /**
-   * Indicates that a node breaks the caja rules in some way, but that this
-   * is OK since it comes from a privileged source, not user code.
-   */
-  public static final SyntheticAttributeKey<Boolean> SYNTHETIC
-      = new SyntheticAttributeKey<Boolean>(Boolean.class, "synthetic");

   public static boolean isSynthesizable(ParseTreeNode node) {
     return (node instanceof Identifier && node.getValue() != null)
         || node instanceof FunctionConstructor
         || node instanceof UncajoledModule;
   }
+
+  public static boolean is(ParseTreeNode t) {
+    return t instanceof AbstractParseTreeNode
+        && ((AbstractParseTreeNode) t).isSynthetic();
+  }

   /**
* A convenience function used to mark {@link #isSynthesizable synthesizable}
    * nodes created during source->javascript translation as
-   * {@link #SYNTHETIC synthetic}.  Nodes corresponsing to javascript
+   * {@link #SYNTHETIC synthetic}.  Nodes corresponding to javascript
    * embedded in the original will not be synthetic.
    * <p>
    * This is meant to be imported statically.
    */
   public static <T extends ParseTreeNode> T s(T t) {
     if (isSynthesizable(t)) {
-      t.getAttributes().set(SYNTHETIC, Boolean.TRUE);
+      ((AbstractParseTreeNode) t).setSynthetic(true);
     }
     return t;
   }

   /** A synthetic identifier may occupy the <code>*__</code> namespace. */
   public static Identifier s(Identifier t) {
-    t.getAttributes().set(SYNTHETIC, Boolean.TRUE);
+    t.setSynthetic(true);
     return t;
   }

@@ -74,7 +73,12 @@
    * function.
    */
   public static FunctionConstructor s(FunctionConstructor t) {
-    t.getAttributes().set(SYNTHETIC, Boolean.TRUE);
+    t.setSynthetic(true);
+    return t;
+  }
+
+  public static Block s(Block t) {
+    t.setSynthetic(true);
     return t;
   }

=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/ES53Rewriter.java Fri Nov 18 12:29:44 2011 +++ /trunk/src/com/google/caja/parser/quasiliteral/ES53Rewriter.java Mon Dec 12 16:56:03 2011
@@ -14,6 +14,8 @@

 package com.google.caja.parser.quasiliteral;

+import static com.google.caja.parser.js.SyntheticNodes.s;
+
 import com.google.caja.lexer.FilePosition;
 import com.google.caja.parser.ParseTreeNode;
 import com.google.caja.parser.ParseTreeNodeContainer;
@@ -70,8 +72,6 @@
 import com.google.caja.util.Pair;
 import com.google.caja.util.Sets;

-import static com.google.caja.parser.js.SyntheticNodes.s;
-
 import java.net.URI;
 import java.util.Arrays;
 import java.util.Collection;
@@ -231,7 +231,8 @@
           name="translatedCode",
           synopsis="Allow code received from a *->JS translator",
reason="Translated code should not be treated as user supplied JS.",
-          matches="<TranslatedCode>")
+          matches="<TranslatedCode>",
+          matchNode=TranslatedCode.class)
       public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
         if (node instanceof TranslatedCode) {
           Statement rewritten
@@ -322,6 +323,7 @@
               + "contents of the 'substitutes' of this rule.",
reason="So that the module loader can be invoked to load a module.",
           matches="<an UncajoledModule>",
+          matchNode=UncajoledModule.class,
           substitutes=(
               ""
               + "(/*@synthetic*/{"
@@ -709,7 +711,8 @@
           name="varBadSuffixDeclaration",
synopsis="Statically reject if a variable with `__` suffix is found.",
           reason="Caja reserves the `__` suffix for internal use.",
- matches="<approx>(var|function) @v__ ...", // TODO(mikesamuel): limit
+          matches="<approx> var @v__ ...",
+          matchNode=Declaration.class,
           substitutes="<reject>")
       public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
         if (node instanceof Declaration) {
@@ -725,6 +728,33 @@
       }
     },

+
+    // This rule is separate from varBadSuffixDeclaration, because
+    // although FunctionDeclaration is a subclass of Declaration,
+    // it fuzzes as a FunctionConstructor.
+    new Rule() {
+      @Override
+      @RuleDescription(
+          name="functionBadSuffixDeclaration",
+ synopsis="Statically reject if a variable with `__` suffix is found.",
+          reason="Caja reserves the `__` suffix for internal use.",
+          matches="<approx> function @v__ ...",
+          matchNode=FunctionDeclaration.class,
+          substitutes="<reject>")
+      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
+        if (node instanceof FunctionDeclaration) {
+          Identifier name = ((FunctionDeclaration) node).getIdentifier();
+          if (name.getValue().endsWith("__")) {
+            mq.addMessage(
+ RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
+                node.getFilePosition(), this, node);
+            return node;
+          }
+        }
+        return NONE;
+      }
+    },
+
     // TODO(metaweta): Use fastpath
     new Rule() {
       @Override
@@ -733,6 +763,7 @@
synopsis="Global vars are rewritten to be properties of IMPORTS___.",
           reason="",
           matches="@v",
+          matchNode=Reference.class,
           substitutes="IMPORTS___.@fp ?" +
               "IMPORTS___.@v :" +
               "___.ri(IMPORTS___, @vname)")
@@ -762,7 +793,8 @@
           name="varDefault",
           synopsis="Any remaining uses of a variable name are preserved.",
           reason="",
-          matches="@v",  // TODO(mikesamuel): limit further
+          matches="@v",
+          matchNode=Reference.class,
           substitutes="@v")
       public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
         Map<String, ParseTreeNode> bindings = match(node);
@@ -1300,8 +1332,8 @@
           name="setReadModifyWriteLocalVar",
           synopsis="",
           reason="",
-          // TODO(mikesamuel): better lower limit
           matches="<approx> @x @op= @y",
+          matchNode=AssignOperation.class,
           substitutes="<approx> @x = @x @op @y")
// Handle x += 3 and similar ops by rewriting them using the assignment // delegate, "x += y" => "x = x + y", with deconstructReadAssignOperand
@@ -1334,8 +1366,8 @@
       @RuleDescription(
           name="setIncrDecr",
           synopsis="Handle pre and post ++ and --.",
-          // TODO(mikesamuel): better lower bound
           matches="<approx> ++@x but any {pre,post}{in,de}crement will do",
+          matchNode=AssignOperation.class,
           reason="")
       public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
         if (!(node instanceof AssignOperation)) { return NONE; }
@@ -1853,7 +1885,8 @@
           name="mapBadKeySuffix",
synopsis="Statically reject a property whose name ends with `__`",
           reason="",
-          matches="\"@k__\": @v",
+          matches="<approx> \"@k__\": @v",
+          matchNode=ObjProperty.class,
           substitutes="<reject>")
       public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
         if (node instanceof ObjProperty) {
@@ -1876,7 +1909,8 @@
           name="objectProperty",
           synopsis="nymize object properties",
           reason="",
-          matches="\"@k\": @v",
+          matches="<approx> \"@k\": @v",
+          matchNode=ObjProperty.class,
           substitutes="<nymized>")
       public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
         if (node instanceof ObjProperty) {
@@ -2064,6 +2098,7 @@
reason="So that every use of a regex literal creates a new instance" + " to prevent state from leaking via interned literals. This"
               + " is consistent with the way ES4 treates regex literals.",
+          matchNode=RegexpLiteral.class,
           substitutes="new RegExp.new___(@pattern, @modifiers?)")
       public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
         if (node instanceof RegexpLiteral) {
@@ -2091,7 +2126,8 @@
+ " could lead to a directive appearing in an illegal position" + " since directives must appear at the beginning of a program"
               + " or function body, not in an arbitrary block",
-          matches="'use';",
+          matches="<approx> 'use';",
+          matchNode=DirectivePrologue.class,
           substitutes=";")
       public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
         if (node instanceof DirectivePrologue) {
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/IllegalReferenceCheckRewriter.java Mon Aug 10 14:00:55 2009 +++ /trunk/src/com/google/caja/parser/quasiliteral/IllegalReferenceCheckRewriter.java Mon Dec 12 16:56:03 2011
@@ -38,8 +38,7 @@
reason="Double underscore identifiers may not be mentioned by Caja "
               + "code")
       public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
-        if (node instanceof Identifier
-            && !node.getAttributes().is(SyntheticNodes.SYNTHETIC)) {
+        if (node instanceof Identifier && !SyntheticNodes.is(node)) {
           String name = ((Identifier)node).getValue();
           if (name != null && name.endsWith("__")) {
             mq.addMessage(
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/NonAsciiCheckVisitor.java Mon Aug 4 18:06:15 2008 +++ /trunk/src/com/google/caja/parser/quasiliteral/NonAsciiCheckVisitor.java Mon Dec 12 16:56:03 2011
@@ -40,7 +40,7 @@
     if (!(ac.node instanceof Identifier)) { return true; }
     Identifier ident = (Identifier) ac.node;
     String name = ident.getName();
-    if (!ident.getAttributes().is(SyntheticNodes.SYNTHETIC)
+    if (!SyntheticNodes.is(ident)
         && name != null && !name.matches("^[a-zA-Z_$][a-zA-Z0-9_$]*$")) {
       mq.addMessage(
           RewriterMessageType.NONASCII_IDENTIFIER,
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/QuasiBuilder.java Mon Nov 28 01:31:08 2011 +++ /trunk/src/com/google/caja/parser/quasiliteral/QuasiBuilder.java Mon Dec 12 16:56:03 2011
@@ -29,8 +29,11 @@
 import com.google.caja.parser.js.Expression;
 import com.google.caja.parser.js.ExpressionStmt;
 import com.google.caja.parser.js.FormalParam;
+import com.google.caja.parser.js.FunctionConstructor;
 import com.google.caja.parser.js.FunctionDeclaration;
 import com.google.caja.parser.js.Identifier;
+import com.google.caja.parser.js.LabeledStatement;
+import com.google.caja.parser.js.LabeledStmtWrapper;
 import com.google.caja.parser.js.ObjProperty;
 import com.google.caja.parser.js.ObjectConstructor;
 import com.google.caja.parser.js.Parser;
@@ -194,6 +197,26 @@
public static QuasiNode parseQuasiNode(String pattern) throws ParseException {
     return parseQuasiNode(FilePosition.UNKNOWN.source(), pattern);
   }
+
+  /** This parallels the fuzzing done in
+   * {@link QuasiBuilder#parseQuasiNode(InputSource, String)} */
+  // TODO(felix8a): why is this comment a lie?
+  public static Class<? extends ParseTreeNode> fuzzType(
+      Class<? extends ParseTreeNode> nodeClass) {
+    if (nodeClass == FunctionDeclaration.class) {
+      return FunctionConstructor.class;
+    }
+    if (nodeClass == Expression.class) {
+      return ExpressionStmt.class;
+    }
+    if (nodeClass == Reference.class) {
+      return Identifier.class;
+    }
+    if (nodeClass == LabeledStmtWrapper.class) {
+      return LabeledStatement.class;
+    }
+    return nodeClass;
+  }

   private static QuasiNode getPatternNode(String patternText) {
     if (!patternCache.containsKey(patternText)) {
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/Rewriter.java Mon Nov 28 01:31:08 2011 +++ /trunk/src/com/google/caja/parser/quasiliteral/Rewriter.java Mon Dec 12 16:56:03 2011
@@ -29,7 +29,6 @@
 import com.google.caja.reporting.MessageLevel;
 import com.google.caja.reporting.MessageQueue;
 import com.google.caja.reporting.RenderContext;
-import com.google.caja.util.SyntheticAttributes;

 import java.util.HashSet;
 import java.util.Set;
@@ -218,8 +217,7 @@
   // Returns true if check passed.
   private boolean checkTainted(ParseTreeNode node, MessageQueue mq) {
     if (tainted.contains(node)) {
-      SyntheticAttributes attrs = node.getAttributes();
-      if (!attrs.is(SyntheticNodes.SYNTHETIC)) {
+      if (!SyntheticNodes.is(node)) {
         mq.addMessage(
             RewriterMessageType.UNSEEN_NODE_LEFT_OVER, node,
             node.getFilePosition());
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/Rule.java Fri Nov 18 12:29:44 2011 +++ /trunk/src/com/google/caja/parser/quasiliteral/Rule.java Mon Dec 12 16:56:03 2011
@@ -14,6 +14,8 @@

 package com.google.caja.parser.quasiliteral;

+import static com.google.caja.parser.js.SyntheticNodes.s;
+
 import com.google.caja.lexer.FilePosition;
 import com.google.caja.lexer.Keyword;
 import com.google.caja.lexer.TokenConsumer;
@@ -31,12 +33,12 @@
 import com.google.caja.parser.js.Identifier;
 import com.google.caja.parser.js.Literal;
 import com.google.caja.parser.js.Noop;
+import com.google.caja.parser.js.ObjectConstructor;
 import com.google.caja.parser.js.Operation;
 import com.google.caja.parser.js.Operator;
 import com.google.caja.parser.js.Reference;
 import com.google.caja.parser.js.StringLiteral;
 import com.google.caja.parser.js.SyntheticNodes;
-import static com.google.caja.parser.js.SyntheticNodes.s;
 import com.google.caja.reporting.MessageContext;
 import com.google.caja.reporting.MessagePart;
 import com.google.caja.reporting.RenderContext;
@@ -139,6 +141,35 @@
     }
     return description;
   }
+
+  public boolean canMatch(Class<? extends ParseTreeNode> nodeType) {
+    RuleDescription desc = getRuleDescription();
+    Class<? extends ParseTreeNode> bound = desc.matchNode();
+    if (bound != ParseTreeNode.class) {
+      // If the rule has an explicit matchNode=, use it.
+      bound = QuasiBuilder.fuzzType(bound);
+
+    } else {
+      // Otherwise, try parsing the matches= pattern
+      String pattern = desc.matches();
+      bound = quasiLowerBound(QuasiCache.parse(pattern));
+    }
+    return bound.isAssignableFrom(nodeType);
+  }
+
+ private static Class<? extends ParseTreeNode> quasiLowerBound(QuasiNode p) {
+    if (p == null) {
+      return ParseTreeNode.class;
+    } else  if (p instanceof SimpleQuasiNode) {
+ return QuasiBuilder.fuzzType(((SimpleQuasiNode) p).getMatchedClass());
+    } else if (p instanceof ObjectCtorQuasiNode) {
+      return ObjectConstructor.class;
+    } else if (p instanceof StringLiteralQuasiNode) {
+      return StringLiteral.class;
+    } else {
+      return ParseTreeNode.class;
+    }
+  }

   /**
    * Process the given input, returning a rewritten node.
@@ -184,6 +215,9 @@
         node.getValue(),
         rewrittenChildren);
     result.getAttributes().putAll(node.getAttributes());
+    if (SyntheticNodes.is(node)) {
+      SyntheticNodes.s(result);
+    }

     result.makeImmutable();
     return result;
@@ -363,7 +397,7 @@
   }

   protected static boolean isSynthetic(Identifier node) {
-    return node.getAttributes().is(SyntheticNodes.SYNTHETIC);
+    return node.isSynthetic();
   }

   protected static boolean isSynthetic(Reference node) {
@@ -371,7 +405,7 @@
   }

   protected static boolean isSynthetic(FunctionConstructor node) {
-    return node.getAttributes().is(SyntheticNodes.SYNTHETIC);
+    return node.isSynthetic();
   }

   protected static String getReferenceName(ParseTreeNode ref) {
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/RuleChain.java Fri Feb 26 16:15:29 2010 +++ /trunk/src/com/google/caja/parser/quasiliteral/RuleChain.java Mon Dec 12 16:56:03 2011
@@ -14,24 +14,12 @@

 package com.google.caja.parser.quasiliteral;

-import com.google.caja.lexer.ParseException;
 import com.google.caja.parser.ParseTreeNode;
-import com.google.caja.parser.js.Expression;
-import com.google.caja.parser.js.ExpressionStmt;
-import com.google.caja.parser.js.FunctionConstructor;
-import com.google.caja.parser.js.FunctionDeclaration;
-import com.google.caja.parser.js.Identifier;
-import com.google.caja.parser.js.LabeledStatement;
-import com.google.caja.parser.js.LabeledStmtWrapper;
-import com.google.caja.parser.js.ObjectConstructor;
-import com.google.caja.parser.js.Reference;
-import com.google.caja.parser.js.StringLiteral;
+import com.google.caja.parser.js.SpecialOperation;

 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;

 /**
  * An order-significant series of rules.
@@ -40,91 +28,28 @@
  */
 public final class RuleChain {
   private final List<Rule> rules = new ArrayList<Rule>();
-  private final Map<Class<? extends ParseTreeNode>, List<Rule>> filtered
-      = new HashMap<Class<? extends ParseTreeNode>, List<Rule>>();
+
+  private final NodeTypeFilter nodeTypeFilter = new NodeTypeFilter(this);
+  private final SpecialOpFilter specOpFilter = new SpecialOpFilter(this);

   public void add(Rule r) {
     rules.add(r);
-    filtered.clear();
+    nodeTypeFilter.reset();
+    specOpFilter.reset();
   }

-  /**
- * Return at least the rules applicable to the given node, but possibly more.
-   */
-  public List<Rule> applicableTo(ParseTreeNode node) {
-    Class<? extends ParseTreeNode> nodeType = toNodeType(node.getClass());
-    List<Rule> filteredRules = filtered.get(nodeType);
-    if (filteredRules == null) {
-      computeRulesFor(nodeType);
-      filteredRules = filtered.get(nodeType);
-    }
-    return filteredRules;
-  }
-
-  public Iterable<Rule> getAllRules() {
+  public List<Rule> getAllRules() {
     return Collections.unmodifiableList(rules);
   }

   /**
- * Caches quasi text to a conservative lower bound for ParseTreeNode types
-   * that it might match.
-   * This does not assume that quasi-strings parse to a valid parse tree.
-   * Some of the quasi-strings look like "<Approximately> @foo = @bar" and
-   * so on parse failures we return a lower bound of ParseTreeNode.
+ * Return at least the rules applicable to the given node, but possibly more.
    */
- private static final Map<String, Class<? extends ParseTreeNode>> lowerBounds
-      = Collections.synchronizedMap(
-          new HashMap<String, Class<? extends ParseTreeNode>>());
-  static {
- lowerBounds.put(null, ParseTreeNode.class); // lower bound for no pattern
-  }
-  private void computeRulesFor(Class<? extends ParseTreeNode> nodeType) {
-    List<Rule> applicableRules = new ArrayList<Rule>();
-    for (Rule rule : rules) {
-      String pattern = rule.getRuleDescription().matches();
-      Class<? extends ParseTreeNode> lowerBound = lowerBounds.get(pattern);
-      if (lowerBound == null) {
-        try {
-          QuasiNode p = QuasiBuilder.parseQuasiNode(pattern);
-          if (p instanceof SimpleQuasiNode) {
- lowerBound = toNodeType(((SimpleQuasiNode) p).getMatchedClass());
-          } else if (p instanceof ObjectCtorQuasiNode) {
-            lowerBound = ObjectConstructor.class;
-          } else if (p instanceof StringLiteralQuasiNode) {
-            lowerBound = StringLiteral.class;
-          } else {
-            lowerBound = ParseTreeNode.class;
-          }
-        } catch (ParseException ex) {
- // If the match pattern can't be parsed then assume the lowest lower - // bound. This may happen if the match string is documentation, not
-          // a real pattern.
-          lowerBound = ParseTreeNode.class;
-        }
-        lowerBounds.put(pattern, lowerBound);
-      }
-      if (lowerBound.isAssignableFrom(nodeType)) {
-        applicableRules.add(rule);
-      }
-    }
-    filtered.put(nodeType, Collections.unmodifiableList(applicableRules));
-  }
-
-  /** Parallels fuzzing done in QuasiBuilder.parseQuasiNode */
-  private static Class<? extends ParseTreeNode> toNodeType(
-      Class<? extends ParseTreeNode> nodeClass) {
-    if (nodeClass == FunctionDeclaration.class) {
-      return FunctionConstructor.class;
-    }
-    if (nodeClass == Expression.class) {
-      return ExpressionStmt.class;
-    }
-    if (nodeClass == Reference.class) {
-      return Identifier.class;
-    }
-    if (nodeClass == LabeledStmtWrapper.class) {
-      return LabeledStatement.class;
-    }
-    return nodeClass;
+  public List<Rule> applicableTo(ParseTreeNode node) {
+    if (node instanceof SpecialOperation) {
+      return specOpFilter.rulesFor(node);
+    } else {
+      return nodeTypeFilter.rulesFor(node);
+    }
   }
 }
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/RuleDescription.java Wed Sep 24 19:39:44 2008 +++ /trunk/src/com/google/caja/parser/quasiliteral/RuleDescription.java Mon Dec 12 16:56:03 2011
@@ -14,6 +14,8 @@

 package com.google.caja.parser.quasiliteral;

+import com.google.caja.parser.ParseTreeNode;
+
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -22,7 +24,7 @@

 /**
  * Describes each transformation rule
- *
+ *
  * @author [email protected] (Jasvir Nagra)
  *
  */
@@ -34,27 +36,33 @@
    * @return Name of the transformation {@code Rule}
    */
   String name();
-
+
   /**
    * @return Summary of the effect of {@code Rule}
    */
   String synopsis();
-
+
   /**
    * @return Reason that necessitates {@code Rule}
-   */
+   */
   String reason();

   /**
    * @return Quasiliteral pattern matched by {@code Rule}
-   */
+   */
   String matches() default "<UNKNOWN>";

   /**
    * @return Code generated by {@code Rule}
-   */
+   */
   String substitutes() default "<UNKNOWN>";

+  /**
+   * @return Type of node this rule applies to.  This could go away
+   * if {@code matches} were more expressive.
+   */
+  Class<? extends ParseTreeNode> matchNode() default ParseTreeNode.class;
+
// TODO(ihab.awad): Add metadata for a rule indicating that it always fails, // and perhaps in this case add a sanity check that "substitutes" is empty.
 }
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/Scope.java Fri Nov 18 12:29:44 2011 +++ /trunk/src/com/google/caja/parser/quasiliteral/Scope.java Mon Dec 12 16:56:03 2011
@@ -14,39 +14,37 @@

 package com.google.caja.parser.quasiliteral;

+import static com.google.caja.parser.js.SyntheticNodes.s;
+import static com.google.caja.parser.quasiliteral.QuasiBuilder.substV;
+
 import com.google.caja.lexer.FilePosition;
 import com.google.caja.lexer.Keyword;
 import com.google.caja.parser.ParseTreeNode;
 import com.google.caja.parser.ParseTreeNodeContainer;
-import com.google.caja.parser.js.DirectivePrologue;
-import com.google.caja.parser.js.Expression;
-import com.google.caja.parser.js.MultiDeclaration;
-import com.google.caja.parser.js.SyntheticNodes;
+import com.google.caja.parser.js.Block;
 import com.google.caja.parser.js.CatchStmt;
 import com.google.caja.parser.js.Declaration;
+import com.google.caja.parser.js.DirectivePrologue;
+import com.google.caja.parser.js.Expression;
 import com.google.caja.parser.js.FunctionConstructor;
 import com.google.caja.parser.js.FunctionDeclaration;
 import com.google.caja.parser.js.Identifier;
-import com.google.caja.parser.js.Reference;
-import com.google.caja.parser.js.Block;
-import com.google.caja.parser.js.Statement;
+import com.google.caja.parser.js.MultiDeclaration;
 import com.google.caja.parser.js.Operation;
 import com.google.caja.parser.js.Operator;
+import com.google.caja.parser.js.Reference;
+import com.google.caja.parser.js.Statement;
 import com.google.caja.parser.js.UncajoledModule;
 import com.google.caja.parser.js.scope.ScopeType;
 import com.google.caja.reporting.Message;
 import com.google.caja.reporting.MessageLevel;
 import com.google.caja.reporting.MessagePart;
-import com.google.caja.reporting.MessageQueue;
 import com.google.caja.reporting.MessageType;
 import com.google.caja.util.Lists;
 import com.google.caja.util.Maps;
 import com.google.caja.util.Pair;
 import com.google.caja.util.Sets;

-import static com.google.caja.parser.js.SyntheticNodes.s;
-import static com.google.caja.parser.quasiliteral.QuasiBuilder.substV;
-
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -569,7 +567,7 @@
     }

     private void visitFunctionConstructor(FunctionConstructor node) {
-      if (node.getAttributes().is(SyntheticNodes.SYNTHETIC)) {
+      if (node.isSynthetic()) {
         // Synthetic function definitions are treated as "transparent"; our
// scope analysis should "see through" them as though they were just
         // part of the surrounding code.
@@ -609,7 +607,7 @@
     }

     private void visitReference(Reference node) {
- if (!node.getIdentifier().getAttributes().is(SyntheticNodes.SYNTHETIC) &&
+      if (!node.getIdentifier().isSynthetic() &&
           !exceptionVariables.contains(node.getIdentifierName())) {
         references.add(node);
       }
@@ -686,7 +684,7 @@
              || maskedType == LocalType.CAUGHT_EXCEPTION)
             ? MessageLevel.ERROR
             : MessageLevel.LINT);
-        if (!ident.getAttributes().is(SyntheticNodes.SYNTHETIC) &&
+        if (!ident.isSynthetic() &&
             ident.getFilePosition() != null) {
           s.rewriter.mq.getMessages().add(new Message(
               MessageType.MASKING_SYMBOL,
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/SimpleQuasiNode.java Fri Feb 26 16:15:29 2010 +++ /trunk/src/com/google/caja/parser/quasiliteral/SimpleQuasiNode.java Mon Dec 12 16:56:03 2011
@@ -34,7 +34,7 @@
 public class SimpleQuasiNode extends QuasiNode {
   private final Class<? extends ParseTreeNode> clazz;
   private final Object value;
-  private Equivalence valueComparator;
+  private final Equivalence valueComparator;

   protected SimpleQuasiNode(
       Class<? extends ParseTreeNode> clazz, Object value,
@@ -124,6 +124,8 @@

   /** The class of node matched by this quasi node. */
   public Class<? extends ParseTreeNode> getMatchedClass() { return clazz; }
+
+  public Object getValue() { return value; }

   @Override
   public String toString() {
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/SyntheticRuleSet.java Fri Nov 18 12:29:44 2011 +++ /trunk/src/com/google/caja/parser/quasiliteral/SyntheticRuleSet.java Mon Dec 12 16:56:03 2011
@@ -53,6 +53,7 @@
           synopsis="Pass through synthetic references.",
           reason="A variable may not be mentionable otherwise.",
           matches="/* synthetic */ @ref",
+          matchNode=Reference.class,
           substitutes="<expanded>")
       public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
         if (node instanceof Reference) {
@@ -211,6 +212,7 @@
synopsis="Pass through synthetic formals which are unmentionable.", reason="Synthetic code might need local variables for safe-keeping.",
           matches="/* synthetic */ @x in a parameter list",
+          matchNode=FormalParam.class,
           substitutes="<expanded>")
       public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
         if (node instanceof FormalParam
=======================================
--- /trunk/src/com/google/caja/parser/quasiliteral/TrailingUnderscoresHole.java Wed Feb 11 20:17:12 2009 +++ /trunk/src/com/google/caja/parser/quasiliteral/TrailingUnderscoresHole.java Mon Dec 12 16:56:03 2011
@@ -47,6 +47,7 @@
             specimen.getFilePosition(),
             value.substring(0, value.length() - trailing.length()));
         shortIdentifier.getAttributes().putAll(specimen.getAttributes());
+        shortIdentifier.setSynthetic(specimen.isSynthetic());

         return putIfDeepEquals(
             bindings,
@@ -66,6 +67,7 @@
     Identifier withSuffix = new Identifier(
         withoutSuffix.getFilePosition(), n.getValue() + trailing);
     withSuffix.getAttributes().putAll(withoutSuffix.getAttributes());
+    withSuffix.setSynthetic(withoutSuffix.isSynthetic());
     substitutes.add(withSuffix);
     return true;
   }
=======================================
--- /trunk/tests/com/google/caja/parser/ParseTreeNodeTest.java Mon Nov 28 01:31:08 2011 +++ /trunk/tests/com/google/caja/parser/ParseTreeNodeTest.java Mon Dec 12 16:56:03 2011
@@ -698,6 +698,8 @@
     assertSerializable(p);
   }

+  @SuppressWarnings("unchecked")
+  // setComments causes a warning about generic array for varargs
   public final void testImmutability() throws Exception {
     Block n = new Block(
         FilePosition.UNKNOWN,
@@ -720,7 +722,7 @@
       fail();
     } catch (UnsupportedOperationException e) {}
     try {
-      n.getAttributes().set(SyntheticNodes.SYNTHETIC, true);
+      SyntheticNodes.s(n);
       fail();
     } catch (UnsupportedOperationException e) {}
   }

Reply via email to