| /* |
| * 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.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 LeakingParameters { |
| public final Frame<Value>[] frames; |
| public final boolean[] parameters; |
| public final boolean[] nullableParameters; |
| |
| public LeakingParameters(Frame<Value>[] frames, boolean[] parameters, boolean[] nullableParameters) { |
| this.frames = frames; |
| this.parameters = parameters; |
| this.nullableParameters = nullableParameters; |
| } |
| |
| public static LeakingParameters build(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException { |
| Frame<ParamsValue>[] frames = jsr ? |
| new Analyzer<ParamsValue>(new ParametersUsage(methodNode)).analyze(className, methodNode) : |
| new LiteAnalyzer<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); |
| } |
| } |
| } |
| boolean[] notNullParameters = collector.leaking; |
| boolean[] nullableParameters = collector.nullableLeaking; |
| for (int i = 0; i < nullableParameters.length; i++) { |
| nullableParameters[i] |= notNullParameters[i]; |
| } |
| return new LeakingParameters((Frame<Value>[])(Frame<?>[])frames, notNullParameters, nullableParameters); |
| } |
| |
| public static LeakingParameters buildFast(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException { |
| IParametersUsage parametersUsage = new IParametersUsage(methodNode); |
| Frame<?>[] frames = jsr ? |
| new Analyzer<IParamsValue>(parametersUsage).analyze(className, methodNode) : |
| new LiteAnalyzer<IParamsValue>(parametersUsage).analyze(className, methodNode); |
| int leakingMask = parametersUsage.leaking; |
| int nullableLeakingMask = parametersUsage.nullableLeaking; |
| boolean[] notNullParameters = new boolean[parametersUsage.arity]; |
| boolean[] nullableParameters = new boolean[parametersUsage.arity]; |
| for (int i = 0; i < notNullParameters.length; i++) { |
| notNullParameters[i] = (leakingMask & (1 << i)) != 0; |
| nullableParameters[i] = ((leakingMask | nullableLeakingMask) & (1 << i)) != 0; |
| } |
| return new LeakingParameters((Frame<Value>[])frames, notNullParameters, nullableParameters); |
| } |
| } |
| |
| 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 null; |
| } |
| |
| @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 nullableLeaking = 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: |
| leaking |= value1.params; |
| size = 1; |
| break; |
| case PUTFIELD: |
| leaking |= value1.params; |
| nullableLeaking |= value2.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 BASTORE: |
| case CASTORE: |
| case SASTORE: |
| leaking |= value1.params; |
| break; |
| case AASTORE: |
| leaking |= value1.params; |
| nullableLeaking |= value3.params; |
| break; |
| default: |
| } |
| return null; |
| } |
| |
| @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; |
| final boolean[] nullableLeaking; |
| LeakingParametersCollector(MethodNode methodNode) { |
| super(methodNode); |
| leaking = new boolean[arity]; |
| nullableLeaking = 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: |
| boolean[] params = value1.params; |
| for (int i = 0; i < arity; i++) { |
| leaking[i] |= params[i]; |
| } |
| break; |
| case PUTFIELD: |
| params = value1.params; |
| for (int i = 0; i < arity; i++) { |
| leaking[i] |= params[i]; |
| } |
| params = value2.params; |
| for (int i = 0; i < arity; i++) { |
| nullableLeaking[i] |= params[i]; |
| } |
| break; |
| default: |
| } |
| return super.binaryOperation(insn, value1, value2); |
| } |
| |
| @Override |
| public ParamsValue ternaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2, ParamsValue value3) { |
| boolean[] params; |
| switch (insn.getOpcode()) { |
| case IASTORE: |
| case LASTORE: |
| case FASTORE: |
| case DASTORE: |
| case BASTORE: |
| case CASTORE: |
| case SASTORE: |
| params = value1.params; |
| for (int i = 0; i < arity; i++) { |
| leaking[i] |= params[i]; |
| } |
| break; |
| case AASTORE: |
| params = value1.params; |
| for (int i = 0; i < arity; i++) { |
| leaking[i] |= params[i]; |
| } |
| params = value3.params; |
| for (int i = 0; i < arity; i++) { |
| nullableLeaking[i] |= params[i]; |
| } |
| break; |
| default: |
| } |
| return null; |
| } |
| |
| @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); |
| } |
| } |
| |