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, Value>)res).value : ((Pending, Value>)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 extends BasicValue> 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 extends BasicValue> 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