All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.InstrumentFunctions Maven / Gradle / Ivy

There is a newer version: 9.0.8
Show newest version
/*
 * Copyright 2008 The Closure Compiler Authors.
 *
 * 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.javascript.jscomp;

import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphNode;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.List;

/**
 * Instruments functions for when functions first get called and defined.
 *
 * This pass be used to instrument code to:
 *  1. Gather statistics from real users in the wild.
 *  2. Incorporate utilization statistics into Selenium tests
 *  3. Access utilization statistics from an app's debug UI
 *
 * By parametrizing the whole instrumentation process we expect to be
 * able to support a wide variety of use cases and minimize the cost
 * of developing new instrumentation schemes.
 *
 * TODO(user): This pass currently runs just before the variable and
 * property renaming near the end of the optimization pass.  I think
 * Mark put it there to minimize the difference between the code
 * generated with/without instrumentation; instrumentation makes
 * several optimization passes do less, for example inline functions.
 *
 * My opinion is that we want utilization/profiling information for
 * all function.  This pass should run before most passes that modify
 * the AST (exception being the localization pass, which makes
 * assumptions about the structure of the AST).  We should move the
 * pass up, list inlined functions or give clients the option to
 * instrument before or after optimization.
 *
 */
class InstrumentFunctions implements CompilerPass {

  private final AbstractCompiler compiler;
  private final FunctionNames functionNames;
  private final String appNameStr;
  private final String initCodeSource;
  private final String definedFunctionName;
  private final String reportFunctionName;
  private final String reportFunctionExitName;
  private final String appNameSetter;
  private final List declarationsToRemove;

  /**
   * Creates an instrument functions compiler pass.
   *
   * @param compiler          The JSCompiler
   * @param functionNames     Assigned function identifiers.
   * @param template          Instrumentation template; for use during error reporting only.
   * @param appNameStr        String to pass to appNameSetter.
   */
  InstrumentFunctions(AbstractCompiler compiler,
                      FunctionNames functionNames,
                      Instrumentation template,
                      String appNameStr) {
    this.compiler = compiler;
    this.functionNames = functionNames;
    this.appNameStr = appNameStr;

    StringBuilder initCodeSourceBuilder = new StringBuilder();
    for (String line : template.getInitList()) {
      initCodeSourceBuilder.append(line).append("\n");
    }
    this.initCodeSource = initCodeSourceBuilder.toString();

    this.definedFunctionName = template.getReportDefined();
    this.reportFunctionName = template.getReportCall();
    this.reportFunctionExitName = template.getReportExit();
    this.appNameSetter = template.getAppNameSetter();

    this.declarationsToRemove = ImmutableList.copyOf(
        template.getDeclarationToRemoveList());
  }

  @Override
  public void process(Node externs, Node root) {
    Node initCode = null;
    if (!initCodeSource.isEmpty()) {
      Node initCodeRoot = compiler.parseSyntheticCode(
          "template:init", initCodeSource);
      if (initCodeRoot != null && initCodeRoot.getFirstChild() != null) {
        NodeUtil.markNewScopesChanged(initCodeRoot, compiler);
        initCode = initCodeRoot.removeChildren();
      } else {
        return;  // parse failure
      }
    }

    NodeTraversal.traverseEs6(compiler, root,
                           new RemoveCallback(declarationsToRemove));
    NodeTraversal.traverseEs6(compiler, root, new InstrumentCallback());

    if (!appNameSetter.isEmpty()) {
      Node call = IR.call(
          IR.name(appNameSetter),
          IR.string(appNameStr));
      call.putBooleanProp(Node.FREE_CALL, true);
      Node expr = IR.exprResult(call);

      Node addingRoot = compiler.getNodeForCodeInsertion(null);
      addingRoot.addChildToFront(expr.useSourceInfoIfMissingFromForTree(addingRoot));
      compiler.reportChangeToEnclosingScope(addingRoot);
    }

    if (initCode != null) {
      Node addingRoot = compiler.getNodeForCodeInsertion(null);
      addingRoot.addChildrenToFront(initCode);
      compiler.reportChangeToEnclosingScope(addingRoot);
    }
  }

  /**
   * The application must refer to these variables to output them so the
   * application must also declare these variables for the first
   * {@link VarCheck} pass. These declarations must be removed before the
   * second {@link VarCheck} pass. Otherwise, the second pass would warn about
   * duplicate declarations.
   */
  private static class RemoveCallback extends AbstractPostOrderCallback {
    private final List removable;
    RemoveCallback(List removable) {
      this.removable = removable;
    }

    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (NodeUtil.isNameDeclaration(parent) && n.isName() && removable.contains(n.getString())) {
        parent.removeChild(n);
        if (!parent.hasChildren()) {
          parent.detach();
        }
      }
    }
  }

  /**
   * Traverse a function's body by instrument return sites by
   * inserting calls to {@code reportFunctionExitName}.  If the
   * function is missing an explicit return statement in some control
   * path, this pass inserts a call to {@code reportFunctionExitName}
   * as the last statement in the function's body.
   *
   * Example:
   * Input:
   * function f() {
   *   if (pred) {
   *     return a;
   *   }
   * }
   *
   * Template:
   * reportFunctionExitName: "onExitFn"
   *
   * Output:
   * function f() {
   *   if (pred) {
   *     return onExitFn(0, a, "f");
   *   }
   *   onExitFn(0, undefined, "f");
   * }
   *
   **/
  private class InstrumentReturns implements NodeTraversal.Callback {
    private final int functionId;
    private final String functionName;

    /**
     * @param functionId Function identifier computed by FunctionNames;
     *     used as first argument to {@code reportFunctionExitName}
     *     {@code reportFunctionExitName} must be a 3 argument function that
     *     returns it's second argument.
     * @param functionName Function name.
     */
    InstrumentReturns(int functionId, String functionName) {
      this.functionId = functionId;
      this.functionName = functionName;
    }

    /**
     * @param function function with id == this.functionId
     */
    void process(Node function) {
      Node body = function.getLastChild();
      NodeTraversal.traverseEs6(compiler, body, this);

      if (!allPathsReturn(function)) {
        Node call = newReportFunctionExitNode(function, null);
        Node expr = IR.exprResult(call).useSourceInfoIfMissingFromForTree(function);
        body.addChildToBack(expr);
        compiler.reportChangeToEnclosingScope(body);
      }
    }

    @Override
    public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
      return !n.isFunction();
    }

    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (!n.isReturn()) {
        return;
      }

      Node returnRhs = n.removeFirstChild();
      Node call = newReportFunctionExitNode(n, returnRhs);
      n.addChildToFront(call);
      t.reportCodeChange();
    }

    private Node newReportFunctionExitNode(Node infoNode, Node returnRhs) {
      Node call = IR.call(
          IR.name(reportFunctionExitName),
          IR.number(functionId),
          (returnRhs != null) ? returnRhs : IR.name("undefined"),
          IR.string(functionName));
      call.putBooleanProp(Node.FREE_CALL, true);
      call.useSourceInfoFromForTree(infoNode);
      return call;
    }

    /**
     * @return true if all paths from block must exit with an explicit return.
     */
    private boolean allPathsReturn(Node function) {
      // Computes the control flow graph.
      ControlFlowAnalysis cfa = new ControlFlowAnalysis(
          compiler, false, false);
      cfa.process(null, function);
      ControlFlowGraph cfg = cfa.getCfg();

      Node returnPathsParent = cfg.getImplicitReturn().getValue();
      for (DiGraphNode pred :
        cfg.getDirectedPredNodes(returnPathsParent)) {
        Node n = pred.getValue();
        if (!n.isReturn()) {
          return false;
        }
      }
      return true;
    }
  }

  private class InstrumentCallback extends AbstractPostOrderCallback {
    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (!n.isFunction()) {
        return;
      }

      int id = functionNames.getFunctionId(n);
      if (id < 0) {
        // Function node was added during compilation; don't instrument.
        return;
      }

      String name = functionNames.getFunctionName(n);

      if (!reportFunctionName.isEmpty()) {
        Node body = n.getLastChild();
        Node call = IR.call(
            IR.name(reportFunctionName),
            IR.number(id),
            IR.string(name),
            IR.name("arguments"));
        call.putBooleanProp(Node.FREE_CALL, true);
        Node expr = IR.exprResult(call);
        expr.useSourceInfoFromForTree(n);
        body.addChildToFront(expr);
        t.reportCodeChange();
      }

      if (!reportFunctionExitName.isEmpty()) {
        (new InstrumentReturns(id, name)).process(n);
      }

      if (!definedFunctionName.isEmpty()) {
        Node call = IR.call(
            IR.name(definedFunctionName),
            IR.number(id),
            IR.string(name));
        call.putBooleanProp(Node.FREE_CALL, true);
        call.useSourceInfoFromForTree(n);
        Node expr = NodeUtil.newExpr(call);

        Node addingRoot = null;
        if (NodeUtil.isFunctionDeclaration(n)) {
          JSModule module = t.getModule();
          addingRoot = compiler.getNodeForCodeInsertion(module);
          addingRoot.addChildToFront(expr);
        } else {
          Node beforeChild = n;
          for (Node ancestor : n.getAncestors()) {
            Token type = ancestor.getToken();
            if (type == Token.BLOCK || type == Token.SCRIPT) {
              addingRoot = ancestor;
              break;
            }
            beforeChild = ancestor;
          }
          addingRoot.addChildBefore(expr, beforeChild);
        }
        compiler.reportChangeToEnclosingScope(addingRoot);
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy