blob: a24b1d2815a0a74c8ead229042dd86e9c250678a [file] [log] [blame]
/*
* Copyright 2012 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.gwt.dev.js;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JClassLiteral;
import com.google.gwt.dev.jjs.ast.JConstructor;
import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JValueLiteral;
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsExpression;
import com.google.gwt.thirdparty.guava.common.base.Charsets;
import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.thirdparty.guava.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.util.Set;
/**
* Build up a collection of all instrumentable lines, useful for generating
* coverage reports.
*/
public class BaselineCoverageGatherer {
public static Multimap<String, Integer> exec(JProgram jProgram) {
return new BaselineCoverageGatherer(jProgram, getCoveredSourceFiles()).execImpl();
}
private static Set<String> getCoveredSourceFiles() {
String filename = System.getProperty(CoverageInstrumentor.GWT_COVERAGE_SYSTEM_PROPERTY);
if (filename.indexOf(',') != -1) {
return ImmutableSet.copyOf(filename.split(","));
}
File instrumentationFile = new File(filename);
try {
return Sets.newHashSet(Files.readLines(instrumentationFile, Charsets.UTF_8));
} catch (IOException e) {
throw new InternalCompilerException("Could not open " + filename, e);
}
}
private Multimap<String, Integer> instrumentableLines = HashMultimap.create();
private Set<String> instrumentedFiles;
private JProgram jProgram;
private BaselineCoverageGatherer(JProgram jProgram, Set<String> instrumentedFiles) {
this.jProgram = jProgram;
this.instrumentedFiles = instrumentedFiles;
}
private void cover(SourceInfo info) {
if (instrumentedFiles.contains(info.getFileName())) {
instrumentableLines.put(info.getFileName(), info.getStartLine());
}
}
private Multimap<String, Integer> execImpl() {
/**
* Figure out which lines are executable. This is mostly straightforward
* except that we have to avoid some synthetic nodes introduced earlier,
* otherwise e.g. class declarations will be visited.
*/
new JVisitor() {
@Override public void endVisit(JMethodCall x, Context ctx) {
// this is a bit of a hack. The compiler inserts no-arg super calls, but
// there isn't really a way to detect that they're synthetic, and the
// strategy below of comparing source info with that of the enclosing type
// doesn't work because the enclosing type is set to be that of the superclass.
if (x.getTarget().isSynthetic()
|| (x.getTarget().isConstructor()
&& ((JConstructor) x.getTarget()).isDefaultConstructor())) {
return;
}
endVisit((JExpression) x, ctx);
}
@Override public void endVisit(JThisRef x, Context ctx) {
if (x.getSourceInfo().equals(x.getClassType().getSourceInfo())) {
return;
}
endVisit((JExpression) x, ctx);
}
@Override public void endVisit(JClassLiteral x, Context ctx) {
if (x.getSourceInfo().equals(x.getRefType().getSourceInfo())) {
return;
}
endVisit((JExpression) x, ctx);
}
@Override public void endVisit(JExpression x, Context ctx) {
cover(x.getSourceInfo());
}
@Override public void endVisit(JsniMethodBody x, Context ctx) {
new CoverageVisitor(instrumentedFiles) {
@Override public void endVisit(JsExpression x, JsContext ctx) {
cover(x.getSourceInfo());
}
}.accept(x.getFunc());
}
// don't instrument fields whose initializers are literals, because (1) CoverageVisitor
// doesn't visit literals because it can introduce syntax errors in some cases, and (2) it's
// consistent with other coverage tools, e.g. Emma.
@Override public boolean visit(JDeclarationStatement x, Context ctx) {
return !(x.getInitializer() instanceof JValueLiteral &&
x.getVariableRef().getTarget() instanceof JField);
}
// don't instrument method call arguments; we can get weird coverage results when a call is
// spread over several lines
@Override public boolean visit(JMethodCall x, Context ctx) {
return false;
}
}.accept(jProgram);
return instrumentableLines;
}
}