/*
* Copyright 2014 Google Inc. All Rights Reserved.
*
* 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.errorprone.dataflow;
import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import javax.annotation.Nullable;
import javax.annotation.processing.ProcessingEnvironment;
import org.checkerframework.dataflow.analysis.AbstractValue;
import org.checkerframework.dataflow.analysis.Analysis;
import org.checkerframework.dataflow.analysis.Store;
import org.checkerframework.dataflow.analysis.TransferFunction;
import org.checkerframework.dataflow.cfg.CFGBuilder;
import org.checkerframework.dataflow.cfg.ControlFlowGraph;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
/**
* Provides a wrapper around {@link org.checkerframework.dataflow.analysis.Analysis}.
*
* @author konne@google.com (Konstantin Weitz)
*/
public final class DataFlow {
/**
* A pair of Analysis and ControlFlowGraph.
*/
public static interface Result<A extends AbstractValue<A>, S extends Store<S>,
T extends TransferFunction<A, S>> {
Analysis<A, S, T> getAnalysis();
ControlFlowGraph getControlFlowGraph();
}
/*
* We cache both the control flow graph and the analyses that are run on it.
* We tuned performance to the following assumptions (which are currently true for error-prone):
*
* <ul>
* <li> all dataflow analyses for a method are finished before another method is analyzed
* <li> multiple dataflow analyses for the same method are executed in arbitrary order
* </ul>
*
* TODO(user): Write a test that checks these assumptions
*/
private static final LoadingCache<AnalysisParams, Analysis<?, ?, ?>> analysisCache =
CacheBuilder.newBuilder()
.build(
new CacheLoader<AnalysisParams, Analysis<?, ?, ?>>() {
@Override
public Analysis<?, ?, ?> load(AnalysisParams key) {
final ProcessingEnvironment env = key.environment();
final ControlFlowGraph cfg = key.cfg();
final TransferFunction<?, ?> transfer = key.transferFunction();
@SuppressWarnings({"unchecked", "rawtypes"})
final Analysis<?, ?, ?> analysis = new Analysis(env, transfer);
analysis.performAnalysis(cfg);
return analysis;
}
});
private static final LoadingCache<CfgParams, ControlFlowGraph> cfgCache =
CacheBuilder.newBuilder()
.maximumSize(1)
.build(
new CacheLoader<CfgParams, ControlFlowGraph>() {
@Override
public ControlFlowGraph load(CfgParams key) {
final TreePath methodPath = key.methodPath();
final UnderlyingAST ast;
if (methodPath.getLeaf() instanceof LambdaExpressionTree) {
ast = new UnderlyingAST.CFGLambda((LambdaExpressionTree) methodPath.getLeaf());
} else { // must be a method per findEnclosingMethodOrLambda
MethodTree method = (MethodTree) methodPath.getLeaf();
ast = new UnderlyingAST.CFGMethod(method, /*classTree*/ null);
}
final ProcessingEnvironment env = key.environment();
analysisCache.invalidateAll();
CompilationUnitTree root = methodPath.getCompilationUnit();
// TODO(user), replace with faster build(bodyPath, env, ast, false, false);
return CFGBuilder.build(root, env, ast, false, false);
}
});
// TODO(user), remove once we merge jdk8 specific's with core
private static <T> TreePath findEnclosingMethodOrLambda(TreePath path) {
while (path != null) {
if (path.getLeaf() instanceof MethodTree || path.getLeaf() instanceof LambdaExpressionTree) {
return path;
}
path = path.getParentPath();
}
return null;
}
/**
* Run the {@code transfer} dataflow analysis over the method or lambda which is the leaf of the
* {@code methodPath}.
*
* <p>For caching, we make the following assumptions:
* - if two paths to methods are {@code equal}, their control flow graph is the same.
* - if two transfer functions are {@code equal}, and are run over the same control flow graph,
* the analysis result is the same.
* - for all contexts, the analysis result is the same.
*/
private static <A extends AbstractValue<A>, S extends Store<S>, T extends TransferFunction<A, S>>
Result<A, S, T> methodDataflow(TreePath methodPath, Context context, T transfer) {
final ProcessingEnvironment env = JavacProcessingEnvironment.instance(context);
final ControlFlowGraph cfg = cfgCache.getUnchecked(CfgParams.create(methodPath, env));
final AnalysisParams aparams = AnalysisParams.create(transfer, cfg, env);
@SuppressWarnings("unchecked")
final Analysis<A, S, T> analysis = (Analysis<A, S, T>) analysisCache.getUnchecked(aparams);
return new Result<A, S, T>() {
@Override
public Analysis<A, S, T> getAnalysis() {
return analysis;
}
@Override
public ControlFlowGraph getControlFlowGraph() {
return cfg;
}
};
}
/**
* Run the {@code transfer} dataflow analysis to compute the abstract value of the expression
* which is the leaf of {@code exprPath}.
*
* @return dataflow result for the given expression or {@code null} if the expression is not
* part of a method or lambda
*/
@Nullable
public static <A extends AbstractValue<A>, S extends Store<S>,
T extends TransferFunction<A, S>> A
expressionDataflow(TreePath exprPath, Context context, T transfer) {
final Tree leaf = exprPath.getLeaf();
Preconditions.checkArgument(
leaf instanceof ExpressionTree,
"Leaf of exprPath must be of type ExpressionTree, but was %s",
leaf.getClass().getName());
final ExpressionTree expr = (ExpressionTree) leaf;
final TreePath enclosingMethodPath = findEnclosingMethodOrLambda(exprPath);
if (enclosingMethodPath == null) {
// TODO(user) this can happen in field initialization.
// Currently not supported because it only happens in ~2% of cases.
return null;
}
final Tree method = enclosingMethodPath.getLeaf();
if (method instanceof MethodTree && ((MethodTree) method).getBody() == null) {
// expressions can occur in abstract methods, for example {@code Map.Entry} in:
//
// abstract Set<Map.Entry<K, V>> entries();
return null;
}
return methodDataflow(enclosingMethodPath, context, transfer).getAnalysis().getValue(expr);
}
@AutoValue
abstract static class CfgParams {
abstract TreePath methodPath();
// Should not be used for hashCode or equals
private ProcessingEnvironment environment;
private static CfgParams create(TreePath methodPath, ProcessingEnvironment environment) {
CfgParams cp = new AutoValue_DataFlow_CfgParams(methodPath);
cp.environment = environment;
return cp;
}
ProcessingEnvironment environment() {
return environment;
}
}
@AutoValue
abstract static class AnalysisParams {
abstract TransferFunction<?, ?> transferFunction();
abstract ControlFlowGraph cfg();
// Should not be used for hashCode or equals
private ProcessingEnvironment environment;
private static AnalysisParams create(
TransferFunction<?, ?> transferFunction,
ControlFlowGraph cfg,
ProcessingEnvironment environment) {
AnalysisParams ap = new AutoValue_DataFlow_AnalysisParams(transferFunction, cfg);
ap.environment = environment;
return ap;
}
ProcessingEnvironment environment() {
return environment;
}
}
}