diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java index 2b6589177318..00035f38a964 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java @@ -71,6 +71,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator allEquations = processClass(new ClassReader(file.contentsToByteArray(false)), file.getPresentableUrl()); + allEquations = solvePartially(allEquations); allEquations.forEach((methodKey, equations) -> map.put(methodKey.method.hashed(md), hash(equations, md))); } catch (ProcessCanceledException e) { @@ -85,6 +86,20 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator solvePartially(Map map) { + PuritySolver solver = new PuritySolver(); + EntryStream.of(map).mapToKey((key, eqs) -> new EKey(key.method, Pure, eqs.stable, false)) + .flatMapValues(eqs -> eqs.results.stream().map(drp -> drp.result)) + .selectValues(Effects.class) + .forKeyValue(solver::addEquation); + Map solved = solver.solve(); + Map partiallySolvedPurity = + StreamEx.of(solved, solver.pending).flatMapToEntry(Function.identity()).removeValues(Effects::isTop).toMap(); + return EntryStream.of(map) + .mapToValue((key, eqs) -> eqs.update(Pure, partiallySolvedPurity.get(new EKey(key.method, Pure, eqs.stable, false)))) + .toMap(); + } + private static Equations hash(Equations equations, MessageDigest md) { return new Equations(ContainerUtil.map(equations.results, drp -> hash(drp, md)), equations.stable); } 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 index ffeb0e6a8a1b..333aaf086e42 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java @@ -15,12 +15,11 @@ */ package com.intellij.codeInspection.bytecodeAnalysis; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.stream.Stream; /** @@ -129,6 +128,15 @@ class Equations { public int hashCode() { return 31 * results.hashCode() + (stable ? 1 : 0); } + + @NotNull + Equations update(Direction direction, Effects newResult) { + List newPairs = StreamEx.of(this.results) + .map(drp -> drp.updateForDirection(direction, newResult)) + .nonNull() + .toList(); + return new Equations(newPairs, this.stable); + } } class DirectionResultPair { @@ -159,6 +167,16 @@ class DirectionResultPair { public String toString() { return Direction.fromInt(directionKey) + "->" + result; } + + @Nullable + DirectionResultPair updateForDirection(Direction direction, Result newResult) { + if (this.directionKey == direction.asInt()) { + return newResult == null ? null : new DirectionResultPair(direction.asInt(), newResult); + } + else { + return this; + } + } } interface Result {} @@ -227,6 +245,8 @@ final class Pending implements Result { } final class Effects implements Result { + static final Set TOP_EFFECTS = Collections.singleton(EffectQuantum.TopEffectQuantum); + @NotNull final DataValue returnValue; @NotNull final Set effects; @@ -239,6 +259,10 @@ final class Effects implements Result { return Stream.concat(returnValue.dependencies(), effects.stream().flatMap(EffectQuantum::dependencies)); } + public boolean isTop() { + return returnValue == DataValue.UnknownDataValue1 && effects.equals(TOP_EFFECTS); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -254,6 +278,7 @@ final class Effects implements Result { @Override public String toString() { - return "Effects[" + effects.size() + "|" + returnValue + "]"; + Object effectsPresentation = effects.isEmpty() ? "Pure" : effects.size() == 1 ? effects.iterator().next() : effects.size(); + return "Effects[" + effectsPresentation + "|" + returnValue + "]"; } } \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/PurityAnalysis.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/PurityAnalysis.java index 7575ede44661..5ea806eb3fc8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/PurityAnalysis.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/PurityAnalysis.java @@ -66,7 +66,7 @@ public class PurityAnalysis { for (EffectQuantum effectQuantum : quanta) { if (effectQuantum != null) { if (effectQuantum == EffectQuantum.TopEffectQuantum) { - return returnValue == DataValue.UnknownDataValue1 ? null : new Equation(key, new Effects(returnValue, PuritySolver.TOP_EFFECTS)); + return returnValue == DataValue.UnknownDataValue1 ? null : new Equation(key, new Effects(returnValue, Effects.TOP_EFFECTS)); } effects.add(effectQuantum); } @@ -559,11 +559,10 @@ class DataInterpreter extends Interpreter { } final class PuritySolver { - static final Set TOP_EFFECTS = Collections.singleton(EffectQuantum.TopEffectQuantum); private HashMap solved = new HashMap<>(); private HashMap> dependencies = new HashMap<>(); private final Stack moving = new Stack<>(); - private HashMap pending = new HashMap<>(); + HashMap pending = new HashMap<>(); void addEquation(EKey key, Effects effects) { Set depKeys = effects.dependencies().collect(Collectors.toSet()); @@ -593,7 +592,7 @@ final class PuritySolver { } else { propagateKeys = new EKey[]{key.mkStable(), key}; - propagateEffects = new Effects[]{effects, new Effects(DataValue.UnknownDataValue1, TOP_EFFECTS)}; + propagateEffects = new Effects[]{effects, new Effects(DataValue.UnknownDataValue1, Effects.TOP_EFFECTS)}; } for (int i = 0; i < propagateKeys.length; i++) { EKey pKey = propagateKeys[i]; @@ -615,7 +614,7 @@ final class PuritySolver { EffectQuantum.CallQuantum call = substitute((EffectQuantum.CallQuantum)dEffect, pKey, pEffects); if (call.key.equals(pKey)) { delta = substitute(pEffects, call.data, call.isStatic); - if(delta.equals(TOP_EFFECTS)) { + if(delta.equals(Effects.TOP_EFFECTS)) { newEffects = delta; break; } @@ -630,7 +629,7 @@ final class PuritySolver { EffectQuantum.ReturnChangeQuantum retChange = (EffectQuantum.ReturnChangeQuantum)dEffect; if (retChange.key.equals(pKey)) { if(pEffects.returnValue != DataValue.LocalDataValue) { - newEffects = delta = TOP_EFFECTS; + newEffects = delta = Effects.TOP_EFFECTS; break; } continue; @@ -639,8 +638,8 @@ final class PuritySolver { newEffects.add(dEffect); } - if (TOP_EFFECTS.equals(delta) && returnValue.equals(DataValue.UnknownDataValue1)) { - solved.put(dKey, new Effects(returnValue, TOP_EFFECTS)); + if (Effects.TOP_EFFECTS.equals(delta) && returnValue.equals(DataValue.UnknownDataValue1)) { + solved.put(dKey, new Effects(returnValue, Effects.TOP_EFFECTS)); moving.push(dKey); } else { @@ -682,7 +681,7 @@ final class PuritySolver { } private static Set substitute(Effects effects, DataValue[] data, boolean isStatic) { - if (effects.effects.isEmpty() || TOP_EFFECTS.equals(effects.effects)) { + if (effects.effects.isEmpty() || Effects.TOP_EFFECTS.equals(effects.effects)) { return effects.effects; } Set newEffects = new HashSet<>(effects.effects.size()); @@ -710,7 +709,7 @@ final class PuritySolver { newEffects.add(new EffectQuantum.ReturnChangeQuantum(((DataValue.ReturnDataValue)arg).key)); continue; } - return TOP_EFFECTS; + return Effects.TOP_EFFECTS; } return newEffects; }