diff --git a/java/java-analysis-impl/java-analysis-impl.iml b/java/java-analysis-impl/java-analysis-impl.iml index c3f33d642544..95c129baadfb 100644 --- a/java/java-analysis-impl/java-analysis-impl.iml +++ b/java/java-analysis-impl/java-analysis-impl.iml @@ -17,6 +17,7 @@ + diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java new file mode 100644 index 000000000000..e21296292161 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java @@ -0,0 +1,351 @@ +/* + * 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 org.jetbrains.org.objectweb.asm.Opcodes; +import org.jetbrains.org.objectweb.asm.Type; +import org.jetbrains.org.objectweb.asm.tree.MethodNode; +import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException; +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue; +import org.jetbrains.org.objectweb.asm.tree.analysis.Frame; + +import java.util.*; + +class AbstractValues { + static final class ParamValue extends BasicValue { + ParamValue(Type tp) { + super(tp); + } + } + static final BasicValue InstanceOfCheckValue = new BasicValue(Type.INT_TYPE) { + @Override + public boolean equals(Object value) { + return this == value; + } + }; + + static final BasicValue TrueValue = new BasicValue(Type.INT_TYPE) { + @Override + public boolean equals(Object value) { + return this == value; + } + }; + + static final BasicValue FalseValue = new BasicValue(Type.INT_TYPE) { + @Override + public boolean equals(Object value) { + return this == value; + } + }; + + static final BasicValue NullValue = new BasicValue(Type.getObjectType("null")) { + @Override + public boolean equals(Object value) { + return this == value; + } + }; + static final class NotNullValue extends BasicValue { + NotNullValue(Type tp) { + super(tp); + } + } + static final class CallResultValue extends BasicValue { + final Set inters; + CallResultValue(Type tp, Set inters) { + super(tp); + this.inters = inters; + } + } + + static boolean isInstance(Conf curr, Conf prev) { + if (curr.insnIndex != prev.insnIndex) { + return false; + } + Frame currFr = curr.frame; + Frame prevFr = prev.frame; + for (int i = 0; i < currFr.getLocals(); i++) { + if (!isInstance(currFr.getLocal(i), prevFr.getLocal(i))) { + return false; + } + } + for (int i = 0; i < currFr.getStackSize(); i++) { + if (!isInstance(currFr.getStack(i), prevFr.getStack(i))) { + return false; + } + } + return true; + } + + static boolean isInstance(BasicValue curr, BasicValue prev) { + if (prev instanceof ParamValue) { + return curr instanceof ParamValue; + } + if (InstanceOfCheckValue == prev) { + return InstanceOfCheckValue == curr; + } + if (TrueValue == prev) { + return TrueValue == curr; + } + if (FalseValue == prev) { + return FalseValue == curr; + } + if (NullValue == prev) { + return NullValue == curr; + } + if (prev instanceof NotNullValue) { + return curr instanceof NotNullValue; + } + if (prev instanceof CallResultValue) { + if (curr instanceof CallResultValue) { + CallResultValue prevCall = (CallResultValue) prev; + CallResultValue currCall = (CallResultValue) curr; + return prevCall.inters.equals(currCall.inters); + } + else { + return false; + } + } + return true; + } +} + +final class Conf { + final int insnIndex; + final Frame frame; + + Conf(int insnIndex, Frame frame) { + this.insnIndex = insnIndex; + this.frame = frame; + } +} + +final class State { + final int index; + final Conf conf; + final List history; + final boolean taken; + final boolean hasCompanions; + final int insnIndex; + + State(int index, Conf conf, List history, boolean taken, boolean hasCompanions) { + this.index = index; + this.conf = conf; + this.history = history; + this.taken = taken; + this.hasCompanions = hasCompanions; + insnIndex = conf.insnIndex; + } +} + +interface PendingAction {} +class ProceedState implements PendingAction { + final State state; + + ProceedState(State state) { + this.state = state; + } +} +class MakeResult implements PendingAction { + final State state; + final Res subResult; + final List indices; + + // TODO - indices array + MakeResult(State state, Res subResult, List indices) { + this.state = state; + this.subResult = subResult; + this.indices = indices; + } +} + +abstract class Analysis { + final RichControlFlow richControlFlow; + final Direction direction; + final ControlFlowGraph controlFlow; + final MethodNode methodNode; + final Method method; + final DFSTree dfsTree; + final Res myIdentity; + + final Deque> pending = new LinkedList>(); + final Map> computed = new HashMap>(); + final Map results = new HashMap(); + final Key aKey; + + Res earlyResult = null; + + abstract Res identity(); + abstract Res combineResults(Res delta, List subResults); + abstract boolean isEarlyResult(Res res); + abstract Equation mkEquation(Res result); + abstract void processState(State state) throws AnalyzerException; + + protected Analysis(RichControlFlow richControlFlow, Direction direction) { + this.richControlFlow = richControlFlow; + this.direction = direction; + controlFlow = richControlFlow.controlFlow; + methodNode = controlFlow.methodNode; + method = new Method(controlFlow.className, methodNode.name, methodNode.desc); + dfsTree = richControlFlow.dfsTree; + aKey = new Key(method, direction); + myIdentity = identity(); + } + + final State createStartState() { + return new State(0, new Conf(0, createStartFrame()), new ArrayList(), false, false); + } + + static boolean stateInstance(State curr, State prev) { + if (curr.taken != prev.taken) { + return false; + } + if (!AbstractValues.isInstance(curr.conf, prev.conf)) { + return false; + } + if (curr.history.size() != prev.history.size()) { + return false; + } + for (int i = 0; i < curr.history.size(); i++) { + if (!AbstractValues.isInstance(curr.history.get(i), prev.history.get(i))) { + return false; + } + } + return true; + } + + final Equation analyze() throws AnalyzerException { + pending.push(new ProceedState(createStartState())); + while (!pending.isEmpty() && earlyResult == null) { + PendingAction action = pending.pop(); + if (action instanceof MakeResult) { + MakeResult makeResult = (MakeResult) action; + ArrayList subResults = new ArrayList(); + for (int index : makeResult.indices) { + subResults.add(results.get(index)); + } + Res result = combineResults(makeResult.subResult, subResults); + if (isEarlyResult(result)) { + earlyResult = result; + } else { + State state = makeResult.state; + int insnIndex = state.insnIndex; + results.put(state.index, result); + List thisComputed = computed.get(insnIndex); + if (thisComputed == null) { + thisComputed = new ArrayList(); + computed.put(insnIndex, thisComputed); + } + thisComputed.add(state); + } + } + else if (action instanceof ProceedState) { + ProceedState proceedState = (ProceedState) action; + State state = proceedState.state; + int insnIndex = state.insnIndex; + Conf conf = state.conf; + List history = state.history; + + boolean fold = false; + if (dfsTree.loopEnters.contains(insnIndex)) { + for (Conf prev : history) { + if (AbstractValues.isInstance(conf, prev)) { + fold = true; + } + } + } + if (fold) { + results.put(state.index, myIdentity); + List thisComputed = computed.get(insnIndex); + if (thisComputed == null) { + thisComputed = new ArrayList(); + computed.put(insnIndex, thisComputed); + } + thisComputed.add(state); + } + else { + State baseState = null; + List thisComputed = computed.get(insnIndex); + if (thisComputed != null) { + for (State prevState : thisComputed) { + if (stateInstance(state, prevState)) { + baseState = prevState; + break; + } + } + } + if (baseState != null) { + results.put(state.index, results.get(baseState.index)); + } else { + // the main call + processState(state); + } + + } + } + } + if (earlyResult != null) { + return mkEquation(earlyResult); + } else { + return mkEquation(results.get(0)); + } + } + + final Frame createStartFrame() { + Frame frame = new Frame(methodNode.maxLocals, methodNode.maxStack); + Type returnType = Type.getReturnType(methodNode.desc); + BasicValue returnValue = Type.VOID_TYPE.equals(returnType) ? null : new BasicValue(returnType); + frame.setReturn(returnValue); + + Type[] args = Type.getArgumentTypes(methodNode.desc); + int local = 0; + if ((methodNode.access & Opcodes.ACC_STATIC) == 0) { + frame.setLocal(local++, new BasicValue(Type.getObjectType(controlFlow.className))); + } + for (int i = 0; i < args.length; i++) { + BasicValue value; + if (direction instanceof InOut && ((InOut)direction).paramIndex == i) { + value = new AbstractValues.ParamValue(args[i]); + } + else if (direction instanceof In && ((In)direction).paramIndex == i) { + value = new AbstractValues.ParamValue(args[i]); + } + else { + value = new BasicValue(args[i]); + } + frame.setLocal(local++, value); + if (args[i].getSize() == 2) { + frame.setLocal(local++, BasicValue.UNINITIALIZED_VALUE); + } + } + while (local < methodNode.maxLocals) { + frame.setLocal(local++, BasicValue.UNINITIALIZED_VALUE); + } + return frame; + } + + static BasicValue popValue(Frame frame) { + return frame.getStack(frame.getStackSize() - 1); + } + + static List append(List xs, A x) { + ArrayList result = new ArrayList(); + if (xs != null) { + result.addAll(xs); + } + result.add(x); + return result; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisHandler.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisHandler.java new file mode 100644 index 000000000000..d72abfb2bdea --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisHandler.java @@ -0,0 +1,261 @@ +/* + * 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.ProjectTopics; +import com.intellij.lang.PsiBuilder; +import com.intellij.lang.java.parser.JavaParser; +import com.intellij.lang.java.parser.JavaParserUtil; +import com.intellij.openapi.components.AbstractProjectComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.DumbModeTask; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileVisitor; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiManager; +import com.intellij.psi.PsiModifierListOwner; +import com.intellij.psi.impl.source.*; +import com.intellij.psi.util.PsiFormatUtil; +import com.intellij.util.Function; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashSet; +import com.intellij.util.containers.MostlySingularMultiMap; +import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +public class BytecodeAnalysisHandler extends AbstractProjectComponent { + + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisHandler"); + protected final PsiManager myPsiManager; + + private MostlySingularMultiMap myAnnotations = new MostlySingularMultiMap(); + + void setAnnotations(MostlySingularMultiMap annotations) { + this.myAnnotations = annotations; + } + + public BytecodeAnalysisHandler(Project project, PsiManager psiManager) { + super(project); + myPsiManager = psiManager; + + StartupManager.getInstance(project).registerPostStartupActivity(new Runnable() { + @Override + public void run() { + doIndex(); + } + }); + final MessageBusConnection connection = myProject.getMessageBus().connect(); + connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { + @Override + public void rootsChanged(ModuleRootEvent event) { + doIndex(); + } + }); + } + + @Nullable + public PsiAnnotation findInferredAnnotation(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN) { + String key = getExternalName(listOwner); + if (key == null) { + return null; + } + SmartList list = collectInferredAnnotations(listOwner); + AnnotationData data = findByFQN(list, annotationFQN); + if (data == null) { + return null; + } + LOG.info("annotation: " + key + " " + data); + return data.getAnnotation(this); + } + + @Nullable + private static AnnotationData findByFQN(@NotNull List map, @NotNull final String annotationFQN) { + return ContainerUtil.find(map, new Condition() { + @Override + public boolean value(AnnotationData data) { + return data.annotationClassFqName.equals(annotationFQN); + } + }); + } + + @Nullable + public PsiAnnotation[] findInferredAnnotations(@NotNull PsiModifierListOwner listOwner) { + SmartList result = collectInferredAnnotations(listOwner); + if (result == null || result.isEmpty()) return null; + PsiAnnotation[] myResult = ContainerUtil.map2Array(result, PsiAnnotation.EMPTY_ARRAY, new Function() { + @Override + public PsiAnnotation fun(AnnotationData data) { + return data.getAnnotation(BytecodeAnalysisHandler.this); + } + }); + String key = getExternalName(listOwner); + LOG.info("annotations: " + key + " " + result); + return myResult; + } + + private SmartList collectInferredAnnotations(PsiModifierListOwner listOwner) { + String key = getExternalName(listOwner); + if (key == null) { + return null; + } + SmartList result = new SmartList(); + Iterable inferred = myAnnotations.get(key); + ContainerUtil.addAll(result, inferred); + return result; + } + + @Nullable + protected static String getExternalName(@NotNull PsiModifierListOwner listOwner) { + return PsiFormatUtil.getExternalName(listOwner, false, Integer.MAX_VALUE); + } + // interner for storing annotation FQN + private final CharTableImpl charTable = new CharTableImpl(); + private static final JavaParserUtil.ParserWrapper ANNOTATION = new JavaParserUtil.ParserWrapper() { + @Override + public void parse(final PsiBuilder builder) { + JavaParser.INSTANCE.getDeclarationParser().parseAnnotation(builder); + } + }; + @NotNull + PsiAnnotation createAnnotationFromText(@NotNull final String text) throws IncorrectOperationException { + // synchronize during interning in charTable + synchronized (charTable) { + final DummyHolder holder = DummyHolderFactory.createHolder(myPsiManager, + new JavaDummyElement(text, ANNOTATION, LanguageLevel.HIGHEST), null, + charTable); + final PsiElement element = SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode()); + if (!(element instanceof PsiAnnotation)) { + throw new IncorrectOperationException("Incorrect annotation \"" + text + "\"."); + } + return (PsiAnnotation)element; + } + } + + private void doIndex() { + DumbService.getInstance(myProject).queueTask(new BytecodeAnalysisTask(myProject)); + } +} + +class AnnotationData { + @NotNull final String annotationClassFqName; + @NotNull final String annotationParameters; + private volatile PsiAnnotation annotation; + + AnnotationData(@NotNull String annotationClassFqName, @NotNull String annotationParameters) { + this.annotationClassFqName = annotationClassFqName; + this.annotationParameters = annotationParameters; + } + + @NotNull + PsiAnnotation getAnnotation(@NotNull BytecodeAnalysisHandler context) { + PsiAnnotation a = annotation; + if (a == null) { + annotation = a = context.createAnnotationFromText("@" + annotationClassFqName + (annotationParameters.isEmpty() ? "" : "("+annotationParameters+")")); + } + return a; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AnnotationData data = (AnnotationData)o; + + return annotationClassFqName.equals(data.annotationClassFqName) && annotationParameters.equals(data.annotationParameters); + } + + @Override + public int hashCode() { + int result = annotationClassFqName.hashCode(); + result = 31 * result + annotationParameters.hashCode(); + return result; + } + + @Override + public String toString() { + return "AnnotationData{" + + "annotationClassFqName='" + annotationClassFqName + '\'' + + ", annotationParameters='" + annotationParameters + '\'' + + '}'; + } +} + +class BytecodeAnalysisTask extends DumbModeTask { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisTask"); + private final Project myProject; + private long myFileCount = 0; + + BytecodeAnalysisTask(Project project) { + myProject = project; + } + + private VirtualFileVisitor myCountFileVisitor = new VirtualFileVisitor() { + @Override + public boolean visitFile(@NotNull VirtualFile file) { + if (!file.isDirectory() && "class".equals(file.getExtension())) { + myFileCount ++; + } + return true; + } + }; + + @Override + public void performInDumbMode(@NotNull ProgressIndicator indicator) { + indicator.setText("Bytecode analysis"); + HashSet classRoots = new HashSet(); + ModuleManager moduleManager = ModuleManager.getInstance(myProject); + for (Module module : moduleManager.getModules()) { + ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); + OrderEntry[] entries = moduleRootManager.getOrderEntries(); + for (OrderEntry entry : entries) { + if (!(entry instanceof JdkOrderEntry)) { + Collections.addAll(classRoots, entry.getFiles(OrderRootType.CLASSES)); + } + } + } + // to display progress + for (VirtualFile classRoot : classRoots) { + VfsUtilCore.visitChildrenRecursively(classRoot, myCountFileVisitor); + } + indicator.setFraction(0.01); + LOG.info("Found " + myFileCount + " classes to Index"); + ClassProcessor myClassProcessor = new ClassProcessor(indicator, myFileCount); + for (VirtualFile classRoot : classRoots) { + VfsUtilCore.visitChildrenRecursively(classRoot, myClassProcessor); + } + myProject.getComponent(BytecodeAnalysisHandler.class).setAnnotations(myClassProcessor.annotations()); + } + + +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassProcessor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassProcessor.java new file mode 100644 index 000000000000..0f29f87d6a25 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassProcessor.java @@ -0,0 +1,167 @@ +/* + * 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.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileVisitor; +import com.intellij.util.containers.MostlySingularMultiMap; +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.io.IOException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class ClassProcessor extends VirtualFileVisitor { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.bytecodeAnalysis.ClassProcessor"); + + final static ELattice valueLattice = new ELattice(Value.Bot, Value.Top); + final Solver solver = new Solver(valueLattice); + final Map extras = new HashMap(); + final @NotNull ProgressIndicator myProgressIndicator; + private final long totalClassFiles; + long processed = 0; + + public ClassProcessor(@NotNull ProgressIndicator indicator, long totalClassFiles) { + this.myProgressIndicator = indicator; + this.totalClassFiles = totalClassFiles; + } + + @Override + public boolean visitFile(@NotNull VirtualFile file) { + if (!file.isDirectory() && "class".equals(file.getExtension())) { + try { + processClass(new ClassReader(file.contentsToByteArray())); + } + catch (IOException e) { + // TODO + } + myProgressIndicator.setFraction((double)processed++ / totalClassFiles); + } + return true; + } + + public void processClass(final ClassReader classReader) { + classReader.accept(new ClassVisitor(Opcodes.ASM5) { + @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) { + @Override + public void visitEnd() { + super.visitEnd(); + processMethod(classReader.getClassName(), node); + } + }; + } + }, 0); + } + + void processMethod(String className, MethodNode methodNode) { + Method method = new Method(className, methodNode.name, methodNode.desc); + extras.put(method, new MethodExtra(methodNode.signature, methodNode.access)); + + ControlFlowGraph graph = cfg.buildControlFlowGraph(className, methodNode); + boolean added = false; + Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc); + Type resultType = Type.getReturnType(methodNode.desc); + int resultSort = resultType.getSort(); + + boolean isReferenceResult = resultSort == Type.OBJECT || resultSort == Type.ARRAY; + boolean isBooleanResult = Type.BOOLEAN_TYPE == resultType; + + if (graph.transitions.length > 0) { + DFSTree dfs = cfg.buildDFSTree(graph.transitions); + boolean reducible = dfs.back.isEmpty() || cfg.reducible(graph, dfs); + if (reducible) { + List> toAdd = new LinkedList>(); + try { + for (int i = 0; i < argumentTypes.length; i++) { + Type argType = argumentTypes[i]; + int argSort = argType.getSort(); + boolean isReferenceArg = argSort == Type.OBJECT || argSort == Type.ARRAY; + boolean isBooleanArg = Type.BOOLEAN_TYPE.equals(argType); + if (isReferenceArg) { + toAdd.add(new NonNullInAnalysis(new RichControlFlow(graph, dfs), new In(i)).analyze()); + } + if (isReferenceResult || isBooleanResult) { + if (isReferenceArg) { + toAdd.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.Null)).analyze()); + toAdd.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.NotNull)).analyze()); + } + if (isBooleanArg) { + toAdd.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.False)).analyze()); + toAdd.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.True)).analyze()); + } + } + } + if (isReferenceResult) { + toAdd.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new Out()).analyze()); + } + added = true; + for (Equation equation : toAdd) { + solver.addEquation(equation); + } + } catch (AnalyzerException e) { + throw new RuntimeException(); + } + } else { + LOG.debug("CFG for " + + className + " " + + methodNode.name + + methodNode.desc + " " + + "is not reducible"); + } + } + + if (!added) { + method = new Method(className, methodNode.name, methodNode.desc); + for (int i = 0; i < argumentTypes.length; i++) { + Type argType = argumentTypes[i]; + int argSort = argType.getSort(); + boolean isReferenceArg = argSort == Type.OBJECT || argSort == Type.ARRAY; + + if (isReferenceArg) { + solver.addEquation(new Equation(new Key(method, new In(i)), new Final(Value.Top))); + if (isReferenceResult || isBooleanResult) { + solver.addEquation(new Equation(new Key(method, new InOut(i, Value.Null)), new Final(Value.Top))); + solver.addEquation(new Equation(new Key(method, new InOut(i, Value.NotNull)), new Final(Value.Top))); + } + } + if (Type.BOOLEAN_TYPE.equals(argType)) { + if (isReferenceResult || isBooleanResult) { + solver.addEquation(new Equation(new Key(method, new InOut(i, Value.False)), new Final(Value.Top))); + solver.addEquation(new Equation(new Key(method, new InOut(i, Value.True)), new Final(Value.Top))); + } + } + } + if (isReferenceResult) { + solver.addEquation(new Equation(new Key(method, new Out()), new Final(Value.Top))); + } + } + } + + MostlySingularMultiMap annotations() { + Map solutions = solver.solve(); + return Util.makeAnnotations(solutions, extras); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Contracts.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Contracts.java new file mode 100644 index 000000000000..736d6f59c356 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Contracts.java @@ -0,0 +1,351 @@ +/* + * 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 org.jetbrains.org.objectweb.asm.Handle; +import org.jetbrains.org.objectweb.asm.Type; +import org.jetbrains.org.objectweb.asm.tree.*; +import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException; +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter; +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue; +import org.jetbrains.org.objectweb.asm.tree.analysis.Frame; + +import java.util.*; + +import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.*; +import static org.jetbrains.org.objectweb.asm.Opcodes.*; + +class InOutAnalysis extends Analysis> { + + final Result.ResultUtil resultUtil = + new Result.ResultUtil(new ELattice(Value.Bot, Value.Top)); + + private final InOutInterpreter interpreter; + private final Value inValue; + + protected InOutAnalysis(RichControlFlow richControlFlow, Direction direction) { + super(richControlFlow, direction); + interpreter = new InOutInterpreter(direction); + inValue = direction instanceof InOut ? ((InOut)direction).inValue : null; + } + + @Override + Result identity() { + return new Final(Value.Bot); + } + + @Override + Result combineResults(Result delta, List> subResults) { + Result result = null; + for (Result subResult : subResults) { + if (result == null) { + result = subResult; + } else { + result = resultUtil.join(result, subResult); + } + } + return result; + } + + @Override + boolean isEarlyResult(Result res) { + Value value = res instanceof Final ? ((Final)res).value : ((Pending)res).infinum; + return value == Value.Top; + } + + @Override + Equation mkEquation(Result res) { + return new Equation(aKey, res); + } + + private int id = 0; + + @Override + void processState(State state) throws AnalyzerException { + int stateIndex = state.index; + Conf preConf = state.conf; + int insnIndex = preConf.insnIndex; + boolean loopEnter = dfsTree.loopEnters.contains(insnIndex); + Conf conf = loopEnter ? generalize(preConf) : preConf; + List history = state.history; + boolean taken = state.taken; + Frame frame = conf.frame; + AbstractInsnNode insnNode = methodNode.instructions.get(insnIndex); + List nextHistory = dfsTree.loopEnters.contains(insnIndex) ? append(history, conf) : history; + Frame nextFrame = execute(frame, insnNode); + + int opcode = insnNode.getOpcode(); + switch (opcode) { + case ARETURN: + case IRETURN: + case LRETURN: + case FRETURN: + case DRETURN: + case RETURN: + BasicValue stackTop = popValue(frame); + if (FalseValue == stackTop) { + results.put(stateIndex, new Final(Value.False)); + computed.put(insnIndex, append(computed.get(insnIndex), state)); + } + else if (TrueValue == stackTop) { + results.put(stateIndex, new Final(Value.True)); + computed.put(insnIndex, append(computed.get(insnIndex), state)); + } + else if (NullValue == stackTop) { + results.put(stateIndex, new Final(Value.Null)); + computed.put(insnIndex, append(computed.get(insnIndex), state)); + } + else if (stackTop instanceof NotNullValue) { + results.put(stateIndex, new Final(Value.NotNull)); + computed.put(insnIndex, append(computed.get(insnIndex), state)); + } + else if (stackTop instanceof ParamValue) { + results.put(stateIndex, new Final(inValue)); + computed.put(insnIndex, append(computed.get(insnIndex), state)); + } + else if (stackTop instanceof CallResultValue) { + Set keys = ((CallResultValue) stackTop).inters; + Set> components = new HashSet>(); + components.add(new Component(false, keys)); + results.put(stateIndex, new Pending(Value.Bot, components)); + computed.put(insnIndex, append(computed.get(insnIndex), state)); + } + else { + earlyResult = new Final(Value.Top); + } + return; + case ATHROW: + earlyResult = new Final(Value.Top); + return; + default: + } + + if (opcode == IFNONNULL && popValue(frame) instanceof ParamValue) { + int nextInsnIndex = inValue == Value.Null ? insnIndex + 1 : methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label); + State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false); + pending.push(new MakeResult>(state, myIdentity, Collections.singletonList(nextState.index))); + pending.push(new ProceedState>(nextState)); + return; + } + + if (opcode == IFNULL && popValue(frame) instanceof ParamValue) { + int nextInsnIndex = inValue == Value.NotNull ? insnIndex + 1 : methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label); + State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false); + pending.push(new MakeResult>(state, myIdentity, Collections.singletonList(nextState.index))); + pending.push(new ProceedState>(nextState)); + return; + } + + if (opcode == IFEQ && popValue(frame) == InstanceOfCheckValue && inValue == Value.Null) { + int nextInsnIndex = methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label); + State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false); + pending.push(new MakeResult>(state, myIdentity, Collections.singletonList(nextState.index))); + pending.push(new ProceedState>(nextState)); + return; + } + + if (opcode == IFNE && popValue(frame) == InstanceOfCheckValue && inValue == Value.Null) { + int nextInsnIndex = insnIndex + 1; + State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false); + pending.push(new MakeResult>(state, myIdentity, Collections.singletonList(nextState.index))); + pending.push(new ProceedState>(nextState)); + return; + } + + if (opcode == IFEQ && popValue(frame) instanceof ParamValue) { + int nextInsnIndex = inValue == Value.True ? insnIndex + 1 : methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label); + State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false); + pending.push(new MakeResult>(state, myIdentity, Collections.singletonList(nextState.index))); + pending.push(new ProceedState>(nextState)); + return; + } + + if (opcode == IFNE && popValue(frame) instanceof ParamValue) { + int nextInsnIndex = inValue == Value.False ? insnIndex + 1 : methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label); + State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false); + pending.push(new MakeResult>(state, myIdentity, Collections.singletonList(nextState.index))); + pending.push(new ProceedState>(nextState)); + return; + } + + // general case + List nextInsnIndices = controlFlow.transitions[insnIndex]; + List nextStates = new ArrayList(); + List subIndices = new ArrayList(); + for (int nextInsnIndex : nextInsnIndices) { + Frame nextFrame1 = nextFrame; + if (controlFlow.errorTransitions.contains(new Edge(insnIndex, nextInsnIndex))) { + nextFrame1 = new Frame(frame); + nextFrame1.clearStack(); + nextFrame1.push(new BasicValue(Type.getType("java/lang/Throwable"))); + } + nextStates.add(new State(++id, new Conf(nextInsnIndex, nextFrame1), nextHistory, taken, false)); + subIndices.add(id); + } + + pending.push(new MakeResult>(state, myIdentity, subIndices)); + for (State nextState : nextStates) { + pending.push(new ProceedState>(nextState)); + } + + } + + private Frame execute(Frame frame, AbstractInsnNode insnNode) throws AnalyzerException { + switch (insnNode.getType()) { + case AbstractInsnNode.LABEL: + case AbstractInsnNode.LINE: + case AbstractInsnNode.FRAME: + return frame; + default: + Frame nextFrame = new Frame(frame); + nextFrame.execute(insnNode, interpreter); + return nextFrame; + } + } + + private Conf generalize(Conf conf) { + Frame frame = conf.frame; + for (int i = 0; i < frame.getLocals(); i++) { + BasicValue value = frame.getLocal(i); + Class valueClass = value.getClass(); + if (valueClass != BasicValue.class && valueClass != ParamValue.class) { + frame.setLocal(i, new BasicValue(value.getType())); + } + } + + BasicValue[] stack = new BasicValue[frame.getStackSize()]; + for (int i = 0; i < frame.getStackSize(); i++) { + stack[i] = frame.getStack(i); + } + frame.clearStack(); + + for (BasicValue value : stack) { + Class valueClass = value.getClass(); + if (valueClass != BasicValue.class && valueClass != ParamValue.class) { + frame.push(new BasicValue(value.getType())); + } else { + frame.push(value); + } + } + + return conf; + } +} + +class InOutInterpreter extends BasicInterpreter { + final Direction direction; + + InOutInterpreter(Direction direction) { + this.direction = direction; + } + + @Override + public BasicValue newOperation(AbstractInsnNode insn) throws AnalyzerException { + switch (insn.getOpcode()) { + case ICONST_0: + return FalseValue; + case ICONST_1: + return TrueValue; + case ACONST_NULL: + return NullValue; + case LDC: + Object cst = ((LdcInsnNode) insn).cst; + if (cst instanceof Type) { + Type type = (Type) cst; + if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { + return new NotNullValue(Type.getObjectType("java/lang/Class")); + } + if (type.getSort() == Type.METHOD) { + return new NotNullValue(Type.getObjectType("java/lang/invoke/MethodType")); + } + } + else if (cst instanceof String) { + return new NotNullValue(Type.getObjectType("java/lang/String")); + } + else if (cst instanceof Handle) { + return new NotNullValue(Type.getObjectType("java/lang/invoke/MethodHandle")); + } + break; + case NEW: + return new NotNullValue(Type.getObjectType(((TypeInsnNode)insn).desc)); + default: + } + return super.newOperation(insn); + } + + @Override + public BasicValue unaryOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException { + switch (insn.getOpcode()) { + case CHECKCAST: + if (value instanceof ParamValue) { + return new ParamValue(Type.getObjectType(((TypeInsnNode)insn).desc)); + } + break; + case INSTANCEOF: + if (value instanceof ParamValue) { + return InstanceOfCheckValue; + } + break; + case NEWARRAY: + case ANEWARRAY: + return new NotNullValue(super.unaryOperation(insn, value).getType()); + default: + } + return super.unaryOperation(insn, value); + } + + @Override + public BasicValue naryOperation(AbstractInsnNode insn, List values) throws AnalyzerException { + int opCode = insn.getOpcode(); + int shift = opCode == INVOKESTATIC ? 0 : 1; + switch (opCode) { + case INVOKESTATIC: + case INVOKESPECIAL: + MethodInsnNode mNode = (MethodInsnNode) insn; + Method method = new Method(mNode.owner, mNode.name, mNode.desc); + Type retType = Type.getReturnType(mNode.desc); + boolean isRefRetType = retType.getSort() == Type.OBJECT || retType.getSort() == Type.ARRAY; + if (!Type.VOID_TYPE.equals(retType)) { + if (direction instanceof InOut) { + InOut inOut = (InOut) direction; + HashSet keys = new HashSet(); + for (int i = shift; i < values.size(); i++) { + if (values.get(i) instanceof ParamValue) { + keys.add(new Key(method, new InOut(i - shift, inOut.inValue))); + } + } + if (isRefRetType) { + keys.add(new Key(method, new Out())); + } + if (!keys.isEmpty()) { + return new CallResultValue(retType, keys); + } + } + else if (isRefRetType) { + HashSet keys = new HashSet(); + keys.add(new Key(method, new Out())); + return new CallResultValue(retType, keys); + } + + } + break; + case MULTIANEWARRAY: + return new NotNullValue(super.naryOperation(insn, values).getType()); + default: + } + return super.naryOperation(insn, values); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ControlFlow.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ControlFlow.java new file mode 100644 index 000000000000..63dcba206316 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ControlFlow.java @@ -0,0 +1,292 @@ +/* + * 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 org.jetbrains.org.objectweb.asm.tree.MethodNode; +import org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer; +import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException; +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter; +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue; + +import java.util.*; + +final class cfg { + static ControlFlowGraph buildControlFlowGraph(String className, MethodNode methodNode) { + try { + return new ControlFlowBuilder(className, methodNode).buildCFG(); + } catch (AnalyzerException e) { + throw new RuntimeException(); + } + } + + private interface Action {} + private static class MarkScanned implements Action { + final int node; + private MarkScanned(int node) { + this.node = node; + } + } + private static class ExamineEdge implements Action { + final int from; + final int to; + + private ExamineEdge(int from, int to) { + this.from = from; + this.to = to; + } + } + + // Graphs: Theory and Algorithms. by K. Thulasiraman , M. N. S. Swamy (1992) + // 11.7.2 DFS of a directed graph + static DFSTree buildDFSTree(List[] transitions) { + Set tree = new HashSet(); + Set forward = new HashSet(); + Set back = new HashSet(); + Set cross = new HashSet(); + + boolean[] marked = new boolean[transitions.length]; + boolean[] scanned = new boolean[transitions.length]; + int[] preOrder = new int[transitions.length]; + int[] postOrder = new int[transitions.length]; + + int entered = 0; + int completed = 0; + + Deque stack = new LinkedList(); + Set loopEnters = new HashSet(); + + // enter 0 + entered ++; + preOrder[0] = entered; + marked[0] = true; + stack.push(new MarkScanned(0)); + for (int to : transitions[0]) { + stack.push(new ExamineEdge(0, to)); + } + + while (!stack.isEmpty()) { + Action action = stack.pop(); + if (action instanceof MarkScanned) { + MarkScanned markScannedAction = (MarkScanned) action; + completed ++; + postOrder[markScannedAction.node] = completed; + scanned[markScannedAction.node] = true; + } + else { + ExamineEdge examineEdgeAction = (ExamineEdge) action; + int from = examineEdgeAction.from; + int to = examineEdgeAction.to; + if (!marked[to]) { + tree.add(new Edge(from, to)); + // enter to + entered ++; + preOrder[to] = entered; + marked[to] = true; + stack.push(new MarkScanned(to)); + for (int to1 : transitions[to]) { + stack.push(new ExamineEdge(to, to1)); + } + } + else if (preOrder[to] > preOrder[from]) { + forward.add(new Edge(from, to)); + } + else if (preOrder[to] < preOrder[from] && !scanned[to]) { + back.add(new Edge(from, to)); + loopEnters.add(to); + } else { + cross.add(new Edge(from, to)); + } + } + } + + return new DFSTree(preOrder, postOrder, tree, forward, back, cross, loopEnters); + } + + // Tarjan. Testing flow graph reducibility. + // Journal of Computer and System Sciences 9.3 (1974): 355-365. + static boolean reducible(ControlFlowGraph cfg, DFSTree dfs) { + int size = cfg.transitions.length; + HashSet[] cycles = new HashSet[size]; + HashSet[] nonCycles = new HashSet[size]; + int[] collapsedTo = new int[size]; + for (int i = 0; i < size; i++) { + cycles[i] = new HashSet(); + nonCycles[i] = new HashSet(); + collapsedTo[i] = i; + } + + for (Edge edge : dfs.back) { + cycles[edge.to].add(edge.from); + } + for (Edge edge : dfs.tree) { + nonCycles[edge.to].add(edge.from); + } + for (Edge edge : dfs.forward) { + nonCycles[edge.to].add(edge.from); + } + for (Edge edge : dfs.cross) { + nonCycles[edge.to].add(edge.from); + } + + for (int w = size - 1; w >= 0 ; w--) { + HashSet p = new HashSet(cycles[w]); + Queue queue = new LinkedList(cycles[w]); + + while (!queue.isEmpty()) { + int x = queue.remove(); + for (int y : nonCycles[x]) { + int y1 = collapsedTo[y]; + if (!dfs.isDescendant(y1, w)) { + return false; + } + if (y1 != w && p.add(y1)) { + queue.add(y1); + } + } + } + + for (int x : p) { + collapsedTo[x] = w; + } + } + + return true; + } + +} + +final class Edge { + final int from, to; + + Edge(int from, int to) { + this.from = from; + this.to = to; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Edge)) { + return false; + } + Edge edge = (Edge) o; + return from == edge.from && to == edge.to; + } + + @Override + public int hashCode() { + return 31 * from + to; + } + + @Override + public String toString() { + return "(" + from + "," + to + ")"; + } +} + +final class ControlFlowGraph { + final String className; + final MethodNode methodNode; + final List[] transitions; + final Set errorTransitions; + + ControlFlowGraph(String className, MethodNode methodNode, List[] transitions, Set errorTransitions) { + this.className = className; + this.methodNode = methodNode; + this.transitions = transitions; + this.errorTransitions = errorTransitions; + } + + @Override + public String toString() { + return "CFG(" + + Arrays.toString(transitions) + "," + + errorTransitions + + ')'; + } +} + +final class RichControlFlow { + final ControlFlowGraph controlFlow; + final DFSTree dfsTree; + + RichControlFlow(ControlFlowGraph controlFlow, DFSTree dfsTree) { + this.controlFlow = controlFlow; + this.dfsTree = dfsTree; + } +} + +final class ControlFlowBuilder extends Analyzer { + static final BasicInterpreter INTERPRETER = new BasicInterpreter(); + final String className; + final MethodNode methodNode; + final LinkedList[] transitions; + final Set errorTransitions; + + ControlFlowBuilder(String className, MethodNode methodNode) { + super(INTERPRETER); + this.className = className; + this.methodNode = methodNode; + transitions = new LinkedList[methodNode.instructions.size()]; + for (int i = 0; i < transitions.length; i++) { + transitions[i] = new LinkedList(); + } + errorTransitions = new HashSet(); + } + + final ControlFlowGraph buildCFG() throws AnalyzerException { + analyze(className, methodNode); + return new ControlFlowGraph(className, methodNode, transitions, errorTransitions); + } + + @Override + protected final void newControlFlowEdge(int insn, int successor) { + transitions[insn].addFirst(successor); + } + + @Override + protected final boolean newControlFlowExceptionEdge(int insn, int successor) { + transitions[insn].addFirst(successor); + errorTransitions.add(new Edge(insn, successor)); + return true; + } +} + +final class DFSTree { + final int[] preOrder, postOrder; + final Set tree, forward, back, cross; + final Set loopEnters; + + DFSTree(int[] preOrder, + int[] postOrder, + Set tree, + Set forward, + Set back, + Set cross, + Set loopEnters) { + this.preOrder = preOrder; + this.postOrder = postOrder; + this.tree = tree; + this.forward = forward; + this.back = back; + this.cross = cross; + this.loopEnters = loopEnters; + } + + final boolean isDescendant(int child, int parent) { + return preOrder[parent] <= preOrder[child] && postOrder[child] <= postOrder[parent]; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java new file mode 100644 index 000000000000..73929904d73b --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java @@ -0,0 +1,190 @@ +/* + * 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; + +final class MethodExtra { + final String signature; + final int access; + + MethodExtra(String signature, int access) { + this.signature = signature; + this.access = access; + } +} + +final class Method { + final String internalClassName; + final String methodName; + final String methodDesc; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Method method = (Method) o; + return internalClassName.equals(method.internalClassName) && methodDesc.equals(method.methodDesc) && methodName.equals(method.methodName); + } + + @Override + public int hashCode() { + int result = internalClassName.hashCode(); + result = 31 * result + methodName.hashCode(); + result = 31 * result + methodDesc.hashCode(); + return result; + } + + Method(String internalClassName, String methodName, String methodDesc) { + this.internalClassName = internalClassName; + this.methodName = methodName; + this.methodDesc = methodDesc; + } + + @Override + public String toString() { + return "Method(" + + internalClassName + ',' + + methodName + ',' + + methodDesc + + ')'; + } +} + +enum Value { + Bot, NotNull, Null, True, False, Top +} + +interface Direction {} +final class In implements Direction { + final int paramIndex; + + In(int paramIndex) { + this.paramIndex = paramIndex; + } + + @Override + public String toString() { + return "In(" + paramIndex + ")"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + In in = (In) o; + if (paramIndex != in.paramIndex) return false; + return true; + } + + @Override + public int hashCode() { + return paramIndex; + } +} + +final class InOut implements Direction { + final int paramIndex; + final Value inValue; + + InOut(int paramIndex, Value inValue) { + this.paramIndex = paramIndex; + this.inValue = inValue; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InOut inOut = (InOut) o; + + if (paramIndex != inOut.paramIndex) return false; + if (inValue != inOut.inValue) return false; + + return true; + } + + @Override + public int hashCode() { + int result = paramIndex; + result = 31 * result + inValue.hashCode(); + return result; + } + + @Override + public String toString() { + return "InOut(" + + paramIndex + + ", " + inValue + + ')'; + } +} + +final class Out implements Direction { + @Override + public String toString() { + return "Out"; + } + + @Override + public int hashCode() { + return 1; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Out; + } +} + +final class Key { + final Method method; + final Direction direction; + + Key(Method method, Direction direction) { + this.method = method; + this.direction = direction; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (!direction.equals(key.direction)) return false; + if (!method.equals(key.method)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = method.hashCode(); + result = 31 * result + direction.hashCode(); + return result; + } + + @Override + public String toString() { + return "Key{" + + "method=" + method + + ", direction=" + direction + + '}'; + } +} + + diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Parameters.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Parameters.java new file mode 100644 index 000000000000..627732e7a2a2 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Parameters.java @@ -0,0 +1,385 @@ +/* + * 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 org.jetbrains.org.objectweb.asm.Type; +import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode; +import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode; +import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode; +import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode; +import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException; +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter; +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue; +import org.jetbrains.org.objectweb.asm.tree.analysis.Frame; + +import java.util.*; + +import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.InstanceOfCheckValue; +import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.ParamValue; +import static com.intellij.codeInspection.bytecodeAnalysis.PResults.*; +import static org.jetbrains.org.objectweb.asm.Opcodes.*; + +abstract class PResults { + // SoP = sum of products + static Set> join(Set> sop1, Set> sop2) { + Set> sop = new HashSet>(); + sop.addAll(sop1); + sop.addAll(sop2); + return sop; + } + + static Set> meet(Set> sop1, Set> sop2) { + Set> sop = new HashSet>(); + for (Set prod1 : sop1) { + for (Set prod2 : sop2) { + Set prod = new HashSet(); + prod.addAll(prod1); + prod.addAll(prod2); + sop.add(prod); + } + } + return sop; + } + + // Results + interface PResult {} + static final PResult Identity = new PResult() { + @Override + public String toString() { + return "Identity"; + } + }; + static final PResult Return = new PResult() { + @Override + public String toString() { + return "Return"; + } + }; + static final PResult NPE = new PResult() { + @Override + public String toString() { + return "NPE"; + } + }; + static final class ConditionalNPE implements PResult { + final Set> sop; + public ConditionalNPE(Set> sop) { + this.sop = sop; + } + + public ConditionalNPE(Key key) { + sop = new HashSet>(); + Set prod = new HashSet(); + prod.add(key); + sop.add(prod); + } + } + + static PResult join(PResult r1, PResult r2) { + if (Identity == r1) return r2; + if (Identity == r2) return r1; + if (Return == r1) return Return; + if (Return == r2) return Return; + if (NPE == r1) return r2; + if (NPE == r2) return r1; + ConditionalNPE cnpe1 = (ConditionalNPE) r1; + ConditionalNPE cnpe2 = (ConditionalNPE) r2; + return new ConditionalNPE(join(cnpe1.sop, cnpe2.sop)); + } + + static PResult meet(PResult r1, PResult r2) { + if (Identity == r1) return r2; + if (Identity == r2) return r1; + if (Return == r1) return r2; + if (Return == r2) return r1; + if (NPE == r1) return NPE; + if (NPE == r2) return NPE; + ConditionalNPE cnpe1 = (ConditionalNPE) r1; + ConditionalNPE cnpe2 = (ConditionalNPE) r2; + return new ConditionalNPE(meet(cnpe1.sop, cnpe2.sop)); + } + +} + +class NonNullInAnalysis extends Analysis { + + private static final NonNullInInterpreter interpreter = new NonNullInInterpreter(); + private final Key parameter; + + protected NonNullInAnalysis(RichControlFlow richControlFlow, Direction direction) { + super(richControlFlow, direction); + parameter = new Key(method, direction); + } + + @Override + PResult identity() { + return Identity; + } + + @Override + PResult combineResults(PResult delta, List subResults) { + PResult subResult = Identity; + for (PResult sr : subResults) { + subResult = join(subResult, sr); + } + return meet(delta, subResult); + } + + @Override + boolean isEarlyResult(PResult result) { + return false; + } + + @Override + Equation mkEquation(PResult result) { + if (Identity == result || Return == result) { + return new Equation(parameter, new Final(Value.Top)); + } + else if (NPE == result) { + return new Equation(parameter, new Final(Value.NotNull)); + } + else { + ConditionalNPE condNpe = (ConditionalNPE) result; + Set> components = new HashSet>(); + for (Set prod : condNpe.sop) { + components.add(new Component(false, prod)); + } + return new Equation(parameter, new Pending(Value.NotNull, components)); + } + } + + private int id = 0; + private Frame nextFrame = null; + private PResult subResult = null; + + @Override + void processState(State state) throws AnalyzerException { + int stateIndex = state.index; + Conf conf = state.conf; + int insnIndex = conf.insnIndex; + List history = state.history; + boolean taken = state.taken; + Frame frame = conf.frame; + AbstractInsnNode insnNode = methodNode.instructions.get(insnIndex); + List nextHistory = dfsTree.loopEnters.contains(insnIndex) ? append(history, conf) : history; + boolean hasCompanions = state.hasCompanions; + execute(frame, insnNode); + + boolean notEmptySubResult = subResult != Identity; + + if (subResult == NPE) { + results.put(stateIndex, NPE); + computed.put(insnIndex, append(computed.get(insnIndex), state)); + return; + } + + int opcode = insnNode.getOpcode(); + switch (opcode) { + case ARETURN: + case IRETURN: + case LRETURN: + case FRETURN: + case DRETURN: + case RETURN: + if (!hasCompanions) { + earlyResult = Return; + } else { + results.put(stateIndex, Return); + computed.put(insnIndex, append(computed.get(insnIndex), state)); + } + return; + default: + } + + if (opcode == ATHROW) { + if (taken) { + results.put(stateIndex, NPE); + } else { + results.put(stateIndex, Identity); + } + computed.put(insnIndex, append(computed.get(insnIndex), state)); + return; + } + + if (opcode == IFNONNULL && popValue(frame) instanceof ParamValue) { + int nextInsnIndex = insnIndex + 1; + State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, hasCompanions || notEmptySubResult); + pending.push(new MakeResult(state, subResult, Collections.singletonList(nextState.index))); + pending.push(new ProceedState(nextState)); + return; + } + + if (opcode == IFNULL && popValue(frame) instanceof ParamValue) { + int nextInsnIndex = methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label); + State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, hasCompanions || notEmptySubResult); + pending.push(new MakeResult(state, subResult, Collections.singletonList(nextState.index))); + pending.push(new ProceedState(nextState)); + return; + } + + if (opcode == IFEQ && popValue(frame) == InstanceOfCheckValue) { + int nextInsnIndex = methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label); + State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, hasCompanions || notEmptySubResult); + pending.push(new MakeResult(state, subResult, Collections.singletonList(nextState.index))); + pending.push(new ProceedState(nextState)); + return; + } + + if (opcode == IFNE && popValue(frame) == InstanceOfCheckValue) { + int nextInsnIndex = insnIndex + 1; + State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, hasCompanions || notEmptySubResult); + pending.push(new MakeResult(state, subResult, Collections.singletonList(nextState.index))); + pending.push(new ProceedState(nextState)); + return; + } + + // general case + List nextInsnIndices = controlFlow.transitions[insnIndex]; + List nextStates = new ArrayList(); + List subIndices = new ArrayList(); + for (int nextInsnIndex : nextInsnIndices) { + Frame nextFrame1 = nextFrame; + if (controlFlow.errorTransitions.contains(new Edge(insnIndex, nextInsnIndex))) { + nextFrame1 = new Frame(frame); + nextFrame1.clearStack(); + nextFrame1.push(new BasicValue(Type.getType("java/lang/Throwable"))); + } + nextStates.add(new State(++id, new Conf(nextInsnIndex, nextFrame1), nextHistory, taken, hasCompanions || notEmptySubResult)); + subIndices.add(id); + } + + pending.push(new MakeResult(state, subResult, subIndices)); + for (State nextState : nextStates) { + pending.push(new ProceedState(nextState)); + } + + } + + private void execute(Frame frame, AbstractInsnNode insnNode) throws AnalyzerException { + switch (insnNode.getType()) { + case AbstractInsnNode.LABEL: + case AbstractInsnNode.LINE: + case AbstractInsnNode.FRAME: + nextFrame = frame; + subResult = Identity; + break; + default: + nextFrame = new Frame(frame); + interpreter.reset(); + nextFrame.execute(insnNode, interpreter); + subResult = interpreter.getSubResult(); + } + } +} + +class NonNullInInterpreter extends BasicInterpreter { + private PResult subResult = Identity; + public PResult getSubResult() { + return subResult; + } + void reset() { + subResult = Identity; + } + + @Override + public BasicValue unaryOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException { + switch (insn.getOpcode()) { + case GETFIELD: + case ARRAYLENGTH: + case MONITORENTER: + if (value instanceof ParamValue) { + subResult = NPE; + } + break; + case CHECKCAST: + if (value instanceof ParamValue) { + return new ParamValue(Type.getObjectType(((TypeInsnNode)insn).desc)); + } + break; + case INSTANCEOF: + if (value instanceof ParamValue) { + return InstanceOfCheckValue; + } + break; + default: + + } + return super.unaryOperation(insn, value); + } + + @Override + public BasicValue binaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2) throws AnalyzerException { + switch (insn.getOpcode()) { + case IALOAD: + case LALOAD: + case FALOAD: + case DALOAD: + case AALOAD: + case BALOAD: + case CALOAD: + case SALOAD: + case PUTFIELD: + if (value1 instanceof ParamValue) { + subResult = NPE; + } + break; + default: + } + return super.binaryOperation(insn, value1, value2); + } + + @Override + public BasicValue ternaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2, BasicValue value3) throws AnalyzerException { + switch (insn.getOpcode()) { + case IASTORE: + case LASTORE: + case FASTORE: + case DASTORE: + case AASTORE: + case BASTORE: + case CASTORE: + case SASTORE: + if (value1 instanceof ParamValue) { + subResult = NPE; + } + default: + } + return super.ternaryOperation(insn, value1, value2, value3); + } + + @Override + public BasicValue naryOperation(AbstractInsnNode insn, List values) throws AnalyzerException { + int opcode = insn.getOpcode(); + boolean isStaticInvoke = opcode == INVOKESTATIC; + int shift = isStaticInvoke ? 0 : 1; + if ((opcode == INVOKESPECIAL || opcode ==INVOKEINTERFACE || opcode == INVOKEVIRTUAL) && values.get(0) instanceof ParamValue) { + subResult = NPE; + } + switch (opcode) { + case INVOKESTATIC: + case INVOKESPECIAL: + MethodInsnNode methodNode = (MethodInsnNode) insn; + for (int i = shift; i < values.size(); i++) { + if (values.get(i) instanceof ParamValue) { + Method method = new Method(methodNode.owner, methodNode.name, methodNode.desc); + subResult = meet(subResult, new ConditionalNPE(new Key(method, new In(i - shift)))); + } + } + default: + } + return super.naryOperation(insn, values); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Solver.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Solver.java new file mode 100644 index 000000000000..417c12fc85ce --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Solver.java @@ -0,0 +1,323 @@ +/* + * 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 java.util.*; + +final class ELattice> { + final T bot; + final T top; + + ELattice(T bot, T top) { + this.bot = bot; + this.top = top; + } + + final T join(T x, T y) { + if (x == bot) return y; + if (y == bot) return x; + if (x == y) return x; + return top; + } + + final T meet(T x, T y) { + if (x == top) return y; + if (y == top) return x; + if (x == y) return x; + return bot; + } +} + +final class Component { + final boolean touched; + final Set ids; + + Component(boolean touched, Set ids) { + this.touched = touched; + this.ids = ids; + } + + Component remove(Id id) { + if (ids.contains(id)) { + HashSet newIds = new HashSet(ids); + newIds.remove(id); + return new Component(touched, newIds); + } + else { + return this; + } + } + + Component removeAndTouch(Id id) { + if (ids.contains(id)) { + HashSet newIds = new HashSet(ids); + newIds.remove(id); + return new Component(true, newIds); + } else { + return this; + } + } + + boolean isEmpty() { + return ids.isEmpty(); + } + + boolean isEmptyAndTouched() { + return ids.isEmpty() && touched; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Component component = (Component) o; + + if (touched != component.touched) return false; + if (!ids.equals(component.ids)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = (touched ? 1 : 0); + result = 31 * result + ids.hashCode(); + return result; + } + + @Override + public String toString() { + return "Component{" + + "touched=" + touched + + ", ids=" + ids + + '}'; + } +} + +interface Result { + static class ResultUtil> { + private final ELattice lattice; + final T top; + ResultUtil(ELattice lattice) { + this.lattice = lattice; + top = lattice.top; + } + + Result join(Result r1, Result r2) { + if (r1 instanceof Final && ((Final) r1).value == top) { + return r1; + } + if (r2 instanceof Final && ((Final) r2).value == top) { + return r2; + } + if (r1 instanceof Final && r2 instanceof Final) { + return new Final(lattice.join(((Final) r1).value, ((Final) r2).value)); + } + if (r1 instanceof Final && r2 instanceof Pending) { + Pending pending = (Pending) r2; + return new Pending(lattice.join(((Final) r1).value, pending.infinum), pending.delta); + } + if (r1 instanceof Pending && r2 instanceof Final) { + Pending pending = (Pending) r1; + return new Pending(lattice.join(((Final) r2).value, pending.infinum), pending.delta); + } + Pending pending1 = (Pending) r1; + Pending pending2 = (Pending) r2; + Set> delta = new HashSet>(); + delta.addAll(pending1.delta); + delta.addAll(pending2.delta); + return new Pending(lattice.join(pending1.infinum, pending2.infinum), delta); + } + } +} +final class Final implements Result { + final T value; + Final(T value) { + this.value = value; + } + + @Override + public String toString() { + return "Final{" + + "value=" + value + + '}'; + } +} + +final class Solution { + final Id id; + final Val value; + + Solution(Id id, Val value) { + this.id = id; + this.value = value; + } +} + +final class Pending implements Result { + final T infinum; + final Set> delta; + + Pending(T infinum, Set> delta) { + this.infinum = infinum; + this.delta = delta; + } + + @Override + public String toString() { + return "Pending{" + + "infinum=" + infinum + + ", delta=" + delta + + '}'; + } +} + +final class Equation { + final Id id; + final Result rhs; + + Equation(Id id, Result rhs) { + this.id = id; + this.rhs = rhs; + } + + @Override + public String toString() { + return "Equation{" + + "id=" + id + + ", rhs=" + rhs + + '}'; + } +} + +final class Solver> { + private final ELattice lattice; + private final HashMap> dependencies = new HashMap>(); + private final HashMap> pending = new HashMap>(); + private final Queue> moving = new LinkedList>(); + private final HashMap solved = new HashMap(); + + Solver(ELattice lattice) { + this.lattice = lattice; + } + + void addEquation(Equation equation) { + if (equation.rhs instanceof Final) { + Final finalResult = (Final) equation.rhs; + moving.add(new Solution(equation.id, finalResult.value)); + } + else if (equation.rhs instanceof Pending) { + Pending pendingResult = (Pending) equation.rhs; + if (pendingResult.infinum.equals(lattice.top)) { + moving.add(new Solution(equation.id, lattice.top)); + } + else { + for (Component component : pendingResult.delta) { + for (Id trigger : component.ids) { + Set set = dependencies.get(trigger); + if (set == null) { + set = new HashSet(); + dependencies.put(trigger, set); + } + set.add(equation.id); + } + } + pending.put(equation.id, pendingResult); + } + } + } + + Map solve() { + Solution sol; + while ((sol = moving.poll()) != null) { + solved.put(sol.id, sol.value); + Set dIds = dependencies.remove(sol.id); + if (dIds != null) { + for (Id dId : dIds) { + Pending pend = pending.remove(dId); + if (pend != null) { + Result pend1 = substitute(pend, sol.id, sol.value); + if (pend1 instanceof Final) { + Final fi = (Final) pend1; + moving.add(new Solution(dId, fi.value)); + } + else { + pending.put(dId, (Pending) pend1); + } + } + } + } + } + pending.clear(); + return solved; + } + + Result substitute(Pending pending, Id id, Val value) { + if (value.equals(lattice.bot)) { + HashSet> delta = new HashSet>(); + for (Component component : pending.delta) { + if (!component.ids.contains(id)) { + delta.add(component); + } + } + if (delta.isEmpty()) { + return new Final(pending.infinum); + } + else { + return new Pending(pending.infinum, delta); + } + } + else if (value.equals(lattice.top)) { + HashSet> delta = new HashSet>(); + for (Component component : pending.delta) { + Component component1 = component.remove(id); + if (!component1.isEmptyAndTouched()) { + if (component1.isEmpty()) { + return new Final(lattice.top); + } else { + delta.add(component1); + } + } + } + if (delta.isEmpty()) { + return new Final(pending.infinum); + } + else { + return new Pending(pending.infinum, delta); + } + } + else { + Val infinum = lattice.join(pending.infinum, value); + if (infinum == lattice.top) { + return new Final(lattice.top); + } + HashSet> delta = new HashSet>(); + for (Component component : pending.delta) { + Component component1 = component.removeAndTouch(id); + if (!component1.isEmpty()) { + delta.add(component1); + } + } + if (delta.isEmpty()) { + return new Final(infinum); + } + else { + return new Pending(infinum, delta); + } + } + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Util.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Util.java new file mode 100644 index 000000000000..b1754552a481 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Util.java @@ -0,0 +1,321 @@ +/* + * 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.util.containers.HashMap; +import com.intellij.util.containers.MostlySingularMultiMap; +import org.jetbrains.org.objectweb.asm.Opcodes; +import org.jetbrains.org.objectweb.asm.Type; +import org.jetbrains.org.objectweb.asm.signature.SignatureReader; +import org.jetbrains.org.objectweb.asm.signature.SignatureVisitor; + +import java.util.Map; + +public class Util { + + public static MostlySingularMultiMap makeAnnotations(Map solutions, Map extras) { + MostlySingularMultiMap annotations = new MostlySingularMultiMap(); + HashMap contracts = new HashMap(); + for (Map.Entry solution : solutions.entrySet()) { + Key key = solution.getKey(); + + Value value = solution.getValue(); + if (value == Value.Top || value == Value.Bot) { + continue; + } + + Direction direction = key.direction; + + String annKey = annotationKey(key.method, extras.get(key.method), direction); + if ((direction instanceof In || direction instanceof Out) && value == Value.NotNull) { + annotations.add(annKey, new AnnotationData("org.jetbrains.annotations.NotNull", "")); + } + else if (direction instanceof InOut) { + StringBuilder sb = contracts.get(annKey); + if (sb == null) { + sb = new StringBuilder("\""); + contracts.put(annKey, sb); + } else { + sb.append(';'); + } + contractElement(sb, key.method, (InOut)direction, value); + } + } + for (Map.Entry contract : contracts.entrySet()) { + annotations.add(contract.getKey(), new AnnotationData("org.jetbrains.annotations.Contract", contract.getValue().append('"').toString())); + } + return annotations; + } + + static String contractValueString(Value v) { + switch (v) { + case False: return "false"; + case True: return "true"; + case NotNull: return "!null"; + case Null: return "null"; + default: return "_"; + } + } + + static String contractElement(StringBuilder sb, Method method, InOut inOut, Value value) { + int arity = Type.getArgumentTypes(method.methodDesc).length; + for (int i = 0; i < arity; i++) { + Value currentValue = Value.Top; + if (i == inOut.paramIndex) { + currentValue = inOut.inValue; + } + if (i > 0) { + sb.append(','); + } + sb.append(contractValueString(currentValue)); + } + sb.append("->"); + sb.append(contractValueString(value)); + return sb.toString(); + } + + public static String annotationKey(Method method, MethodExtra extra, Direction dir) { + String annPrefix = annotationKey(method, extra); + if (dir instanceof In) { + return annPrefix + " " + ((In)dir).paramIndex; + } else { + return annPrefix; + } + } + + public static String annotationKey(Method method, MethodExtra extra) { + if ("".equals(method.methodName)) { + return canonical(method.internalClassName) + " " + + simpleName(method.internalClassName) + + parameters(method, extra); + } else { + return canonical(method.internalClassName) + " " + + returnType(method, extra) + " " + + method.methodName + + parameters(method, extra); + } + } + + private static String returnType(Method method, MethodExtra extra) { + if (extra.signature != null) { + final StringBuilder sb = new StringBuilder(); + new SignatureReader(extra.signature).accept(new SignatureVisitor(Opcodes.ASM5) { + @Override + public SignatureVisitor visitReturnType() { + return new GenericTypeRenderer(sb); + } + }); + return sb.toString(); + } + else { + return canonical(Type.getReturnType(method.methodDesc).getClassName()); + } + } + + public static String canonical(String internalName) { + return internalName.replace('/', '.').replace('$', '.'); + } + + private static String simpleName(String internalName) { + String cn = canonical(internalName); + int lastDotIndex = cn.lastIndexOf('.'); + if (lastDotIndex == -1) { + return cn; + } else { + return cn.substring(lastDotIndex + 1); + } + } + + private static String parameters(Method method, MethodExtra extra) { + String result; + if (extra.signature != null) { + GenericMethodParametersRenderer renderer = new GenericMethodParametersRenderer(); + new SignatureReader(extra.signature).accept(renderer); + result = renderer.parameters(); + } + else { + Type[] argTypes = Type.getArgumentTypes(method.methodDesc); + StringBuilder sb = new StringBuilder("("); + boolean notFirst = false; + for (Type argType : argTypes) { + if (notFirst) { + sb.append(", "); + } + else { + notFirst = true; + } + sb.append(canonical(argType.getClassName())); + } + sb.append(')'); + result = sb.toString(); + } + if ((extra.access & Opcodes.ACC_VARARGS) != 0) { + return result.replace("[])", "...)"); + } else { + return result; + } + } + + static class GenericMethodParametersRenderer extends SignatureVisitor { + + private StringBuilder sb = new StringBuilder("("); + private boolean first = true; + public GenericMethodParametersRenderer() { + super(Opcodes.ASM5); + } + public String parameters() { + return sb.append(')').toString(); + } + + @Override + public SignatureVisitor visitParameterType() { + if (first) { + first = false; + } + else { + sb.append(", "); + } + return new GenericTypeRenderer(sb); + } + } + + static class GenericTypeRenderer extends SignatureVisitor { + + final StringBuilder sb; + private boolean angleBracketOpen = false; + + public GenericTypeRenderer(StringBuilder sb) { + super(Opcodes.ASM5); + this.sb = sb; + } + + private boolean openAngleBracket() { + if (angleBracketOpen) { + return false; + } else { + angleBracketOpen = true; + sb.append('<'); + return true; + } + } + + private void closeAngleBracket() { + if (angleBracketOpen) { + angleBracketOpen = false; + sb.append('>'); + } + } + + private void beforeTypeArgument() { + boolean first = openAngleBracket(); + if (!first) { + sb.append(','); + } + } + + protected void endType() {} + + @Override + public void visitBaseType(char descriptor) { + switch (descriptor) { + case 'V': + sb.append("void"); + break; + case 'B': + sb.append("byte"); + break; + case 'J': + sb.append("long"); + break; + case 'Z': + sb.append("boolean"); + break; + case 'I': + sb.append("int"); + break; + case 'S': + sb.append("short"); + break; + case 'C': + sb.append("char"); + break; + case 'F': + sb.append("float"); + break; + case 'D': + sb.append("double"); + break; + } + endType(); + } + + @Override + public void visitTypeVariable(String name) { + sb.append(name); + endType(); + } + + @Override + public SignatureVisitor visitArrayType() { + return new GenericTypeRenderer(sb) { + @Override + protected void endType() { + sb.append("[]"); + } + }; + } + + @Override + public void visitClassType(String name) { + sb.append(canonical(name)); + } + + @Override + public void visitInnerClassType(String name) { + closeAngleBracket(); + sb.append('.').append(canonical(name)); + } + + @Override + public void visitTypeArgument() { + beforeTypeArgument(); + sb.append('?'); + } + + @Override + public SignatureVisitor visitTypeArgument(char wildcard) { + beforeTypeArgument(); + switch (wildcard) { + case SignatureVisitor.EXTENDS: + sb.append("? extends "); + break; + case SignatureVisitor.SUPER: + sb.append("? super "); + break; + case SignatureVisitor.INSTANCEOF: + break; + } + return new GenericTypeRenderer(sb); + } + + @Override + public void visitEnd() { + closeAngleBracket(); + endType(); + } + + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java index e5bc58f2be8a..69e2f889ab26 100644 --- a/java/java-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java @@ -15,6 +15,7 @@ */ package com.intellij.codeInsight; +import com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisHandler; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiModifierListOwner; import org.jetbrains.annotations.NotNull; @@ -24,12 +25,12 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager { @Nullable @Override public PsiAnnotation findInferredAnnotation(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN) { - return null; + return listOwner.getProject().getComponent(BytecodeAnalysisHandler.class).findInferredAnnotation(listOwner, annotationFQN); } @Nullable @Override public PsiAnnotation[] findInferredAnnotations(@NotNull PsiModifierListOwner listOwner) { - return new PsiAnnotation[0]; + return listOwner.getProject().getComponent(BytecodeAnalysisHandler.class).findInferredAnnotations(listOwner); } } diff --git a/platform/platform-resources/src/componentSets/Lang.xml b/platform/platform-resources/src/componentSets/Lang.xml index bd3879e67a90..456b12a4080e 100644 --- a/platform/platform-resources/src/componentSets/Lang.xml +++ b/platform/platform-resources/src/componentSets/Lang.xml @@ -225,6 +225,10 @@ com.intellij.ide.GeneratedSourceFileChangeTracker com.intellij.ide.GeneratedSourceFileChangeTrackerImpl + + + com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisHandler +