From 755dcc6044056171de7f66b41a0cae2672fb6785 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 8 Jun 2017 15:11:19 +0700 Subject: [PATCH] BytecodeAnalysis: partial purity solution before serialization All purity dependencies within current class are solved before serialization. This reduces index space and solve complexity after reading (in particular for some methods complete solution could be found analyzing their declaring class only). Also this may reduce the necessity to load some dependencies. E.g. if X::a calls X::b and Y::c (in any order) and X::b is not pure then X::a is solved to be not pure and class Y could be not analyzed at all. --- .../bytecodeAnalysis/ClassDataIndexer.java | 15 ++++++++ .../codeInspection/bytecodeAnalysis/Data.java | 35 ++++++++++++++++--- .../bytecodeAnalysis/PurityAnalysis.java | 19 +++++----- 3 files changed, 54 insertions(+), 15 deletions(-) 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; }