mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
refactoring: moving LeakingParametersAnalysis into asm package
This commit is contained in:
+4
-1
@@ -17,6 +17,7 @@ package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.ASMUtils;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.ControlFlowGraph;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.LeakingParametersAnalysis;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.OriginsAnalysis;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
@@ -340,7 +341,9 @@ public class ClassDataIndexer implements DataIndexer<HKey, HResult, FileContent>
|
||||
MethodNode methodNode,
|
||||
Type[] argumentTypes)
|
||||
throws AnalyzerException {
|
||||
return (argumentTypes.length < 32 ? cfg.fastLeakingParameters(method.internalClassName, methodNode) : cfg.leakingParameters(method.internalClassName, methodNode));
|
||||
return argumentTypes.length < 32 ?
|
||||
LeakingParametersAnalysis.fastLeakingParameters(method.internalClassName, methodNode) :
|
||||
LeakingParametersAnalysis.leakingParameters(method.internalClassName, methodNode);
|
||||
}
|
||||
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
|
||||
|
||||
+2
-541
@@ -15,66 +15,23 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.ASMUtils;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.ControlFlowGraph;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.ControlFlowGraph.Edge;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.FramelessAnalyzer;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import gnu.trove.TIntArrayList;
|
||||
import gnu.trove.TIntHashSet;
|
||||
import gnu.trove.TIntIterator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.org.objectweb.asm.Type;
|
||||
import org.jetbrains.org.objectweb.asm.tree.*;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.*;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.Value;
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
|
||||
|
||||
final class cfg {
|
||||
static ControlFlowGraph buildControlFlowGraph(String className, MethodNode methodNode) throws AnalyzerException {
|
||||
return new ControlFlowBuilder(className, methodNode).buildCFG();
|
||||
}
|
||||
|
||||
static Pair<boolean[], Frame<Value>[]> leakingParameters(String className, MethodNode methodNode) throws AnalyzerException {
|
||||
Frame<ParamsValue>[] frames = new Analyzer<ParamsValue>(new ParametersUsage(methodNode)).analyze(className, methodNode);
|
||||
InsnList insns = methodNode.instructions;
|
||||
LeakingParametersCollector collector = new LeakingParametersCollector(methodNode);
|
||||
for (int i = 0; i < frames.length; i++) {
|
||||
AbstractInsnNode insnNode = insns.get(i);
|
||||
Frame<ParamsValue> frame = frames[i];
|
||||
if (frame != null) {
|
||||
switch (insnNode.getType()) {
|
||||
case AbstractInsnNode.LABEL:
|
||||
case AbstractInsnNode.LINE:
|
||||
case AbstractInsnNode.FRAME:
|
||||
break;
|
||||
default:
|
||||
new Frame<ParamsValue>(frame).execute(insnNode, collector);
|
||||
}
|
||||
}
|
||||
}
|
||||
// to hack type checker
|
||||
Frame<?>[] frames1 = frames;
|
||||
return new Pair<boolean[], Frame<Value>[]>(collector.leaking, (Frame<Value>[]) frames1);
|
||||
}
|
||||
|
||||
static Pair<boolean[], Frame<Value>[]> fastLeakingParameters(String className, MethodNode methodNode) throws AnalyzerException {
|
||||
IParametersUsage parametersUsage = new IParametersUsage(methodNode);
|
||||
Frame<?>[] frames = new Analyzer<IParamsValue>(parametersUsage).analyze(className, methodNode);
|
||||
int leakingMask = parametersUsage.leaking;
|
||||
boolean[] result = new boolean[parametersUsage.arity];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = (leakingMask & (1 << i)) != 0;
|
||||
}
|
||||
return new Pair<boolean[], Frame<Value>[]>(result, (Frame<Value>[]) frames);
|
||||
}
|
||||
|
||||
// Graphs: Theory and Algorithms. by K. Thulasiraman , M. N. S. Swamy (1992)
|
||||
// 11.7.2 DFS of a directed graph
|
||||
static DFSTree buildDFSTree(int[][] transitions, int edgeCount) {
|
||||
@@ -310,499 +267,3 @@ final class DFSTree {
|
||||
}
|
||||
}
|
||||
|
||||
final class ParamsValue implements Value {
|
||||
@NotNull final boolean[] params;
|
||||
final int size;
|
||||
|
||||
ParamsValue(@NotNull boolean[] params, int size) {
|
||||
this.params = params;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
ParamsValue that = (ParamsValue)o;
|
||||
return (this.size == that.size && Arrays.equals(this.params, that.params));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * Arrays.hashCode(params) + size;
|
||||
}
|
||||
}
|
||||
|
||||
// specialized version
|
||||
final class IParamsValue implements Value {
|
||||
final int params;
|
||||
final int size;
|
||||
|
||||
IParamsValue(int params, int size) {
|
||||
this.params = params;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
IParamsValue that = (IParamsValue)o;
|
||||
return (this.size == that.size && this.params == that.params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * params + size;
|
||||
}
|
||||
}
|
||||
|
||||
class ParametersUsage extends Interpreter<ParamsValue> {
|
||||
final ParamsValue val1;
|
||||
final ParamsValue val2;
|
||||
int called = -1;
|
||||
final int rangeStart;
|
||||
final int rangeEnd;
|
||||
final int arity;
|
||||
final int shift;
|
||||
|
||||
ParametersUsage(MethodNode methodNode) {
|
||||
super(ASM5);
|
||||
arity = Type.getArgumentTypes(methodNode.desc).length;
|
||||
boolean[] emptyParams = new boolean[arity];
|
||||
val1 = new ParamsValue(emptyParams, 1);
|
||||
val2 = new ParamsValue(emptyParams, 2);
|
||||
|
||||
shift = (methodNode.access & ACC_STATIC) == 0 ? 2 : 1;
|
||||
rangeStart = shift;
|
||||
rangeEnd = arity + shift;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue newValue(Type type) {
|
||||
if (type == null) return val1;
|
||||
called++;
|
||||
if (type == Type.VOID_TYPE) return null;
|
||||
if (called < rangeEnd && rangeStart <= called && (ASMUtils.isReferenceType(type) || ASMUtils.isBooleanType(type))) {
|
||||
boolean[] params = new boolean[arity];
|
||||
params[called - shift] = true;
|
||||
return type.getSize() == 1 ? new ParamsValue(params, 1) : new ParamsValue(params, 2);
|
||||
}
|
||||
else {
|
||||
return type.getSize() == 1 ? val1 : val2;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue newOperation(final AbstractInsnNode insn) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case LCONST_0:
|
||||
case LCONST_1:
|
||||
case DCONST_0:
|
||||
case DCONST_1:
|
||||
size = 2;
|
||||
break;
|
||||
case LDC:
|
||||
Object cst = ((LdcInsnNode) insn).cst;
|
||||
size = cst instanceof Long || cst instanceof Double ? 2 : 1;
|
||||
break;
|
||||
case GETSTATIC:
|
||||
size = Type.getType(((FieldInsnNode) insn).desc).getSize();
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue copyOperation(AbstractInsnNode insn, ParamsValue value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue unaryOperation(AbstractInsnNode insn, ParamsValue value) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case CHECKCAST:
|
||||
return value;
|
||||
case LNEG:
|
||||
case DNEG:
|
||||
case I2L:
|
||||
case I2D:
|
||||
case L2D:
|
||||
case F2L:
|
||||
case F2D:
|
||||
case D2L:
|
||||
size = 2;
|
||||
break;
|
||||
case GETFIELD:
|
||||
size = Type.getType(((FieldInsnNode) insn).desc).getSize();
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue binaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case LALOAD:
|
||||
case DALOAD:
|
||||
case LADD:
|
||||
case DADD:
|
||||
case LSUB:
|
||||
case DSUB:
|
||||
case LMUL:
|
||||
case DMUL:
|
||||
case LDIV:
|
||||
case DDIV:
|
||||
case LREM:
|
||||
case DREM:
|
||||
case LSHL:
|
||||
case LSHR:
|
||||
case LUSHR:
|
||||
case LAND:
|
||||
case LOR:
|
||||
case LXOR:
|
||||
size = 2;
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue ternaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2, ParamsValue value3) {
|
||||
return val1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue naryOperation(AbstractInsnNode insn, List<? extends ParamsValue> values) {
|
||||
int size;
|
||||
int opcode = insn.getOpcode();
|
||||
if (opcode == MULTIANEWARRAY) {
|
||||
size = 1;
|
||||
} else {
|
||||
String desc = (opcode == INVOKEDYNAMIC) ? ((InvokeDynamicInsnNode) insn).desc : ((MethodInsnNode) insn).desc;
|
||||
size = Type.getReturnType(desc).getSize();
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnOperation(AbstractInsnNode insn, ParamsValue value, ParamsValue expected) {}
|
||||
|
||||
@Override
|
||||
public ParamsValue merge(ParamsValue v1, ParamsValue v2) {
|
||||
if (v1.equals(v2)) return v1;
|
||||
boolean[] params = new boolean[arity];
|
||||
boolean[] params1 = v1.params;
|
||||
boolean[] params2 = v2.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
params[i] = params1[i] || params2[i];
|
||||
}
|
||||
return new ParamsValue(params, Math.min(v1.size, v2.size));
|
||||
}
|
||||
}
|
||||
|
||||
class IParametersUsage extends Interpreter<IParamsValue> {
|
||||
static final IParamsValue val1 = new IParamsValue(0, 1);
|
||||
static final IParamsValue val2 = new IParamsValue(0, 2);
|
||||
int leaking = 0;
|
||||
int called = -1;
|
||||
final int rangeStart;
|
||||
final int rangeEnd;
|
||||
final int arity;
|
||||
final int shift;
|
||||
|
||||
IParametersUsage(MethodNode methodNode) {
|
||||
super(ASM5);
|
||||
arity = Type.getArgumentTypes(methodNode.desc).length;
|
||||
shift = (methodNode.access & ACC_STATIC) == 0 ? 2 : 1;
|
||||
rangeStart = shift;
|
||||
rangeEnd = arity + shift;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue newValue(Type type) {
|
||||
if (type == null) return val1;
|
||||
called++;
|
||||
if (type == Type.VOID_TYPE) return null;
|
||||
if (called < rangeEnd && rangeStart <= called && (ASMUtils.isReferenceType(type) || ASMUtils.isBooleanType(type))) {
|
||||
int n = called - shift;
|
||||
return type.getSize() == 1 ? new IParamsValue(1 << n, 1) : new IParamsValue(1 << n, 2);
|
||||
}
|
||||
else {
|
||||
return type.getSize() == 1 ? val1 : val2;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue newOperation(final AbstractInsnNode insn) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case LCONST_0:
|
||||
case LCONST_1:
|
||||
case DCONST_0:
|
||||
case DCONST_1:
|
||||
size = 2;
|
||||
break;
|
||||
case LDC:
|
||||
Object cst = ((LdcInsnNode) insn).cst;
|
||||
size = cst instanceof Long || cst instanceof Double ? 2 : 1;
|
||||
break;
|
||||
case GETSTATIC:
|
||||
size = ASMUtils.getSizeFast(((FieldInsnNode)insn).desc);
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue copyOperation(AbstractInsnNode insn, IParamsValue value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue unaryOperation(AbstractInsnNode insn, IParamsValue value) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case CHECKCAST:
|
||||
return value;
|
||||
case LNEG:
|
||||
case DNEG:
|
||||
case I2L:
|
||||
case I2D:
|
||||
case L2D:
|
||||
case F2L:
|
||||
case F2D:
|
||||
case D2L:
|
||||
size = 2;
|
||||
break;
|
||||
case GETFIELD:
|
||||
size = ASMUtils.getSizeFast(((FieldInsnNode)insn).desc);
|
||||
leaking |= value.params;
|
||||
break;
|
||||
case ARRAYLENGTH:
|
||||
case MONITORENTER:
|
||||
case INSTANCEOF:
|
||||
case IRETURN:
|
||||
case ARETURN:
|
||||
case IFNONNULL:
|
||||
case IFNULL:
|
||||
case IFEQ:
|
||||
case IFNE:
|
||||
size = 1;
|
||||
leaking |= value.params;
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue binaryOperation(AbstractInsnNode insn, IParamsValue value1, IParamsValue value2) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case LALOAD:
|
||||
case DALOAD:
|
||||
size = 2;
|
||||
leaking |= value1.params;
|
||||
break;
|
||||
case LADD:
|
||||
case DADD:
|
||||
case LSUB:
|
||||
case DSUB:
|
||||
case LMUL:
|
||||
case DMUL:
|
||||
case LDIV:
|
||||
case DDIV:
|
||||
case LREM:
|
||||
case DREM:
|
||||
case LSHL:
|
||||
case LSHR:
|
||||
case LUSHR:
|
||||
case LAND:
|
||||
case LOR:
|
||||
case LXOR:
|
||||
size = 2;
|
||||
break;
|
||||
case IALOAD:
|
||||
case FALOAD:
|
||||
case AALOAD:
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
case PUTFIELD:
|
||||
leaking |= value1.params;
|
||||
size = 1;
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue ternaryOperation(AbstractInsnNode insn, IParamsValue value1, IParamsValue value2, IParamsValue value3) {
|
||||
switch (insn.getOpcode()) {
|
||||
case IASTORE:
|
||||
case LASTORE:
|
||||
case FASTORE:
|
||||
case DASTORE:
|
||||
case AASTORE:
|
||||
case BASTORE:
|
||||
case CASTORE:
|
||||
case SASTORE:
|
||||
leaking |= value1.params;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return val1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue naryOperation(AbstractInsnNode insn, List<? extends IParamsValue> values) {
|
||||
int opcode = insn.getOpcode();
|
||||
switch (opcode) {
|
||||
case INVOKESTATIC:
|
||||
case INVOKESPECIAL:
|
||||
case INVOKEVIRTUAL:
|
||||
case INVOKEINTERFACE:
|
||||
for (IParamsValue value : values) {
|
||||
leaking |= value.params;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
int size;
|
||||
if (opcode == MULTIANEWARRAY) {
|
||||
size = 1;
|
||||
} else {
|
||||
String desc = (opcode == INVOKEDYNAMIC) ? ((InvokeDynamicInsnNode) insn).desc : ((MethodInsnNode) insn).desc;
|
||||
size = ASMUtils.getReturnSizeFast(desc);
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnOperation(AbstractInsnNode insn, IParamsValue value, IParamsValue expected) {}
|
||||
|
||||
@Override
|
||||
public IParamsValue merge(IParamsValue v1, IParamsValue v2) {
|
||||
if (v1.equals(v2)) return v1;
|
||||
return new IParamsValue(v1.params | v2.params, Math.min(v1.size, v2.size));
|
||||
}
|
||||
}
|
||||
|
||||
class LeakingParametersCollector extends ParametersUsage {
|
||||
final boolean[] leaking;
|
||||
LeakingParametersCollector(MethodNode methodNode) {
|
||||
super(methodNode);
|
||||
leaking = new boolean[arity];
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue unaryOperation(AbstractInsnNode insn, ParamsValue value) {
|
||||
switch (insn.getOpcode()) {
|
||||
case GETFIELD:
|
||||
case ARRAYLENGTH:
|
||||
case MONITORENTER:
|
||||
case INSTANCEOF:
|
||||
case IRETURN:
|
||||
case ARETURN:
|
||||
case IFNONNULL:
|
||||
case IFNULL:
|
||||
case IFEQ:
|
||||
case IFNE:
|
||||
boolean[] params = value.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.unaryOperation(insn, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue binaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2) {
|
||||
switch (insn.getOpcode()) {
|
||||
case IALOAD:
|
||||
case LALOAD:
|
||||
case FALOAD:
|
||||
case DALOAD:
|
||||
case AALOAD:
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
case PUTFIELD:
|
||||
boolean[] params = value1.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.binaryOperation(insn, value1, value2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue ternaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2, ParamsValue value3) {
|
||||
switch (insn.getOpcode()) {
|
||||
case IASTORE:
|
||||
case LASTORE:
|
||||
case FASTORE:
|
||||
case DASTORE:
|
||||
case AASTORE:
|
||||
case BASTORE:
|
||||
case CASTORE:
|
||||
case SASTORE:
|
||||
boolean[] params = value1.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.ternaryOperation(insn, value1, value2, value3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue naryOperation(AbstractInsnNode insn, List<? extends ParamsValue> values) {
|
||||
switch (insn.getOpcode()) {
|
||||
case INVOKESTATIC:
|
||||
case INVOKESPECIAL:
|
||||
case INVOKEVIRTUAL:
|
||||
case INVOKEINTERFACE:
|
||||
for (ParamsValue value : values) {
|
||||
boolean[] params = value.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.naryOperation(insn, values);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+563
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* 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 com.intellij.openapi.util.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.org.objectweb.asm.Type;
|
||||
import org.jetbrains.org.objectweb.asm.tree.*;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class LeakingParametersAnalysis {
|
||||
public static Pair<boolean[], Frame<Value>[]> leakingParameters(String className, MethodNode methodNode) throws AnalyzerException {
|
||||
Frame<ParamsValue>[] frames = new Analyzer<ParamsValue>(new ParametersUsage(methodNode)).analyze(className, methodNode);
|
||||
InsnList insns = methodNode.instructions;
|
||||
LeakingParametersCollector collector = new LeakingParametersCollector(methodNode);
|
||||
for (int i = 0; i < frames.length; i++) {
|
||||
AbstractInsnNode insnNode = insns.get(i);
|
||||
Frame<ParamsValue> frame = frames[i];
|
||||
if (frame != null) {
|
||||
switch (insnNode.getType()) {
|
||||
case AbstractInsnNode.LABEL:
|
||||
case AbstractInsnNode.LINE:
|
||||
case AbstractInsnNode.FRAME:
|
||||
break;
|
||||
default:
|
||||
new Frame<ParamsValue>(frame).execute(insnNode, collector);
|
||||
}
|
||||
}
|
||||
}
|
||||
// to hack type checker
|
||||
Frame<?>[] frames1 = frames;
|
||||
return new Pair<boolean[], Frame<Value>[]>(collector.leaking, (Frame<Value>[]) frames1);
|
||||
}
|
||||
|
||||
public static Pair<boolean[], Frame<Value>[]> fastLeakingParameters(String className, MethodNode methodNode) throws AnalyzerException {
|
||||
IParametersUsage parametersUsage = new IParametersUsage(methodNode);
|
||||
Frame<?>[] frames = new Analyzer<IParamsValue>(parametersUsage).analyze(className, methodNode);
|
||||
int leakingMask = parametersUsage.leaking;
|
||||
boolean[] result = new boolean[parametersUsage.arity];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = (leakingMask & (1 << i)) != 0;
|
||||
}
|
||||
return new Pair<boolean[], Frame<Value>[]>(result, (Frame<Value>[]) frames);
|
||||
}
|
||||
}
|
||||
|
||||
final class ParamsValue implements Value {
|
||||
@NotNull final boolean[] params;
|
||||
final int size;
|
||||
|
||||
ParamsValue(@NotNull boolean[] params, int size) {
|
||||
this.params = params;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
ParamsValue that = (ParamsValue)o;
|
||||
return (this.size == that.size && Arrays.equals(this.params, that.params));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * Arrays.hashCode(params) + size;
|
||||
}
|
||||
}
|
||||
|
||||
// specialized version
|
||||
final class IParamsValue implements Value {
|
||||
final int params;
|
||||
final int size;
|
||||
|
||||
IParamsValue(int params, int size) {
|
||||
this.params = params;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
IParamsValue that = (IParamsValue)o;
|
||||
return (this.size == that.size && this.params == that.params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * params + size;
|
||||
}
|
||||
}
|
||||
|
||||
class ParametersUsage extends Interpreter<ParamsValue> {
|
||||
final ParamsValue val1;
|
||||
final ParamsValue val2;
|
||||
int called = -1;
|
||||
final int rangeStart;
|
||||
final int rangeEnd;
|
||||
final int arity;
|
||||
final int shift;
|
||||
|
||||
ParametersUsage(MethodNode methodNode) {
|
||||
super(ASM5);
|
||||
arity = Type.getArgumentTypes(methodNode.desc).length;
|
||||
boolean[] emptyParams = new boolean[arity];
|
||||
val1 = new ParamsValue(emptyParams, 1);
|
||||
val2 = new ParamsValue(emptyParams, 2);
|
||||
|
||||
shift = (methodNode.access & ACC_STATIC) == 0 ? 2 : 1;
|
||||
rangeStart = shift;
|
||||
rangeEnd = arity + shift;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue newValue(Type type) {
|
||||
if (type == null) return val1;
|
||||
called++;
|
||||
if (type == Type.VOID_TYPE) return null;
|
||||
if (called < rangeEnd && rangeStart <= called && (ASMUtils.isReferenceType(type) || ASMUtils.isBooleanType(type))) {
|
||||
boolean[] params = new boolean[arity];
|
||||
params[called - shift] = true;
|
||||
return type.getSize() == 1 ? new ParamsValue(params, 1) : new ParamsValue(params, 2);
|
||||
}
|
||||
else {
|
||||
return type.getSize() == 1 ? val1 : val2;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue newOperation(final AbstractInsnNode insn) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case LCONST_0:
|
||||
case LCONST_1:
|
||||
case DCONST_0:
|
||||
case DCONST_1:
|
||||
size = 2;
|
||||
break;
|
||||
case LDC:
|
||||
Object cst = ((LdcInsnNode) insn).cst;
|
||||
size = cst instanceof Long || cst instanceof Double ? 2 : 1;
|
||||
break;
|
||||
case GETSTATIC:
|
||||
size = Type.getType(((FieldInsnNode) insn).desc).getSize();
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue copyOperation(AbstractInsnNode insn, ParamsValue value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue unaryOperation(AbstractInsnNode insn, ParamsValue value) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case CHECKCAST:
|
||||
return value;
|
||||
case LNEG:
|
||||
case DNEG:
|
||||
case I2L:
|
||||
case I2D:
|
||||
case L2D:
|
||||
case F2L:
|
||||
case F2D:
|
||||
case D2L:
|
||||
size = 2;
|
||||
break;
|
||||
case GETFIELD:
|
||||
size = Type.getType(((FieldInsnNode) insn).desc).getSize();
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue binaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case LALOAD:
|
||||
case DALOAD:
|
||||
case LADD:
|
||||
case DADD:
|
||||
case LSUB:
|
||||
case DSUB:
|
||||
case LMUL:
|
||||
case DMUL:
|
||||
case LDIV:
|
||||
case DDIV:
|
||||
case LREM:
|
||||
case DREM:
|
||||
case LSHL:
|
||||
case LSHR:
|
||||
case LUSHR:
|
||||
case LAND:
|
||||
case LOR:
|
||||
case LXOR:
|
||||
size = 2;
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue ternaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2, ParamsValue value3) {
|
||||
return val1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue naryOperation(AbstractInsnNode insn, List<? extends ParamsValue> values) {
|
||||
int size;
|
||||
int opcode = insn.getOpcode();
|
||||
if (opcode == MULTIANEWARRAY) {
|
||||
size = 1;
|
||||
} else {
|
||||
String desc = (opcode == INVOKEDYNAMIC) ? ((InvokeDynamicInsnNode) insn).desc : ((MethodInsnNode) insn).desc;
|
||||
size = Type.getReturnType(desc).getSize();
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnOperation(AbstractInsnNode insn, ParamsValue value, ParamsValue expected) {}
|
||||
|
||||
@Override
|
||||
public ParamsValue merge(ParamsValue v1, ParamsValue v2) {
|
||||
if (v1.equals(v2)) return v1;
|
||||
boolean[] params = new boolean[arity];
|
||||
boolean[] params1 = v1.params;
|
||||
boolean[] params2 = v2.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
params[i] = params1[i] || params2[i];
|
||||
}
|
||||
return new ParamsValue(params, Math.min(v1.size, v2.size));
|
||||
}
|
||||
}
|
||||
|
||||
class IParametersUsage extends Interpreter<IParamsValue> {
|
||||
static final IParamsValue val1 = new IParamsValue(0, 1);
|
||||
static final IParamsValue val2 = new IParamsValue(0, 2);
|
||||
int leaking = 0;
|
||||
int called = -1;
|
||||
final int rangeStart;
|
||||
final int rangeEnd;
|
||||
final int arity;
|
||||
final int shift;
|
||||
|
||||
IParametersUsage(MethodNode methodNode) {
|
||||
super(ASM5);
|
||||
arity = Type.getArgumentTypes(methodNode.desc).length;
|
||||
shift = (methodNode.access & ACC_STATIC) == 0 ? 2 : 1;
|
||||
rangeStart = shift;
|
||||
rangeEnd = arity + shift;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue newValue(Type type) {
|
||||
if (type == null) return val1;
|
||||
called++;
|
||||
if (type == Type.VOID_TYPE) return null;
|
||||
if (called < rangeEnd && rangeStart <= called && (ASMUtils.isReferenceType(type) || ASMUtils.isBooleanType(type))) {
|
||||
int n = called - shift;
|
||||
return type.getSize() == 1 ? new IParamsValue(1 << n, 1) : new IParamsValue(1 << n, 2);
|
||||
}
|
||||
else {
|
||||
return type.getSize() == 1 ? val1 : val2;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue newOperation(final AbstractInsnNode insn) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case LCONST_0:
|
||||
case LCONST_1:
|
||||
case DCONST_0:
|
||||
case DCONST_1:
|
||||
size = 2;
|
||||
break;
|
||||
case LDC:
|
||||
Object cst = ((LdcInsnNode) insn).cst;
|
||||
size = cst instanceof Long || cst instanceof Double ? 2 : 1;
|
||||
break;
|
||||
case GETSTATIC:
|
||||
size = ASMUtils.getSizeFast(((FieldInsnNode)insn).desc);
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue copyOperation(AbstractInsnNode insn, IParamsValue value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue unaryOperation(AbstractInsnNode insn, IParamsValue value) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case CHECKCAST:
|
||||
return value;
|
||||
case LNEG:
|
||||
case DNEG:
|
||||
case I2L:
|
||||
case I2D:
|
||||
case L2D:
|
||||
case F2L:
|
||||
case F2D:
|
||||
case D2L:
|
||||
size = 2;
|
||||
break;
|
||||
case GETFIELD:
|
||||
size = ASMUtils.getSizeFast(((FieldInsnNode)insn).desc);
|
||||
leaking |= value.params;
|
||||
break;
|
||||
case ARRAYLENGTH:
|
||||
case MONITORENTER:
|
||||
case INSTANCEOF:
|
||||
case IRETURN:
|
||||
case ARETURN:
|
||||
case IFNONNULL:
|
||||
case IFNULL:
|
||||
case IFEQ:
|
||||
case IFNE:
|
||||
size = 1;
|
||||
leaking |= value.params;
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue binaryOperation(AbstractInsnNode insn, IParamsValue value1, IParamsValue value2) {
|
||||
int size;
|
||||
switch (insn.getOpcode()) {
|
||||
case LALOAD:
|
||||
case DALOAD:
|
||||
size = 2;
|
||||
leaking |= value1.params;
|
||||
break;
|
||||
case LADD:
|
||||
case DADD:
|
||||
case LSUB:
|
||||
case DSUB:
|
||||
case LMUL:
|
||||
case DMUL:
|
||||
case LDIV:
|
||||
case DDIV:
|
||||
case LREM:
|
||||
case DREM:
|
||||
case LSHL:
|
||||
case LSHR:
|
||||
case LUSHR:
|
||||
case LAND:
|
||||
case LOR:
|
||||
case LXOR:
|
||||
size = 2;
|
||||
break;
|
||||
case IALOAD:
|
||||
case FALOAD:
|
||||
case AALOAD:
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
case PUTFIELD:
|
||||
leaking |= value1.params;
|
||||
size = 1;
|
||||
break;
|
||||
default:
|
||||
size = 1;
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue ternaryOperation(AbstractInsnNode insn, IParamsValue value1, IParamsValue value2, IParamsValue value3) {
|
||||
switch (insn.getOpcode()) {
|
||||
case IASTORE:
|
||||
case LASTORE:
|
||||
case FASTORE:
|
||||
case DASTORE:
|
||||
case AASTORE:
|
||||
case BASTORE:
|
||||
case CASTORE:
|
||||
case SASTORE:
|
||||
leaking |= value1.params;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return val1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParamsValue naryOperation(AbstractInsnNode insn, List<? extends IParamsValue> values) {
|
||||
int opcode = insn.getOpcode();
|
||||
switch (opcode) {
|
||||
case INVOKESTATIC:
|
||||
case INVOKESPECIAL:
|
||||
case INVOKEVIRTUAL:
|
||||
case INVOKEINTERFACE:
|
||||
for (IParamsValue value : values) {
|
||||
leaking |= value.params;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
int size;
|
||||
if (opcode == MULTIANEWARRAY) {
|
||||
size = 1;
|
||||
} else {
|
||||
String desc = (opcode == INVOKEDYNAMIC) ? ((InvokeDynamicInsnNode) insn).desc : ((MethodInsnNode) insn).desc;
|
||||
size = ASMUtils.getReturnSizeFast(desc);
|
||||
}
|
||||
return size == 1 ? val1 : val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnOperation(AbstractInsnNode insn, IParamsValue value, IParamsValue expected) {}
|
||||
|
||||
@Override
|
||||
public IParamsValue merge(IParamsValue v1, IParamsValue v2) {
|
||||
if (v1.equals(v2)) return v1;
|
||||
return new IParamsValue(v1.params | v2.params, Math.min(v1.size, v2.size));
|
||||
}
|
||||
}
|
||||
|
||||
class LeakingParametersCollector extends ParametersUsage {
|
||||
final boolean[] leaking;
|
||||
LeakingParametersCollector(MethodNode methodNode) {
|
||||
super(methodNode);
|
||||
leaking = new boolean[arity];
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue unaryOperation(AbstractInsnNode insn, ParamsValue value) {
|
||||
switch (insn.getOpcode()) {
|
||||
case GETFIELD:
|
||||
case ARRAYLENGTH:
|
||||
case MONITORENTER:
|
||||
case INSTANCEOF:
|
||||
case IRETURN:
|
||||
case ARETURN:
|
||||
case IFNONNULL:
|
||||
case IFNULL:
|
||||
case IFEQ:
|
||||
case IFNE:
|
||||
boolean[] params = value.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.unaryOperation(insn, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue binaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2) {
|
||||
switch (insn.getOpcode()) {
|
||||
case IALOAD:
|
||||
case LALOAD:
|
||||
case FALOAD:
|
||||
case DALOAD:
|
||||
case AALOAD:
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
case PUTFIELD:
|
||||
boolean[] params = value1.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.binaryOperation(insn, value1, value2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue ternaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2, ParamsValue value3) {
|
||||
switch (insn.getOpcode()) {
|
||||
case IASTORE:
|
||||
case LASTORE:
|
||||
case FASTORE:
|
||||
case DASTORE:
|
||||
case AASTORE:
|
||||
case BASTORE:
|
||||
case CASTORE:
|
||||
case SASTORE:
|
||||
boolean[] params = value1.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.ternaryOperation(insn, value1, value2, value3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamsValue naryOperation(AbstractInsnNode insn, List<? extends ParamsValue> values) {
|
||||
switch (insn.getOpcode()) {
|
||||
case INVOKESTATIC:
|
||||
case INVOKESPECIAL:
|
||||
case INVOKEVIRTUAL:
|
||||
case INVOKEINTERFACE:
|
||||
for (ParamsValue value : values) {
|
||||
boolean[] params = value.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.naryOperation(insn, values);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -17,6 +17,7 @@ package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.InferredAnnotationsManager;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.LeakingParametersAnalysis;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.data.*;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
@@ -94,7 +95,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
public void visitEnd() {
|
||||
super.visitEnd();
|
||||
try {
|
||||
map.put(method, cfg.leakingParameters(classReader.getClassName(), node).first);
|
||||
map.put(method, LeakingParametersAnalysis.leakingParameters(classReader.getClassName(), node).first);
|
||||
}
|
||||
catch (AnalyzerException ignore) {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user