blob: f06ad3fa8987277300f124fbb59db721e2d7c58b [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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.intellij.codeInspection.bytecodeAnalysis;
import com.intellij.codeInspection.bytecodeAnalysis.asm.*;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Pair;
import com.intellij.util.indexing.DataIndexer;
import com.intellij.util.indexing.FileContent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.org.objectweb.asm.*;
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
import java.security.MessageDigest;
import java.util.*;
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis.LOG;
/**
* @author lambdamix
*/
public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileContent> {
private static final int STABLE_FLAGS = Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
public static final Final<Key, Value> FINAL_TOP = new Final<Key, Value>(Value.Top);
public static final Final<Key, Value> FINAL_BOT = new Final<Key, Value>(Value.Bot);
public static final Final<Key, Value> FINAL_NOT_NULL = new Final<Key, Value>(Value.NotNull);
public static final Final<Key, Value> FINAL_NULL = new Final<Key, Value>(Value.Null);
private static final List<Equation<Key, Value>> EMPTY_EQUATIONS = Collections.EMPTY_LIST;
@NotNull
@Override
public Map<Bytes, HEquations> map(@NotNull FileContent inputData) {
HashMap<Bytes, HEquations> map = new HashMap<Bytes, HEquations>();
try {
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
Map<Key, List<Equation<Key, Value>>> rawEquations = processClass(new ClassReader(inputData.getContent()), inputData.getFile().getPresentableUrl());
for (Map.Entry<Key, List<Equation<Key, Value>>> entry: rawEquations.entrySet()) {
Key primaryKey = entry.getKey();
Key serKey = new Key(primaryKey.method, primaryKey.direction, true);
List<Equation<Key, Value>> equations = entry.getValue();
List<DirectionResultPair> result = new ArrayList<DirectionResultPair>(equations.size());
for (Equation<Key, Value> equation : equations) {
result.add(BytecodeAnalysisConverter.convert(equation, md));
}
map.put(new Bytes(BytecodeAnalysisConverter.asmKey(serKey, md).key), new HEquations(result, primaryKey.stable));
}
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Throwable e) {
// incorrect bytecode may result in Runtime exceptions during analysis
// so here we suppose that exception is due to incorrect bytecode
LOG.debug("Unexpected Error during indexing of bytecode", e);
}
//return map;
return map;
}
public static Map<Key, List<Equation<Key, Value>>> processClass(final ClassReader classReader, final String presentableUrl) {
final Map<Key, List<Equation<Key, Value>>> equations = new HashMap<Key, List<Equation<Key, Value>>>();
classReader.accept(new ClassVisitor(Opcodes.ASM5) {
private String className;
private boolean stableClass;
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
className = name;
stableClass = (access & Opcodes.ACC_FINAL) != 0;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
final MethodNode node = new MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions);
return new MethodVisitor(Opcodes.ASM5, node) {
private boolean jsr;
@Override
public void visitJumpInsn(int opcode, Label label) {
if (opcode == Opcodes.JSR) {
jsr = true;
}
super.visitJumpInsn(opcode, label);
}
@Override
public void visitEnd() {
super.visitEnd();
Pair<Key, List<Equation<Key, Value>>> methodEquations = processMethod(node, jsr);
equations.put(methodEquations.first, methodEquations.second);
}
};
}
private Pair<Key, List<Equation<Key, Value>>> processMethod(final MethodNode methodNode, boolean jsr) {
ProgressManager.checkCanceled();
final Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc);
final Type resultType = Type.getReturnType(methodNode.desc);
final boolean isReferenceResult = ASMUtils.isReferenceType(resultType);
final boolean isBooleanResult = ASMUtils.isBooleanType(resultType);
final boolean isInterestingResult = isReferenceResult || isBooleanResult;
final Method method = new Method(className, methodNode.name, methodNode.desc);
final boolean stable = stableClass || (methodNode.access & STABLE_FLAGS) != 0 || "<init>".equals(methodNode.name);
Key primaryKey = new Key(method, Out, stable);
if (argumentTypes.length == 0 && !isInterestingResult) {
return Pair.create(primaryKey, EMPTY_EQUATIONS);
}
try {
final ControlFlowGraph graph = ControlFlowGraph.build(className, methodNode, jsr);
if (graph.transitions.length > 0) {
final DFSTree dfs = DFSTree.build(graph.transitions, graph.edgeCount);
boolean branching = !dfs.back.isEmpty();
if (!branching) {
for (int[] transition : graph.transitions) {
if (transition != null && transition.length > 1) {
branching = true;
break;
}
}
}
if (branching) {
RichControlFlow richControlFlow = new RichControlFlow(graph, dfs);
if (richControlFlow.reducible()) {
return Pair.create(primaryKey,
processBranchingMethod(method, methodNode, richControlFlow, argumentTypes, isReferenceResult, isInterestingResult, stable, jsr));
}
LOG.debug(method + ": CFG is not reducible");
}
// simple
else {
return Pair.create(primaryKey,
processNonBranchingMethod(method, argumentTypes, graph, isReferenceResult, isBooleanResult, stable));
}
}
return Pair.create(primaryKey, topEquations(method, argumentTypes, isReferenceResult, isInterestingResult, stable));
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Throwable e) {
// incorrect bytecode may result in Runtime exceptions during analysis
// so here we suppose that exception is due to incorrect bytecode
LOG.debug("Unexpected Error during processing of " + method + " in " + presentableUrl, e);
return Pair.create(primaryKey, topEquations(method, argumentTypes, isReferenceResult, isInterestingResult, stable));
}
}
private List<Equation<Key, Value>> processBranchingMethod(final Method method,
final MethodNode methodNode,
final RichControlFlow richControlFlow,
Type[] argumentTypes,
boolean isReferenceResult,
boolean isInterestingResult,
final boolean stable,
boolean jsr) throws AnalyzerException {
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 2);
boolean maybeLeakingParameter = isInterestingResult;
for (Type argType : argumentTypes) {
if (ASMUtils.isReferenceType(argType) || (isReferenceResult && ASMUtils.isBooleanType(argType))) {
maybeLeakingParameter = true;
break;
}
}
final LeakingParameters leakingParametersAndFrames =
maybeLeakingParameter ? leakingParametersAndFrames(method, methodNode, argumentTypes, jsr) : null;
boolean[] leakingParameters =
leakingParametersAndFrames != null ? leakingParametersAndFrames.parameters : null;
boolean[] leakingNullableParameters =
leakingParametersAndFrames != null ? leakingParametersAndFrames.nullableParameters : null;
final boolean[] origins =
isInterestingResult ?
OriginsAnalysis.resultOrigins(leakingParametersAndFrames.frames, methodNode.instructions, richControlFlow.controlFlow) :
null;
Equation<Key, Value> outEquation =
isInterestingResult ?
new InOutAnalysis(richControlFlow, Out, origins, stable).analyze() :
null;
if (isReferenceResult) {
result.add(outEquation);
result.add(new Equation<Key, Value>(new Key(method, NullableOut, stable), NullableMethodAnalysis.analyze(methodNode, origins, jsr)));
}
for (int i = 0; i < argumentTypes.length; i++) {
boolean isReferenceArg = ASMUtils.isReferenceType(argumentTypes[i]);
boolean notNullParam = false;
if (isReferenceArg) {
boolean possibleNPE = false;
if (leakingParameters[i]) {
NonNullInAnalysis notNullInAnalysis = new NonNullInAnalysis(richControlFlow, new In(i, In.NOT_NULL), stable);
Equation<Key, Value> notNullParamEquation = notNullInAnalysis.analyze();
possibleNPE = notNullInAnalysis.possibleNPE;
notNullParam = notNullParamEquation.rhs.equals(FINAL_NOT_NULL);
result.add(notNullParamEquation);
}
else {
// parameter is not leaking, so it is definitely NOT @NotNull
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NOT_NULL), stable), FINAL_TOP));
}
if (leakingNullableParameters[i]) {
if (notNullParam || possibleNPE) {
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_TOP));
}
else {
result.add(new NullableInAnalysis(richControlFlow, new In(i, In.NULLABLE), stable).analyze());
}
}
else {
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_NULL));
}
}
if (isReferenceArg && isInterestingResult) {
if (leakingParameters[i]) {
if (!notNullParam) {
// may be null on some branch, running "null->..." analysis
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.Null), origins, stable).analyze());
}
else {
// @NotNull, so "null->fail"
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), FINAL_BOT));
}
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.NotNull), origins, stable).analyze());
}
else {
// parameter is not leaking, so a contract is the same as for the whole method
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), outEquation.rhs));
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.NotNull), stable), outEquation.rhs));
}
}
if (ASMUtils.isBooleanType(argumentTypes[i]) && isInterestingResult) {
if (leakingParameters[i]) {
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.False), origins, stable).analyze());
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.True), origins, stable).analyze());
}
else {
// parameter is not leaking, so a contract is the same as for the whole method
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.False), stable), outEquation.rhs));
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.True), stable), outEquation.rhs));
}
}
}
return result;
}
private List<Equation<Key, Value>> processNonBranchingMethod(Method method,
Type[] argumentTypes,
ControlFlowGraph graph,
boolean isReferenceResult,
boolean isBooleanResult,
boolean stable) throws AnalyzerException {
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 2);
CombinedAnalysis analyzer = new CombinedAnalysis(method, graph);
analyzer.analyze();
if (isReferenceResult) {
result.add(analyzer.outContractEquation(stable));
result.add(analyzer.nullableResultEquation(stable));
}
for (int i = 0; i < argumentTypes.length; i++) {
Type argType = argumentTypes[i];
boolean isRefArg = ASMUtils.isReferenceType(argType);
if (isRefArg) {
result.add(analyzer.notNullParamEquation(i, stable));
result.add(analyzer.nullableParamEquation(i, stable));
}
if (isRefArg && (isReferenceResult || isBooleanResult)) {
result.add(analyzer.contractEquation(i, Value.Null, stable));
result.add(analyzer.contractEquation(i, Value.NotNull, stable));
}
if (ASMUtils.isBooleanType(argType) && (isReferenceResult || isBooleanResult)) {
result.add(analyzer.contractEquation(i, Value.True, stable));
result.add(analyzer.contractEquation(i, Value.False, stable));
}
}
return result;
}
private List<Equation<Key, Value>> topEquations(Method method,
Type[] argumentTypes,
boolean isReferenceResult,
boolean isInterestingResult,
boolean stable) {
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 2);
if (isReferenceResult) {
result.add(new Equation<Key, Value>(new Key(method, Out, stable), FINAL_TOP));
result.add(new Equation<Key, Value>(new Key(method, NullableOut, stable), FINAL_BOT));
}
for (int i = 0; i < argumentTypes.length; i++) {
Type argType = argumentTypes[i];
boolean isReferenceArg = ASMUtils.isReferenceType(argType);
boolean isBooleanArg = ASMUtils.isBooleanType(argType);
if (isReferenceArg) {
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NOT_NULL), stable), FINAL_TOP));
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_TOP));
}
if (isReferenceArg && isInterestingResult) {
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), FINAL_TOP));
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.NotNull), stable), FINAL_TOP));
}
if (isBooleanArg && isInterestingResult) {
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.False), stable), FINAL_TOP));
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.True), stable), FINAL_TOP));
}
}
return result;
}
private LeakingParameters leakingParametersAndFrames(Method method, MethodNode methodNode, Type[] argumentTypes, boolean jsr)
throws AnalyzerException {
return argumentTypes.length < 32 ?
LeakingParameters.buildFast(method.internalClassName, methodNode, jsr) :
LeakingParameters.build(method.internalClassName, methodNode, jsr);
}
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return equations;
}
}