From c4ccab76d79465446e62fd15b4db82372f87e111 Mon Sep 17 00:00:00 2001 From: Ilya Klyuchnikov Date: Thu, 14 Aug 2014 12:35:02 +0400 Subject: [PATCH] lite analyzers for methods without JSR instructions --- .../bytecodeAnalysis/ClassDataIndexer.java | 30 ++- .../asm/ControlFlowGraph.java | 54 ++++- .../asm/FramelessAnalyzer.java | 16 +- .../asm/LeakingParametersAnalysis.java | 12 +- .../bytecodeAnalysis/asm/LiteAnalyzer.java | 189 ++++++++++++++++++ .../asm/LiteFramelessAnalyzer.java | 56 ++++++ .../BytecodeAnalysisTest.java | 2 +- 7 files changed, 335 insertions(+), 24 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteAnalyzer.java create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteFramelessAnalyzer.java 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 faa1f67cc35e..2d621a708313 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 @@ -103,15 +103,25 @@ public class ClassDataIndexer implements DataIndexer public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { final MethodNode node = new MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions); return new MethodVisitor(Opcodes.ASM5, node) { + private boolean jsr; + + @Override + public void visitJumpInsn(int opcode, Label label) { + if (opcode == Opcodes.JSR) { + jsr = true; + } + super.visitJumpInsn(opcode, label); + } + @Override public void visitEnd() { super.visitEnd(); - processMethod(node); + processMethod(node, jsr); } }; } - private void processMethod(final MethodNode methodNode) { + private void processMethod(final MethodNode methodNode, boolean jsr) { ProgressManager.checkCanceled(); final Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc); final Type resultType = Type.getReturnType(methodNode.desc); @@ -127,7 +137,7 @@ public class ClassDataIndexer implements DataIndexer final boolean stable = stableClass || (methodNode.access & STABLE_FLAGS) != 0 || "".equals(methodNode.name); try { - final ControlFlowGraph graph = ControlFlowGraph.build(className, methodNode); + final ControlFlowGraph graph = ControlFlowGraph.build(className, methodNode, jsr); if (graph.transitions.length > 0) { final DFSTree dfs = DFSTree.build(graph.transitions, graph.edgeCount); boolean complex = !dfs.back.isEmpty(); @@ -143,7 +153,7 @@ public class ClassDataIndexer implements DataIndexer if (complex) { RichControlFlow richControlFlow = new RichControlFlow(graph, dfs); if (richControlFlow.reducible()) { - processBranchingMethod(method, methodNode, richControlFlow, argumentTypes, isReferenceResult, isInterestingResult, stable); + processBranchingMethod(method, methodNode, richControlFlow, argumentTypes, isReferenceResult, isInterestingResult, stable, jsr); return; } LOG.debug(method + ": CFG is not reducible"); @@ -172,7 +182,8 @@ public class ClassDataIndexer implements DataIndexer Type[] argumentTypes, boolean isReferenceResult, boolean isInterestingResult, - final boolean stable) throws AnalyzerException { + final boolean stable, + boolean jsr) throws AnalyzerException { boolean maybeLeakingParameter = isInterestingResult; for (Type argType : argumentTypes) { @@ -183,7 +194,7 @@ public class ClassDataIndexer implements DataIndexer } final Pair[]> leakingParametersAndFrames = - maybeLeakingParameter ? leakingParametersAndFrames(method, methodNode, argumentTypes) : null; + maybeLeakingParameter ? leakingParametersAndFrames(method, methodNode, argumentTypes, jsr) : null; boolean[] leakingParameters = leakingParametersAndFrames != null ? leakingParametersAndFrames.first : null; @@ -339,11 +350,12 @@ public class ClassDataIndexer implements DataIndexer private Pair[]> leakingParametersAndFrames(Method method, MethodNode methodNode, - Type[] argumentTypes) + Type[] argumentTypes, + boolean jsr) throws AnalyzerException { return argumentTypes.length < 32 ? - LeakingParametersAnalysis.fastLeakingParameters(method.internalClassName, methodNode) : - LeakingParametersAnalysis.leakingParameters(method.internalClassName, methodNode); + LeakingParametersAnalysis.fastLeakingParameters(method.internalClassName, methodNode, jsr) : + LeakingParametersAnalysis.leakingParameters(method.internalClassName, methodNode, jsr); } }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/ControlFlowGraph.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/ControlFlowGraph.java index 3ed4edbcace4..40a730190d2e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/ControlFlowGraph.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/ControlFlowGraph.java @@ -67,8 +67,8 @@ public final class ControlFlowGraph { this.errorTransitions = errorTransitions; } - public static ControlFlowGraph build(String className, MethodNode methodNode) throws AnalyzerException { - return new ControlFlowBuilder(className, methodNode).buildCFG(); + public static ControlFlowGraph build(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException { + return jsr ? new ControlFlowBuilder(className, methodNode).buildCFG() : new LiteControlFlowBuilder(className, methodNode).buildCFG(); } } @@ -122,3 +122,53 @@ final class ControlFlowBuilder extends FramelessAnalyzer { } } +final class LiteControlFlowBuilder extends LiteFramelessAnalyzer { + final String className; + final MethodNode methodNode; + final TIntArrayList[] transitions; + final Set errorTransitions; + private final boolean[] errors; + private int edgeCount; + + LiteControlFlowBuilder(String className, MethodNode methodNode) { + this.className = className; + this.methodNode = methodNode; + transitions = new TIntArrayList[methodNode.instructions.size()]; + errors = new boolean[methodNode.instructions.size()]; + for (int i = 0; i < transitions.length; i++) { + transitions[i] = new TIntArrayList(); + } + errorTransitions = new HashSet(); + } + + final ControlFlowGraph buildCFG() throws AnalyzerException { + if ((methodNode.access & (ACC_ABSTRACT | ACC_NATIVE)) == 0) { + analyze(methodNode); + } + int[][] resultTransitions = new int[transitions.length][]; + for (int i = 0; i < resultTransitions.length; i++) { + resultTransitions[i] = transitions[i].toNativeArray(); + } + return new ControlFlowGraph(className, methodNode, resultTransitions, edgeCount, errors, errorTransitions); + } + + @Override + protected final void newControlFlowEdge(int insn, int successor) { + if (!transitions[insn].contains(successor)) { + transitions[insn].add(successor); + edgeCount++; + } + } + + @Override + protected final boolean newControlFlowExceptionEdge(int insn, int successor) { + if (!transitions[insn].contains(successor)) { + transitions[insn].add(successor); + edgeCount++; + errorTransitions.add(new Edge(insn, successor)); + errors[successor] = true; + } + return true; + } +} + diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java index d136e4878547..5804723e9ec1 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java @@ -26,7 +26,7 @@ import java.util.List; import java.util.Map; /** - * Specialized lite version of {@link org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer}. + * Specialized version of {@link org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer}. * Calculation of fix-point of frames is removed, since frames are not needed to build control flow graph. * So, the main point here is handling of subroutines (jsr) and try-catch-finally blocks. */ @@ -81,10 +81,10 @@ public class FramelessAnalyzer implements Opcodes { private InsnList insns; private List[] handlers; private Subroutine[] subroutines; - private boolean[] wasQueued; - private boolean[] queued; - private int[] queue; - private int top; + protected boolean[] wasQueued; + protected boolean[] queued; + protected int[] queue; + protected int top; public void analyze(final MethodNode m) throws AnalyzerException { n = m.instructions.size(); @@ -240,7 +240,7 @@ public class FramelessAnalyzer implements Opcodes { } } - private void findSubroutine(int insn, final Subroutine sub, + protected void findSubroutine(int insn, final Subroutine sub, final List calls) throws AnalyzerException { while (true) { if (insn < 0 || insn >= n) { @@ -317,7 +317,7 @@ public class FramelessAnalyzer implements Opcodes { // ------------------------------------------------------------------------- - private void merge(final int insn, @Nullable final Subroutine subroutine) throws AnalyzerException { + protected void merge(final int insn, @Nullable final Subroutine subroutine) throws AnalyzerException { Subroutine oldSubroutine = subroutines[insn]; boolean changes = false; @@ -342,7 +342,7 @@ public class FramelessAnalyzer implements Opcodes { } } - private void merge(final int insn, final Subroutine subroutineBeforeJSR, final boolean[] access) throws AnalyzerException { + protected void merge(final int insn, final Subroutine subroutineBeforeJSR, final boolean[] access) throws AnalyzerException { Subroutine oldSubroutine = subroutines[insn]; boolean changes = false; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LeakingParametersAnalysis.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LeakingParametersAnalysis.java index f617620ef127..2d3206babf99 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LeakingParametersAnalysis.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LeakingParametersAnalysis.java @@ -30,8 +30,10 @@ import static org.jetbrains.org.objectweb.asm.Opcodes.*; * @author lambdamix */ public class LeakingParametersAnalysis { - public static Pair[]> leakingParameters(String className, MethodNode methodNode) throws AnalyzerException { - Frame[] frames = new Analyzer(new ParametersUsage(methodNode)).analyze(className, methodNode); + public static Pair[]> leakingParameters(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException { + Frame[] frames = jsr ? + new Analyzer(new ParametersUsage(methodNode)).analyze(className, methodNode) : + new LiteAnalyzer(new ParametersUsage(methodNode)).analyze(className, methodNode); InsnList insns = methodNode.instructions; LeakingParametersCollector collector = new LeakingParametersCollector(methodNode); for (int i = 0; i < frames.length; i++) { @@ -51,9 +53,11 @@ public class LeakingParametersAnalysis { return Pair.create(collector.leaking, (Frame[])(Frame[])frames); } - public static Pair[]> fastLeakingParameters(String className, MethodNode methodNode) throws AnalyzerException { + public static Pair[]> fastLeakingParameters(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException { IParametersUsage parametersUsage = new IParametersUsage(methodNode); - Frame[] frames = new Analyzer(parametersUsage).analyze(className, methodNode); + Frame[] frames = jsr ? + new Analyzer(parametersUsage).analyze(className, methodNode) : + new LiteAnalyzer(parametersUsage).analyze(className, methodNode); int leakingMask = parametersUsage.leaking; boolean[] result = new boolean[parametersUsage.arity]; for (int i = 0; i < result.length; i++) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteAnalyzer.java new file mode 100644 index 000000000000..22961208076b --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteAnalyzer.java @@ -0,0 +1,189 @@ +/* + * 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.asm; + +import org.jetbrains.org.objectweb.asm.Opcodes; +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.Frame; +import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter; +import org.jetbrains.org.objectweb.asm.tree.analysis.Value; + +import java.util.ArrayList; +import java.util.List; + +/** + * Specialized lite version of {@link org.objectweb.asm.tree.analysis.Analyzer}. + * No processing of Subroutines. May be used for methods without JSR/RET instructions. + * + * @author lambdamix + */ +public class LiteAnalyzer implements Opcodes { + + private final Interpreter interpreter; + + private Frame[] frames; + + private boolean[] queued; + + private int[] queue; + + private int top; + + public LiteAnalyzer(final Interpreter interpreter) { + this.interpreter = interpreter; + } + + public Frame[] analyze(final String owner, final MethodNode m) throws AnalyzerException { + if ((m.access & (ACC_ABSTRACT | ACC_NATIVE)) != 0 || m.instructions.size() == 0) { + frames = (Frame[]) new Frame[0]; + return frames; + } + int n = m.instructions.size(); + InsnList insns = m.instructions; + List[] handlers = (List[]) new List[n]; + frames = (Frame[]) new Frame[n]; + queued = new boolean[n]; + queue = new int[n]; + top = 0; + + // computes exception handlers for each instruction + for (int i = 0; i < m.tryCatchBlocks.size(); ++i) { + TryCatchBlockNode tcb = m.tryCatchBlocks.get(i); + int begin = insns.indexOf(tcb.start); + int end = insns.indexOf(tcb.end); + for (int j = begin; j < end; ++j) { + List insnHandlers = handlers[j]; + if (insnHandlers == null) { + insnHandlers = new ArrayList(); + handlers[j] = insnHandlers; + } + insnHandlers.add(tcb); + } + } + + // initializes the data structures for the control flow analysis + Frame current = new Frame(m.maxLocals, m.maxStack); + Frame handler = new Frame(m.maxLocals, m.maxStack); + current.setReturn(interpreter.newValue(Type.getReturnType(m.desc))); + Type[] args = Type.getArgumentTypes(m.desc); + int local = 0; + if ((m.access & ACC_STATIC) == 0) { + Type ctype = Type.getObjectType(owner); + current.setLocal(local++, interpreter.newValue(ctype)); + } + for (int i = 0; i < args.length; ++i) { + current.setLocal(local++, interpreter.newValue(args[i])); + if (args[i].getSize() == 2) { + current.setLocal(local++, interpreter.newValue(null)); + } + } + while (local < m.maxLocals) { + current.setLocal(local++, interpreter.newValue(null)); + } + merge(0, current); + + // control flow analysis + while (top > 0) { + int insn = queue[--top]; + Frame f = frames[insn]; + queued[insn] = false; + + AbstractInsnNode insnNode = null; + try { + insnNode = m.instructions.get(insn); + int insnOpcode = insnNode.getOpcode(); + int insnType = insnNode.getType(); + + if (insnType == AbstractInsnNode.LABEL || insnType == AbstractInsnNode.LINE || insnType == AbstractInsnNode.FRAME) { + merge(insn + 1, f); + } else { + current.init(f).execute(insnNode, interpreter); + + if (insnNode instanceof JumpInsnNode) { + JumpInsnNode j = (JumpInsnNode) insnNode; + if (insnOpcode != GOTO && insnOpcode != JSR) { + merge(insn + 1, current); + } + int jump = insns.indexOf(j.label); + merge(jump, current); + } else if (insnNode instanceof LookupSwitchInsnNode) { + LookupSwitchInsnNode lsi = (LookupSwitchInsnNode) insnNode; + int jump = insns.indexOf(lsi.dflt); + merge(jump, current); + for (int j = 0; j < lsi.labels.size(); ++j) { + LabelNode label = lsi.labels.get(j); + jump = insns.indexOf(label); + merge(jump, current); + } + } else if (insnNode instanceof TableSwitchInsnNode) { + TableSwitchInsnNode tsi = (TableSwitchInsnNode) insnNode; + int jump = insns.indexOf(tsi.dflt); + merge(jump, current); + for (int j = 0; j < tsi.labels.size(); ++j) { + LabelNode label = tsi.labels.get(j); + jump = insns.indexOf(label); + merge(jump, current); + } + } else if (insnOpcode != ATHROW && (insnOpcode < IRETURN || insnOpcode > RETURN)) { + merge(insn + 1, current); + } + } + + List insnHandlers = handlers[insn]; + if (insnHandlers != null) { + for (int i = 0; i < insnHandlers.size(); ++i) { + TryCatchBlockNode tcb = insnHandlers.get(i); + int jump = insns.indexOf(tcb.handler); + handler.init(f); + handler.clearStack(); + handler.push(interpreter.newValue(ASMUtils.THROWABLE_TYPE)); + merge(jump, handler); + } + } + } catch (AnalyzerException e) { + throw new AnalyzerException(e.node, "Error at instruction " + insn + ": " + e.getMessage(), e); + } catch (Exception e) { + throw new AnalyzerException(insnNode, "Error at instruction " + insn + ": " + e.getMessage(), e); + } + } + + return frames; + } + + public Frame[] getFrames() { + return frames; + } + + private void merge(final int insn, final Frame frame) throws AnalyzerException { + Frame oldFrame = frames[insn]; + boolean changes; + + if (oldFrame == null) { + frames[insn] = new Frame(frame); + changes = true; + } else { + changes = oldFrame.merge(frame, interpreter); + } + if (changes && !queued[insn]) { + queued[insn] = true; + queue[top++] = insn; + } + } + +} + diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteFramelessAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteFramelessAnalyzer.java new file mode 100644 index 000000000000..0a9721018862 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/asm/LiteFramelessAnalyzer.java @@ -0,0 +1,56 @@ +/* + * 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.asm; + +import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode; +import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException; + +import java.util.List; + +/** + * Specialized lite version of {@link FramelessAnalyzer}. + * No processing of Subroutines. May be used for methods without JSR/RET instructions. + * + * @author lambdamix + */ +public class LiteFramelessAnalyzer extends FramelessAnalyzer { + + @Override + protected void findSubroutine(int insn, FramelessAnalyzer.Subroutine sub, List calls) throws AnalyzerException { + } + + @Override + protected void merge(final int insn, final FramelessAnalyzer.Subroutine subroutine) throws AnalyzerException { + if (!wasQueued[insn]) { + wasQueued[insn] = true; + if (!queued[insn]) { + queued[insn] = true; + queue[top++] = insn; + } + } + } + + @Override + protected void merge(final int insn, final FramelessAnalyzer.Subroutine subroutineBeforeJSR, final boolean[] access) throws AnalyzerException { + if (!wasQueued[insn]) { + wasQueued[insn] = true; + if (!queued[insn]) { + queued[insn] = true; + queue[top++] = insn; + } + } + } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisTest.java index 26095f510fec..592b231f5087 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/bytecodeAnalysis/BytecodeAnalysisTest.java @@ -95,7 +95,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase { public void visitEnd() { super.visitEnd(); try { - map.put(method, LeakingParametersAnalysis.leakingParameters(classReader.getClassName(), node).first); + map.put(method, LeakingParametersAnalysis.leakingParameters(classReader.getClassName(), node, false).first); } catch (AnalyzerException ignore) {} }