com.google.javascript.jscomp.BranchCoverageInstrumentationCallback Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.liferay.frontend.js.minifier
Show all versions of com.liferay.frontend.js.minifier
Liferay Frontend JS Minifier
/*
* 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;
/** 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 (node.isScript()) {
if (instrumentationData.get(fileName) != null) {
// Add instrumentation code
node.addChildrenToFront(newHeaderNode(traversal, node).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.isNormalBlock()
&& 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.isNormalBlock()) {
blocks.add(destination);
} else {
Node exitBlock = IR.block();
destination.getParent().addChildBefore(exitBlock, destination);
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.isNormalBlock()) {
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(
NodeUtil.newQName(compiler, objName + ".branchesTaken.push"),
IR.name(createArrayName(traversal)))));
// __jscov.branchPresent.push(hex-data);
nodes.add(
IR.exprResult(
IR.call(
NodeUtil.newQName(compiler, objName + ".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(
NodeUtil.newQName(compiler, objName + ".branchesInLine.push"),
IR.name("JSCompiler_lcov_branchesInLine"))));
// __jscov.fileNames.push(filename);
nodes.add(
IR.exprResult(
IR.call(
NodeUtil.newQName(compiler, objName + ".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);
}
}