Package com.google.caja.parser.quasiliteral

Source Code of com.google.caja.parser.quasiliteral.Rewriter

// Copyright (C) 2007 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;

import com.google.caja.SomethingWidgyHappenedError;
import com.google.caja.lexer.TokenConsumer;
import com.google.caja.parser.ParseTreeNode;
import com.google.caja.parser.ParseTreeNodeContainer;
import com.google.caja.parser.js.Declaration;
import com.google.caja.parser.js.Reference;
import com.google.caja.parser.js.Statement;
import com.google.caja.parser.js.SyntheticNodes;
import com.google.caja.render.JsPrettyPrinter;
import com.google.caja.reporting.MessageLevel;
import com.google.caja.reporting.MessageQueue;
import com.google.caja.reporting.RenderContext;

import java.util.HashSet;
import java.util.Set;

/**
* Rewrites a JavaScript parse tree.
*
* @author ihab.awad@gmail.com (Ihab Awad)
*/
public class Rewriter {

  /**
   * Annotations on {@code rules} in subclasses of {@code Rewriter} are
   * are collated and documented by {@code RulesDoclet}
   */
  private final RuleChain rules = new RuleChain();
  private final Set<String> ruleNames = new HashSet<String>();
  private final Set<ParseTreeNode> tainted = new HashSet<ParseTreeNode>();
  private final Set<ParseTreeNode> forSideEffect = new HashSet<ParseTreeNode>();
  final MessageQueue mq;
  private final boolean taintChecking;
  private final boolean logging;

  /**
   * Creates a new Rewriter.
   *
   * @param logging whether this Rewriter should log the details of
   *     rule firings to standard error.
   */
  public Rewriter(MessageQueue mq, boolean taintChecking, boolean logging) {
    assert mq != null;
    this.mq = mq;
    this.taintChecking = taintChecking;
    this.logging = logging;
  }

  /**
   * Returns the rules of this rewriter
   */
  public Iterable<? extends Rule> getRules() {
    return rules.getAllRules();
  }

  /**
   * Alternate form of {@link #expand(ParseTreeNode)}.
   *
   * @param node a parse tree node to expand.
   * @param scope the scope in which 'node' is defined.
   * @return the expanded parse tree node.
   */
  protected final ParseTreeNode expand(ParseTreeNode node, Scope scope) {
    boolean debug = false;
    Iterable<Rule> run = debug ? rules.getAllRules() : rules.applicableTo(node);
    for (Rule rule : run) {
      try {
        ParseTreeNode result = rule.fire(node, scope);
        if (result != Rule.NONE) {
          if (debug && !rules.applicableTo(node).contains(rule)) {
            throw new SomethingWidgyHappenedError(
                rule.getName() + " should be applicable to " + node);
          }
          if (logging) { logResults(rule, node, result, null); }
          result.makeImmutable();
          return result;
        }
      } catch (RuntimeException ex) {
        if (logging) { logResults(rule, node, null, ex); }
        throw ex;
      }
    }

    mq.addMessage(
        RewriterMessageType.UNMATCHED_NODE_LEFT_OVER,
        node.getFilePosition(), node);
    return node;
  }

  /**
   * Adds a rule to this rewriter. Rules are evaluated in the order in
   * which they have been added to the rewriter via this method. Rules
   * may not be removed from the rewriter.  No two rules added to the
   * rewriter may have the same {@link Rule#getName() name}.
   *
   * @param rule a rewriting rule.
   * @exception IllegalArgumentException if a rule with a duplicate name is
   *     added.
   */
  public void addRule(Rule rule) {
    // We keep 'ruleNames' as a guard against programming errors
    if (!ruleNames.add(rule.getName())) {
      throw new IllegalArgumentException(
          "Duplicate rule name: " + rule.getName());
    }
    rules.add(rule);
    rule.setRewriter(this);
  }

  /**
   * Adds a list of rules in order to this rewriter.
   *
   * @param rules list of rewriting rules
   * @throws IllegalArgumentException if a rule with a duplicate name is added.
   */
  public void addRules(Rule[] rules) {
    for (Rule r : rules) { addRule(r); }
  }

  private void logResults(
      Rule rule,
      ParseTreeNode input,
      ParseTreeNode result,
      Exception exception) {
    StringBuilder s = new StringBuilder();
    s.append("-----------------------------------------------------------------------\n");
    if (rule != null) {
      s.append("rule: ").append(rule.getName()).append("\n");
    }
    if (input != null) {
      s.append("input: (")
          .append(input.getClass().getSimpleName())
          .append(") ")
          .append(render(input))
          .append("\n");
    }
    if (result != null) {
      s.append("result: (")
          .append(result.getClass().getSimpleName())
          .append(") ")
          .append(render(result))
          .append("\n");
    }
    if (exception != null) {
      s.append("error: ")
          .append(exception.toString())
          .append("\n");
    }
    System.err.println(s.toString());
  }

  public static String render(ParseTreeNode n) {
    StringBuilder output = new StringBuilder();
    TokenConsumer renderer = new JsPrettyPrinter(output);
    n.render(new RenderContext(renderer));
    renderer.noMoreTokens();
    return output.toString();
  }

  /**
   * Expands a parse tree node according to the rules of this
   * rewriter, returning the expanded result.
   *
   * @param node a top-level parse tree node to expand.
   * @return the expanded parse tree node.
   */
  public final ParseTreeNode expand(ParseTreeNode node) {
    tainted.clear();
    forSideEffect.clear();
    node.makeImmutable();
    if (taintChecking) {
      flagTainted(node, mq);
      ParseTreeNode result = expand(node, null);
      if (!mq.hasMessageAtLevel(MessageLevel.ERROR)) {
        checkTainted(result, mq);
      }
      result.makeImmutable();
      return result;
    }
    return expand(node, null);
  }

  private void flagTainted(ParseTreeNode node, MessageQueue mq) {
    if (tainted.contains(node)) {
      mq.addMessage(
          RewriterMessageType.MULTIPLY_TAINTED, node, node.getFilePosition());
    }
    tainted.add(node);
    for (ParseTreeNode n : node.children()) {
      flagTainted(n, mq);
    }
  }

  // Returns true if check passed.
  private boolean checkTainted(ParseTreeNode node, MessageQueue mq) {
    if (tainted.contains(node)) {
      if (!SyntheticNodes.is(node)) {
        mq.addMessage(
            RewriterMessageType.UNSEEN_NODE_LEFT_OVER, node,
            node.getFilePosition());
        return false;
      }
    }
    for (ParseTreeNode n : node.children()) {
      if (!checkTainted(n, mq)) {
        return false;
      }
    }
    return true;
  }

  private <T extends ParseTreeNode> T removeTaint(T node) {
    if (taintChecking) {
      // TODO(erights): consider returning a defensive copy rather than
      // side effecting in place. If we do, we also need to revisit all
      // calls to removeTaint and noexpand().
      tainted.remove(node);
    }
    return node;
  }

  protected <T extends ParseTreeNode> T noexpand(T node) {
    return removeTaint(node);
  }

  /**
   * Returns its argument, but declares that we are avoiding passing it
   * through the expander on purpose.
   * <p>
   * We are using taint to check that all nodes emerging from this translator
   * are expanded unless stated otherwise, so {@code noexpand()}
   * removes this taint in order to state otherwise.
   */
  protected Reference noexpand(Reference node) {
    removeTaint(node.getIdentifier());
    return removeTaint(node);
  }

  protected Declaration noexpand(Declaration node) {
    if (node.getInitializer() != null) {
      mq.addMessage(
          RewriterMessageType.NOEXPAND_BINARY_DECL,
          node.getFilePosition(), node);
      return node;
    }
    removeTaint(node.getIdentifier());
    return removeTaint(node);
  }

  protected ParseTreeNodeContainer noexpandParams(ParseTreeNodeContainer node) {
    if (taintChecking) {
      for (ParseTreeNode child : node.children()) {
        noexpand((Declaration) child);
      }
      return removeTaint(node);
    }
    return node;
  }

  protected void setTaint(ParseTreeNode node) {
    tainted.add(node);
  }

  protected void clearTaint(ParseTreeNode node) {
    tainted.remove(node);
  }

  public void markForSideEffect(ParseTreeNode node) {
    forSideEffect.add(node);
  }

  public void markTreeForSideEffect(ParseTreeNode node) {
    if (node instanceof Statement) {
      node.makeImmutable();
      markForSideEffect(node);
      for (ParseTreeNode child : node.children()) {
        markTreeForSideEffect(child);
      }
    }
  }

  public boolean isForSideEffect(ParseTreeNode node) {
    return Rewriter.this.forSideEffect.contains(node);
  }
}
TOP

Related Classes of com.google.caja.parser.quasiliteral.Rewriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.