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

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

/*
 * Copyright 2016 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 static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.GwtIncompatible;
import com.google.javascript.jscomp.graph.DiGraph;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/** Instrument branch coverage for javascript. */
@GwtIncompatible("FileInstrumentationData")
public class BranchCoverageInstrumentationCallback extends NodeTraversal.AbstractPostOrderCallback {
  private final AbstractCompiler compiler;
  private final Map instrumentationData;

  private static final String BRANCH_ARRAY_NAME_PREFIX = "JSCompiler_lcov_branch_data_";

  /** Returns a string that can be used for the branch coverage data. */
  private static String createArrayName(NodeTraversal traversal) {
    return BRANCH_ARRAY_NAME_PREFIX
        + CoverageUtil.createIdentifierFromText(traversal.getSourceName());
  }

  public BranchCoverageInstrumentationCallback(
      AbstractCompiler compiler, Map instrumentationData) {
    this.compiler = compiler;
    this.instrumentationData = instrumentationData;
  }

  @Override
  public void visit(NodeTraversal traversal, Node node, Node parent) {
    String fileName = traversal.getSourceName();

    // If origin of node is not from sourceFile, do not instrument. This typically occurs when
    // polyfill code is injected into the sourceFile AST and this check avoids instrumenting it. We
    // avoid instrumentation as this callback does not distinguish between sourceFile code and
    // injected code and can result in an error.
    if (!Objects.equals(fileName, node.getSourceFileName())) {
      return;
    }

    if (node.isScript()) {
      if (instrumentationData.get(fileName) != null) {
        Node toAddTo =
            node.hasChildren() && node.getFirstChild().isModuleBody() ? node.getFirstChild() : node;
        // Add instrumentation code
        toAddTo.addChildrenToFront(newHeaderNode(traversal, toAddTo).removeChildren());
        compiler.reportChangeToEnclosingScope(node);
        instrumentBranchCoverage(traversal, instrumentationData.get(fileName));
      }
    }

    if (node.isIf()) {
      ControlFlowGraph cfg = traversal.getControlFlowGraph();
      boolean hasDefaultBlock = false;
      for (DiGraph.DiGraphEdge outEdge : cfg.getOutEdges(node)) {
        if (outEdge.getValue() == ControlFlowGraph.Branch.ON_FALSE) {
          Node destination = outEdge.getDestination().getValue();
          if (destination != null
              && destination.isBlock()
              && destination.getParent() != null
              && destination.getParent().isIf()) {
            hasDefaultBlock = true;
          }
          break;
        }
      }
      if (!hasDefaultBlock) {
        addDefaultBlock(node);
      }
      if (!instrumentationData.containsKey(fileName)) {
        instrumentationData.put(
            fileName, new FileInstrumentationData(fileName, createArrayName(traversal)));
      }
      processBranchInfo(node, instrumentationData.get(fileName), getChildrenBlocks(node));
    } else if (NodeUtil.isLoopStructure(node)) {
      List blocks = getChildrenBlocks(node);
      ControlFlowGraph cfg = traversal.getControlFlowGraph();
      for (DiGraph.DiGraphEdge outEdge : cfg.getOutEdges(node)) {
        if (outEdge.getValue() == ControlFlowGraph.Branch.ON_FALSE) {
          Node destination = outEdge.getDestination().getValue();
          if (destination != null && destination.isBlock()) {
            blocks.add(destination);
          } else {
            Node exitBlock = IR.block();
            if (destination != null && destination.getParent().isBlock()) {
              // When the destination of an outEdge of the CFG is not null and the source node's
              // parent is a block. If parent is not a block it may result in an illegal state
              // exception
              destination.getParent().addChildBefore(exitBlock, destination);
            } else {
              // When the destination of an outEdge of the CFG is null then we need to add an empty
              // block directly after the loop structure that we can later instrument
              outEdge
                  .getSource()
                  .getValue()
                  .getParent()
                  .addChildAfter(exitBlock, outEdge.getSource().getValue());
            }
            blocks.add(exitBlock);
          }
        }
      }
      if (!instrumentationData.containsKey(fileName)) {
        instrumentationData.put(
            fileName, new FileInstrumentationData(fileName, createArrayName(traversal)));
      }
      processBranchInfo(node, instrumentationData.get(fileName), blocks);
    }
  }

  private List getChildrenBlocks(Node node) {
    List blocks = new ArrayList<>();
    for (Node child : node.children()) {
      if (child.isBlock()) {
        blocks.add(child);
      }
    }
    return blocks;
  }

  /**
   * Add instrumentation code for branch coverage. For each block that correspond to a branch,
   * insert an assignment of the branch coverage data to the front of the block.
   */
  private void instrumentBranchCoverage(NodeTraversal traversal, FileInstrumentationData data) {
    int maxLine = data.maxBranchPresentLine();
    int branchCoverageOffset = 0;
    for (int lineIdx = 1; lineIdx <= maxLine; ++lineIdx) {
      Integer numBranches = data.getNumBranches(lineIdx);
      if (numBranches != null) {
        for (int branchIdx = 1; branchIdx <= numBranches; ++branchIdx) {
          Node block = data.getBranchNode(lineIdx, branchIdx);
          block.addChildToFront(
              newBranchInstrumentationNode(traversal, block, branchCoverageOffset + branchIdx - 1));
          compiler.reportChangeToEnclosingScope(block);
        }
        branchCoverageOffset += numBranches;
      }
    }
  }

  /**
   * Create an assignment to the branch coverage data for the given index into the array.
   *
   * @return the newly constructed assignment node.
   */
  private Node newBranchInstrumentationNode(NodeTraversal traversal, Node node, int idx) {
    String arrayName = createArrayName(traversal);

    // Create instrumentation Node
    Node getElemNode = IR.getelem(IR.name(arrayName), IR.number(idx)); // Make line number 0-based
    Node exprNode = IR.exprResult(IR.assign(getElemNode, IR.trueNode()));

    // Note line as instrumented
    String fileName = traversal.getSourceName();
    if (!instrumentationData.containsKey(fileName)) {
      instrumentationData.put(fileName, new FileInstrumentationData(fileName, arrayName));
    }
    return exprNode.useSourceInfoIfMissingFromForTree(node);
  }

  /** Add branch instrumentation information for each block. */
  private void processBranchInfo(Node branchNode, FileInstrumentationData data, List blocks) {
    int lineNumber = branchNode.getLineno();
    data.setBranchPresent(lineNumber);

    // Instrument for each block
    int numBranches = 0;
    for (Node child : blocks) {
      data.putBranchNode(lineNumber, numBranches + 1, child);
      numBranches++;
    }
    data.addBranches(lineNumber, numBranches);
  }

  /** Add a default block for conditional statements, e.g., If, Switch. */
  private Node addDefaultBlock(Node node) {
    Node defaultBlock = IR.block();
    node.addChildToBack(defaultBlock);
    return defaultBlock.useSourceInfoIfMissingFromForTree(node);
  }

  private Node newHeaderNode(NodeTraversal traversal, Node srcref) {
    String fileName = traversal.getSourceName();
    FileInstrumentationData data = instrumentationData.get(fileName);
    checkNotNull(data);

    // var JSCompiler_lcov_branch_data_xx = [];
    // __jscov['branchesTaken'].push(JSCompiler_lcov_branch_data_xx);
    String objName = CoverageInstrumentationPass.JS_INSTRUMENTATION_OBJECT_NAME;
    List nodes = new ArrayList<>();
    nodes.add(newArrayDeclarationNode(traversal));
    nodes.add(
        IR.exprResult(
            IR.call(
                IR.getprop(IR.getelem(IR.name(objName), IR.string("branchesTaken")), "push"),
                IR.name(createArrayName(traversal)))));
    // __jscov['branchPresent'].push(hex-data);
    nodes.add(
        IR.exprResult(
            IR.call(
                IR.getprop(IR.getelem(IR.name(objName), IR.string("branchPresent")), "push"),
                IR.string(data.getBranchPresentAsHexString()))));
    nodes.add(newBranchesInLineNode("JSCompiler_lcov_branchesInLine", data));
    // __jscov['branchesInLine'].push(JSCompiler_lcov_branchesInLine);
    nodes.add(
        IR.exprResult(
            IR.call(
                IR.getprop(IR.getelem(IR.name(objName), IR.string("branchesInLine")), "push"),
                IR.name("JSCompiler_lcov_branchesInLine"))));
    // __jscov['fileNames'].push(filename);
    nodes.add(
        IR.exprResult(
            IR.call(
                IR.getprop(IR.getelem(IR.name(objName), IR.string("fileNames")), "push"),
                IR.string(fileName))));
    return IR.block(nodes).useSourceInfoIfMissingFromForTree(srcref);
  }

  private Node newArrayDeclarationNode(NodeTraversal traversal) {
    return IR.var(IR.name(createArrayName(traversal)), IR.arraylit());
  }

  private Node newBranchesInLineNode(String name, FileInstrumentationData data) {
    List assignments = new ArrayList<>();
    // var JSCompiler_lcov_branchesInLine = [];
    assignments.add(IR.var(IR.name(name), IR.arraylit()));
    int lineWithBranch = 0;
    for (int lineIdx = 1; lineIdx <= data.maxBranchPresentLine(); ++lineIdx) {
      Integer numBranches = data.getNumBranches(lineIdx);
      if (numBranches != null && numBranches > 0) {
        // JSCompiler_lcov_branchesInLine[] = 2;
        Node assignment =
            IR.exprResult(
                IR.assign(
                    IR.getelem(IR.name(name), IR.number(lineWithBranch++)),
                    IR.number(numBranches)));
        assignments.add(assignment.useSourceInfoIfMissingFromForTree(assignment));
      }
    }
    return IR.block(assignments);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy