![JAR search and dependency download from the Maven repository](/logo.png)
com.intellij.codeInspection.bytecodeAnalysis.ClassDataIndexer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-analysis-impl Show documentation
Show all versions of java-analysis-impl Show documentation
A packaging of the IntelliJ Community Edition java-analysis-impl library.
This is release number 1 of trunk branch 142.
The newest version!
/*
* 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 {
private static final int STABLE_FLAGS = Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
public static final Final FINAL_TOP = new Final(Value.Top);
public static final Final FINAL_BOT = new Final(Value.Bot);
public static final Final FINAL_NOT_NULL = new Final(Value.NotNull);
public static final Final FINAL_NULL = new Final(Value.Null);
@NotNull
@Override
public Map map(@NotNull FileContent inputData) {
HashMap map = new HashMap();
try {
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
Map>> allEquations = processClass(new ClassReader(inputData.getContent()), inputData.getFile().getPresentableUrl());
for (Map.Entry>> entry: allEquations.entrySet()) {
Key methodKey = entry.getKey();
// method equations with raw (not-compressed keys)
List> rawMethodEquations = entry.getValue();
//
List compressedMethodEquations =
new ArrayList(rawMethodEquations.size());
for (Equation equation : rawMethodEquations) {
compressedMethodEquations.add(BytecodeAnalysisConverter.convert(equation, md));
}
map.put(new Bytes(BytecodeAnalysisConverter.asmKey(methodKey, md).key), new HEquations(compressedMethodEquations, methodKey.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;
}
public static Map>> processClass(final ClassReader classReader, final String presentableUrl) {
// It is OK to share pending states, actions and results for analyses.
// Analyses are designed in such a way that they first write to states/actions/results and then read only those portion
// of states/actions/results which were written by the current pass of the analysis.
// Since states/actions/results are quite expensive to create (32K array) for each analysis, we create them once per class analysis.
final State[] sharedPendingStates = new State[Analysis.STEPS_LIMIT];
final PendingAction[] sharedPendingActions = new PendingAction[Analysis.STEPS_LIMIT];
final PResults.PResult[] sharedResults = new PResults.PResult[Analysis.STEPS_LIMIT];
final Map>> equations = new HashMap>>();
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>> methodEquations = processMethod(node, jsr);
equations.put(methodEquations.first, methodEquations.second);
}
};
}
/**
* Facade for analysis, it invokes specialized analyses for branching/non-branching methods.
*
* @param methodNode asm node for method
* @param jsr whether a method has jsr instruction
* @return pair of (primaryKey, equations)
*/
private Pair>> 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 || "".equals(methodNode.name);
Key primaryKey = new Key(method, Out, stable);
// 4*n: for each reference parameter: @NotNull IN, @Nullable, null -> ... contract, !null -> contract
// 3: @NotNull OUT, @Nullable OUT, purity analysis
List> equations = new ArrayList>(argumentTypes.length * 4 + 3);
equations.add(PurityAnalysis.analyze(method, methodNode, stable));
if (argumentTypes.length == 0 && !isInterestingResult) {
// no need to continue analysis
return Pair.create(primaryKey, 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()) {
processBranchingMethod(method, methodNode, richControlFlow, argumentTypes, isReferenceResult, isInterestingResult, stable, jsr, equations);
return Pair.create(primaryKey, equations);
}
LOG.debug(method + ": CFG is not reducible");
}
// simple
else {
processNonBranchingMethod(method, argumentTypes, graph, isReferenceResult, isBooleanResult, stable, equations);
return Pair.create(primaryKey, equations);
}
}
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 void processBranchingMethod(final Method method,
final MethodNode methodNode,
final RichControlFlow richControlFlow,
Type[] argumentTypes,
boolean isReferenceResult,
boolean isInterestingResult,
final boolean stable,
boolean jsr,
List> result) throws AnalyzerException {
boolean maybeLeakingParameter = isInterestingResult;
for (Type argType : argumentTypes) {
if (ASMUtils.isReferenceType(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 outEquation =
isInterestingResult ?
new InOutAnalysis(richControlFlow, Out, origins, stable, sharedPendingStates).analyze() :
null;
if (isReferenceResult) {
result.add(outEquation);
result.add(new Equation(new Key(method, NullableOut, stable), NullableMethodAnalysis.analyze(methodNode, origins, jsr)));
}
for (int i = 0; i < argumentTypes.length; i++) {
boolean notNullParam = false;
if (ASMUtils.isReferenceType(argumentTypes[i])) {
boolean possibleNPE = false;
if (leakingParameters[i]) {
NonNullInAnalysis notNullInAnalysis =
new NonNullInAnalysis(richControlFlow, new In(i, In.NOT_NULL_MASK), stable, sharedPendingActions, sharedResults);
Equation 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(new Key(method, new In(i, In.NOT_NULL_MASK), stable), FINAL_TOP));
}
if (leakingNullableParameters[i]) {
if (notNullParam || possibleNPE) {
result.add(new Equation(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_TOP));
}
else {
result.add(new NullableInAnalysis(richControlFlow, new In(i, In.NULLABLE_MASK), stable, sharedPendingStates).analyze());
}
}
else {
result.add(new Equation(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_NULL));
}
if (isInterestingResult) {
if (leakingParameters[i]) {
if (notNullParam) {
// @NotNull, so "null->fail"
result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), FINAL_BOT));
}
else {
// may be null on some branch, running "null->..." analysis
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.Null), origins, stable, sharedPendingStates).analyze());
}
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.NotNull), origins, stable, sharedPendingStates).analyze());
}
else {
// parameter is not leaking, so a contract is the same as for the whole method
result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), outEquation.rhs));
result.add(new Equation(new Key(method, new InOut(i, Value.NotNull), stable), outEquation.rhs));
}
}
}
}
}
private void processNonBranchingMethod(Method method,
Type[] argumentTypes,
ControlFlowGraph graph,
boolean isReferenceResult,
boolean isBooleanResult,
boolean stable,
List> result) throws AnalyzerException {
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];
if (ASMUtils.isReferenceType(argType)) {
result.add(analyzer.notNullParamEquation(i, stable));
result.add(analyzer.nullableParamEquation(i, stable));
if (isReferenceResult || isBooleanResult) {
result.add(analyzer.contractEquation(i, Value.Null, stable));
result.add(analyzer.contractEquation(i, Value.NotNull, stable));
}
}
}
}
private List> topEquations(Method method,
Type[] argumentTypes,
boolean isReferenceResult,
boolean isInterestingResult,
boolean stable) {
// 4 = @NotNull parameter, @Nullable parameter, null -> ..., !null -> ...
List> result = new ArrayList>(argumentTypes.length * 4 + 2);
if (isReferenceResult) {
result.add(new Equation(new Key(method, Out, stable), FINAL_TOP));
result.add(new Equation(new Key(method, NullableOut, stable), FINAL_BOT));
}
for (int i = 0; i < argumentTypes.length; i++) {
if (ASMUtils.isReferenceType(argumentTypes[i])) {
result.add(new Equation(new Key(method, new In(i, In.NOT_NULL_MASK), stable), FINAL_TOP));
result.add(new Equation(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_TOP));
if (isInterestingResult) {
result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), FINAL_TOP));
result.add(new Equation(new Key(method, new InOut(i, Value.NotNull), stable), FINAL_TOP));
}
}
}
return result;
}
@NotNull
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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy