diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/descriptors/data/ArgValueData.java b/java/debugger/impl/src/com/intellij/debugger/impl/descriptors/data/ArgValueData.java index 79038ce25cec..3595167f297d 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/descriptors/data/ArgValueData.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/descriptors/data/ArgValueData.java @@ -18,19 +18,23 @@ package com.intellij.debugger.impl.descriptors.data; import com.intellij.debugger.ui.impl.watch.ArgumentValueDescriptorImpl; import com.intellij.openapi.project.Project; import com.sun.jdi.Value; +import org.jetbrains.annotations.Nullable; public class ArgValueData extends DescriptorData{ private final int myIndex; private final Value myValue; + @Nullable + private final String myDisplayName; - public ArgValueData(int index, Value value) { + public ArgValueData(int index, Value value, @Nullable String displayName) { super(); myIndex = index; myValue = value; + myDisplayName = displayName; } protected ArgumentValueDescriptorImpl createDescriptorImpl(Project project) { - return new ArgumentValueDescriptorImpl(project, myIndex, myValue); + return new ArgumentValueDescriptorImpl(project, myIndex, myValue, myDisplayName); } public boolean equals(Object object) { diff --git a/java/debugger/impl/src/com/intellij/debugger/jdi/Bytecodes.java b/java/debugger/impl/src/com/intellij/debugger/jdi/Bytecodes.java new file mode 100644 index 000000000000..86fb655b0853 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/jdi/Bytecodes.java @@ -0,0 +1,449 @@ +/* + * Copyright 2000-2013 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.debugger.jdi; + +/** + * As defined in ADM4 library + */ +class Bytecodes { + /** + * Pseudo access flag to distinguish between the synthetic attribute and + * the synthetic access flag. + */ + static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000; + + /** + * The type of instructions without any argument. + */ + static final int NOARG_INSN = 0; + + /** + * The type of instructions with an signed byte argument. + */ + static final int SBYTE_INSN = 1; + + /** + * The type of instructions with an signed short argument. + */ + static final int SHORT_INSN = 2; + + /** + * The type of instructions with a local variable index argument. + */ + static final int VAR_INSN = 3; + + /** + * The type of instructions with an implicit local variable index argument. + */ + static final int IMPLVAR_INSN = 4; + + /** + * The type of instructions with a type descriptor argument. + */ + static final int TYPE_INSN = 5; + + /** + * The type of field and method invocations instructions. + */ + static final int FIELDORMETH_INSN = 6; + + /** + * The type of the INVOKEINTERFACE/INVOKEDYNAMIC instruction. + */ + static final int ITFMETH_INSN = 7; + + /** + * The type of the INVOKEDYNAMIC instruction. + */ + static final int INDYMETH_INSN = 8; + + /** + * The type of instructions with a 2 bytes bytecode offset label. + */ + static final int LABEL_INSN = 9; + + /** + * The type of instructions with a 4 bytes bytecode offset label. + */ + static final int LABELW_INSN = 10; + + /** + * The type of the LDC instruction. + */ + static final int LDC_INSN = 11; + + /** + * The type of the LDC_W and LDC2_W instructions. + */ + static final int LDCW_INSN = 12; + + /** + * The type of the IINC instruction. + */ + static final int IINC_INSN = 13; + + /** + * The type of the TABLESWITCH instruction. + */ + static final int TABL_INSN = 14; + + /** + * The type of the LOOKUPSWITCH instruction. + */ + static final int LOOK_INSN = 15; + + /** + * The type of the MULTIANEWARRAY instruction. + */ + static final int MANA_INSN = 16; + + /** + * The type of the WIDE instruction. + */ + static final int WIDE_INSN = 17; + + /** + * The instruction types of all JVM opcodes. + */ + static final byte[] TYPE; + + /** + * The type of CONSTANT_Class constant pool items. + */ + static final int CLASS = 7; + + /** + * The type of CONSTANT_Fieldref constant pool items. + */ + static final int FIELD = 9; + + /** + * The type of CONSTANT_Methodref constant pool items. + */ + static final int METH = 10; + + /** + * The type of CONSTANT_InterfaceMethodref constant pool items. + */ + static final int IMETH = 11; + + /** + * The type of CONSTANT_String constant pool items. + */ + static final int STR = 8; + + /** + * The type of CONSTANT_Integer constant pool items. + */ + static final int INT = 3; + + /** + * The type of CONSTANT_Float constant pool items. + */ + static final int FLOAT = 4; + + /** + * The type of CONSTANT_Long constant pool items. + */ + static final int LONG = 5; + + /** + * The type of CONSTANT_Double constant pool items. + */ + static final int DOUBLE = 6; + + /** + * The type of CONSTANT_NameAndType constant pool items. + */ + static final int NAME_TYPE = 12; + + /** + * The type of CONSTANT_Utf8 constant pool items. + */ + static final int UTF8 = 1; + + /** + * The type of CONSTANT_MethodType constant pool items. + */ + static final int MTYPE = 16; + + /** + * The type of CONSTANT_MethodHandle constant pool items. + */ + static final int HANDLE = 15; + + /** + * The type of CONSTANT_InvokeDynamic constant pool items. + */ + static final int INDY = 18; + + /** + * The base value for all CONSTANT_MethodHandle constant pool items. + * Internally, ASM store the 9 variations of CONSTANT_MethodHandle into + * 9 different items. + */ + static final int HANDLE_BASE = 20; + + /** + * Normal type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_NORMAL = 30; + + /** + * Uninitialized type Item stored in the ClassWriter + * {@link ClassWriter#typeTable}, instead of the constant pool, in order to + * avoid clashes with normal constant pool items in the ClassWriter constant + * pool's hash table. + */ + static final int TYPE_UNINIT = 31; + + /** + * Merged type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_MERGED = 32; + + /** + * The type of BootstrapMethods items. These items are stored in a + * special class attribute named BootstrapMethods and + * not in the constant pool. + */ + static final int BSM = 33; + + /** + * Computes the instruction types of JVM opcodes. + */ + static { + int i; + byte[] b = new byte[220]; + String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" + + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA" + + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ"; + for (i = 0; i < b.length; ++i) { + b[i] = (byte) (s.charAt(i) - 'A'); + } + TYPE = b; + } + + + static final int NOP = 0; // visitInsn + static final int ACONST_NULL = 1; // - + static final int ICONST_M1 = 2; // - + static final int ICONST_0 = 3; // - + static final int ICONST_1 = 4; // - + static final int ICONST_2 = 5; // - + static final int ICONST_3 = 6; // - + static final int ICONST_4 = 7; // - + static final int ICONST_5 = 8; // - + static final int LCONST_0 = 9; // - + static final int LCONST_1 = 10; // - + static final int FCONST_0 = 11; // - + static final int FCONST_1 = 12; // - + static final int FCONST_2 = 13; // - + static final int DCONST_0 = 14; // - + static final int DCONST_1 = 15; // - + static final int BIPUSH = 16; // visitIntInsn + static final int SIPUSH = 17; // - + static final int LDC = 18; // visitLdcInsn + static final int LDC_W = 19; // - + static final int LDC2_W = 20; // - + static final int ILOAD = 21; // visitVarInsn + static final int LLOAD = 22; // - + static final int FLOAD = 23; // - + static final int DLOAD = 24; // - + static final int ALOAD = 25; // - + static final int ILOAD_0 = 26; // - + static final int ILOAD_1 = 27; // - + static final int ILOAD_2 = 28; // - + static final int ILOAD_3 = 29; // - + static final int LLOAD_0 = 30; // - + static final int LLOAD_1 = 31; // - + static final int LLOAD_2 = 32; // - + static final int LLOAD_3 = 33; // - + static final int FLOAD_0 = 34; // - + static final int FLOAD_1 = 35; // - + static final int FLOAD_2 = 36; // - + static final int FLOAD_3 = 37; // - + static final int DLOAD_0 = 38; // - + static final int DLOAD_1 = 39; // - + static final int DLOAD_2 = 40; // - + static final int DLOAD_3 = 41; // - + static final int ALOAD_0 = 42; // - + static final int ALOAD_1 = 43; // - + static final int ALOAD_2 = 44; // - + static final int ALOAD_3 = 45; // - + static final int IALOAD = 46; // visitInsn + static final int LALOAD = 47; // - + static final int FALOAD = 48; // - + static final int DALOAD = 49; // - + static final int AALOAD = 50; // - + static final int BALOAD = 51; // - + static final int CALOAD = 52; // - + static final int SALOAD = 53; // - + static final int ISTORE = 54; // visitVarInsn + static final int LSTORE = 55; // - + static final int FSTORE = 56; // - + static final int DSTORE = 57; // - + static final int ASTORE = 58; // - + static final int ISTORE_0 = 59; // - + static final int ISTORE_1 = 60; // - + static final int ISTORE_2 = 61; // - + static final int ISTORE_3 = 62; // - + static final int LSTORE_0 = 63; // - + static final int LSTORE_1 = 64; // - + static final int LSTORE_2 = 65; // - + static final int LSTORE_3 = 66; // - + static final int FSTORE_0 = 67; // - + static final int FSTORE_1 = 68; // - + static final int FSTORE_2 = 69; // - + static final int FSTORE_3 = 70; // - + static final int DSTORE_0 = 71; // - + static final int DSTORE_1 = 72; // - + static final int DSTORE_2 = 73; // - + static final int DSTORE_3 = 74; // - + static final int ASTORE_0 = 75; // - + static final int ASTORE_1 = 76; // - + static final int ASTORE_2 = 77; // - + static final int ASTORE_3 = 78; // - + static final int IASTORE = 79; // visitInsn + static final int LASTORE = 80; // - + static final int FASTORE = 81; // - + static final int DASTORE = 82; // - + static final int AASTORE = 83; // - + static final int BASTORE = 84; // - + static final int CASTORE = 85; // - + static final int SASTORE = 86; // - + static final int POP = 87; // - + static final int POP2 = 88; // - + static final int DUP = 89; // - + static final int DUP_X1 = 90; // - + static final int DUP_X2 = 91; // - + static final int DUP2 = 92; // - + static final int DUP2_X1 = 93; // - + static final int DUP2_X2 = 94; // - + static final int SWAP = 95; // - + static final int IADD = 96; // - + static final int LADD = 97; // - + static final int FADD = 98; // - + static final int DADD = 99; // - + static final int ISUB = 100; // - + static final int LSUB = 101; // - + static final int FSUB = 102; // - + static final int DSUB = 103; // - + static final int IMUL = 104; // - + static final int LMUL = 105; // - + static final int FMUL = 106; // - + static final int DMUL = 107; // - + static final int IDIV = 108; // - + static final int LDIV = 109; // - + static final int FDIV = 110; // - + static final int DDIV = 111; // - + static final int IREM = 112; // - + static final int LREM = 113; // - + static final int FREM = 114; // - + static final int DREM = 115; // - + static final int INEG = 116; // - + static final int LNEG = 117; // - + static final int FNEG = 118; // - + static final int DNEG = 119; // - + static final int ISHL = 120; // - + static final int LSHL = 121; // - + static final int ISHR = 122; // - + static final int LSHR = 123; // - + static final int IUSHR = 124; // - + static final int LUSHR = 125; // - + static final int IAND = 126; // - + static final int LAND = 127; // - + static final int IOR = 128; // - + static final int LOR = 129; // - + static final int IXOR = 130; // - + static final int LXOR = 131; // - + static final int IINC = 132; // visitIincInsn + static final int I2L = 133; // visitInsn + static final int I2F = 134; // - + static final int I2D = 135; // - + static final int L2I = 136; // - + static final int L2F = 137; // - + static final int L2D = 138; // - + static final int F2I = 139; // - + static final int F2L = 140; // - + static final int F2D = 141; // - + static final int D2I = 142; // - + static final int D2L = 143; // - + static final int D2F = 144; // - + static final int I2B = 145; // - + static final int I2C = 146; // - + static final int I2S = 147; // - + static final int LCMP = 148; // - + static final int FCMPL = 149; // - + static final int FCMPG = 150; // - + static final int DCMPL = 151; // - + static final int DCMPG = 152; // - + static final int IFEQ = 153; // visitJumpInsn + static final int IFNE = 154; // - + static final int IFLT = 155; // - + static final int IFGE = 156; // - + static final int IFGT = 157; // - + static final int IFLE = 158; // - + static final int IF_ICMPEQ = 159; // - + static final int IF_ICMPNE = 160; // - + static final int IF_ICMPLT = 161; // - + static final int IF_ICMPGE = 162; // - + static final int IF_ICMPGT = 163; // - + static final int IF_ICMPLE = 164; // - + static final int IF_ACMPEQ = 165; // - + static final int IF_ACMPNE = 166; // - + static final int GOTO = 167; // - + static final int JSR = 168; // - + static final int RET = 169; // visitVarInsn + static final int TABLESWITCH = 170; // visiTableSwitchInsn + static final int LOOKUPSWITCH = 171; // visitLookupSwitch + static final int IRETURN = 172; // visitInsn + static final int LRETURN = 173; // - + static final int FRETURN = 174; // - + static final int DRETURN = 175; // - + static final int ARETURN = 176; // - + static final int RETURN = 177; // - + static final int GETSTATIC = 178; // visitFieldInsn + static final int PUTSTATIC = 179; // - + static final int GETFIELD = 180; // - + static final int PUTFIELD = 181; // - + static final int INVOKEVIRTUAL = 182; // visitMethodInsn + static final int INVOKESPECIAL = 183; // - + static final int INVOKESTATIC = 184; // - + static final int INVOKEINTERFACE = 185; // - + static final int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn + static final int NEW = 187; // visitTypeInsn + static final int NEWARRAY = 188; // visitIntInsn + static final int ANEWARRAY = 189; // visitTypeInsn + static final int ARRAYLENGTH = 190; // visitInsn + static final int ATHROW = 191; // - + static final int CHECKCAST = 192; // visitTypeInsn + static final int INSTANCEOF = 193; // - + static final int MONITORENTER = 194; // visitInsn + static final int MONITOREXIT = 195; // - + static final int WIDE = 196; // NOT VISITED + static final int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn + static final int IFNULL = 198; // visitJumpInsn + static final int IFNONNULL = 199; // - + static final int GOTO_W = 200; // - + static final int JSR_W = 201; // - + +} diff --git a/java/debugger/impl/src/com/intellij/debugger/jdi/DecompiledLocalVariable.java b/java/debugger/impl/src/com/intellij/debugger/jdi/DecompiledLocalVariable.java new file mode 100644 index 000000000000..4564fd60bd71 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/jdi/DecompiledLocalVariable.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2013 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.debugger.jdi; + +import java.util.Comparator; + +/** + * @author Eugene Zhuravlev + * Date: 10/7/13 + */ +public class DecompiledLocalVariable{ + public static final Comparator COMPARATOR = new Comparator() { + @Override + public int compare(DecompiledLocalVariable v1, DecompiledLocalVariable v2) { + return v1.getSlot() - v2.getSlot(); + } + }; + + private final int mySlot; + private final String mySignature; + private final String myName; + + public DecompiledLocalVariable(int slot, String name, String signature) { + mySlot = slot; + mySignature = signature; + myName = name; + } + + public int getSlot() { + return mySlot; + } + + public String getSignature() { + return mySignature; + } + + public String getName() { + return myName; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/jdi/InstructionParser.java b/java/debugger/impl/src/com/intellij/debugger/jdi/InstructionParser.java new file mode 100644 index 000000000000..b8053a891d42 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/jdi/InstructionParser.java @@ -0,0 +1,233 @@ +/* + * Copyright 2000-2013 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.debugger.jdi; + +import org.jetbrains.annotations.Nullable; + +/** + * "Code" attribute parsing extracted from ASM4 library and adopted to finding operations with local variables + */ +public class InstructionParser { + private final byte[] myCode; + private final long myCurrentInstructionIndex; + + public InstructionParser(byte[] code, long instructionIndex) { + myCode = code; + myCurrentInstructionIndex = instructionIndex; + } + + public void parse() { + final int codeEnd = myCode.length; + int v = 0; + int instructionIndex = 0; + while (v < codeEnd) { + if (instructionIndex++ >= myCurrentInstructionIndex) { + break; + } + int opcode = myCode[v] & 0xFF; + switch (Bytecodes.TYPE[opcode]) { + case Bytecodes.NOARG_INSN: + v += 1; + break; + case Bytecodes.IMPLVAR_INSN: { + final int varOpcode; + if (opcode > Bytecodes.ISTORE) { + opcode -= Bytecodes.ISTORE_0; + varOpcode = Bytecodes.ISTORE + (opcode >> 2); + } + else { + opcode -= Bytecodes.ILOAD_0; + varOpcode = Bytecodes.ILOAD + (opcode >> 2); + } + final String signature = getVarInstructionTypeSignature(varOpcode); + if (signature != null) { + localVariableInstructionFound(varOpcode, opcode & 0x3, signature); + } + v += 1; + break; + } + case Bytecodes.LABEL_INSN: + v += 3; + break; + case Bytecodes.LABELW_INSN: + v += 5; + break; + case Bytecodes.WIDE_INSN: { + opcode = myCode[v + 1] & 0xFF; + final String signature = getVarInstructionTypeSignature(opcode); + if (signature != null) { + localVariableInstructionFound(opcode, readUnsignedShort(v + 2), signature); + } + if (opcode == Bytecodes.IINC) { + v += 6; + } + else { + v += 4; + } + break; + } + case Bytecodes.TABL_INSN: + // skips 0 to 3 padding bytes + v = v + 4 - (v & 3); + // reads instruction + int min = readInt(v + 4); + int max = readInt(v + 8); + v += 12; + final int length = max - min + 1; + v += 4 * length; + break; + case Bytecodes.LOOK_INSN: + // skips 0 to 3 padding bytes + v = v + 4 - (v & 3); + // reads instruction + final int len = readInt(v + 4); + v += 8 * (len + 1); + break; + case Bytecodes.VAR_INSN: { + final String signature = getVarInstructionTypeSignature(opcode); + if (signature != null) { + localVariableInstructionFound(opcode, myCode[v + 1] & 0xFF, signature); + } + v += 2; + break; + } + case Bytecodes.SBYTE_INSN: + v += 2; + break; + case Bytecodes.SHORT_INSN: + v += 3; + break; + case Bytecodes.LDC_INSN: + v += 2; + break; + case Bytecodes.LDCW_INSN: + v += 3; + break; + case Bytecodes.FIELDORMETH_INSN: + case Bytecodes.ITFMETH_INSN: { + if (opcode == Bytecodes.INVOKEINTERFACE) { + v += 5; + } + else { + v += 3; + } + break; + } + case Bytecodes.INDYMETH_INSN: { + v += 5; + break; + } + case Bytecodes.TYPE_INSN: + v += 3; + break; + case Bytecodes.IINC_INSN: { + final String signature = getVarInstructionTypeSignature(opcode); + if (signature != null) { + localVariableInstructionFound(opcode, myCode[v + 1] & 0xFF, signature); + } + v += 3; + break; + } + // case MANA_INSN: + default: + v += 4; + break; + } + } + } + + private int readUnsignedShort(final int index) { + final byte[] b = myCode; + return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); + } + + public int readInt(final int index) { + final byte[] b = myCode; + return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) + | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); + } + + protected void localVariableInstructionFound(int opcode, int slot, String typeSignature) { + // override to perform actions + } + + @Nullable + private static String getVarInstructionTypeSignature(int opcode) { + switch (opcode) { + case Bytecodes.ILOAD: + case Bytecodes.ILOAD_0: + case Bytecodes.ILOAD_1: + case Bytecodes.ILOAD_2: + case Bytecodes.ILOAD_3: + case Bytecodes.ISTORE: + case Bytecodes.ISTORE_0: + case Bytecodes.ISTORE_1: + case Bytecodes.ISTORE_2: + case Bytecodes.ISTORE_3: + case Bytecodes.IINC: + return "I"; + + case Bytecodes.LLOAD: + case Bytecodes.LLOAD_0: + case Bytecodes.LLOAD_1: + case Bytecodes.LLOAD_2: + case Bytecodes.LLOAD_3: + case Bytecodes.LSTORE: + case Bytecodes.LSTORE_0: + case Bytecodes.LSTORE_1: + case Bytecodes.LSTORE_2: + case Bytecodes.LSTORE_3: + return "J"; // long + + case Bytecodes.FLOAD: + case Bytecodes.FLOAD_0: + case Bytecodes.FLOAD_1: + case Bytecodes.FLOAD_2: + case Bytecodes.FLOAD_3: + case Bytecodes.FSTORE: + case Bytecodes.FSTORE_0: + case Bytecodes.FSTORE_1: + case Bytecodes.FSTORE_2: + case Bytecodes.FSTORE_3: + return "F"; // float + + case Bytecodes.DLOAD: + case Bytecodes.DLOAD_0: + case Bytecodes.DLOAD_1: + case Bytecodes.DLOAD_2: + case Bytecodes.DLOAD_3: + case Bytecodes.DSTORE: + case Bytecodes.DSTORE_0: + case Bytecodes.DSTORE_1: + case Bytecodes.DSTORE_2: + case Bytecodes.DSTORE_3: + return "D"; // double + + case Bytecodes.ALOAD: + case Bytecodes.ALOAD_0: + case Bytecodes.ALOAD_1: + case Bytecodes.ALOAD_2: + case Bytecodes.ALOAD_3: + case Bytecodes.ASTORE: + case Bytecodes.ASTORE_0: + case Bytecodes.ASTORE_1: + case Bytecodes.ASTORE_2: + case Bytecodes.ASTORE_3: + return "L"; // object ref + } + return null; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/jdi/LocalVariablesUtil.java b/java/debugger/impl/src/com/intellij/debugger/jdi/LocalVariablesUtil.java new file mode 100644 index 000000000000..a9f691d9195b --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/jdi/LocalVariablesUtil.java @@ -0,0 +1,178 @@ +/* + * Copyright 2000-2013 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.debugger.jdi; + +import com.intellij.openapi.diagnostic.Logger; +import com.sun.jdi.InternalException; +import com.sun.jdi.StackFrame; +import com.sun.jdi.Value; +import com.sun.jdi.VirtualMachine; + +import java.lang.reflect.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * From JDI sources: + * + validateStackFrame(); + validateMirrors(variables); + + int count = variables.size(); + JDWP.StackFrame.GetValues.SlotInfo[] slots = + new JDWP.StackFrame.GetValues.SlotInfo[count]; + + for (int i=0; i ourSlotInfoClass; + private static Constructor slotInfoConstructor; + private static Class ourGetValuesClass; + private static Method ourEnqueueMethod; + private static Method ourWaitForReplyMethod; + + static { + boolean success = false; + try { + ourSlotInfoClass = Class.forName("com.sun.tools.jdi.JDWP$StackFrame$GetValues$SlotInfo"); + slotInfoConstructor = ourSlotInfoClass.getDeclaredConstructor(int.class, byte.class); + slotInfoConstructor.setAccessible(true); + + ourGetValuesClass = Class.forName("com.sun.tools.jdi.JDWP$StackFrame$GetValues"); + ourEnqueueMethod = findMethod(ourGetValuesClass, "enqueueCommand"); + ourEnqueueMethod.setAccessible(true); + ourWaitForReplyMethod = findMethod(ourGetValuesClass, "waitForReply"); + ourWaitForReplyMethod.setAccessible(true); + + success = true; + } + catch (Throwable e) { + LOG.info(e); + } + ourInitializationOk = success; + } + + public static Map fetchValues(StackFrame frame, Collection vars) throws Exception { + if (!ourInitializationOk) { + return Collections.emptyMap(); + } + final Field frameIdField = frame.getClass().getDeclaredField("id"); + frameIdField.setAccessible(true); + final Object frameId = frameIdField.get(frame); + + final VirtualMachine vm = frame.virtualMachine(); + final Method stateMethod = vm.getClass().getDeclaredMethod("state"); + stateMethod.setAccessible(true); + + Object slotInfoArray = createSlotInfoArray(vars); + + Object ps; + final Object vmState = stateMethod.invoke(vm); + synchronized(vmState) { + ps = ourEnqueueMethod.invoke(null, vm, frame.thread(), frameId, slotInfoArray); + } + + try { + final Object reply = ourWaitForReplyMethod.invoke(null, vm, ps); + final Field valuesField = reply.getClass().getDeclaredField("values"); + valuesField.setAccessible(true); + final Value[] values = (Value[])valuesField.get(reply); + if (vars.size() != values.length) { + throw new InternalException("Wrong number of values returned from target VM"); + } + final Map map = new HashMap(vars.size()); + int idx = 0; + for (DecompiledLocalVariable var : vars) { + map.put(var, values[idx++]); + } + return map; + } + catch (InvocationTargetException e) { + final Throwable target = e.getTargetException(); + if (target instanceof Exception) { + throw (Exception)target; + } + throw e; + } + } + + private static Object createSlotInfoArray(Collection vars) throws Exception { + final Object arrayInstance = Array.newInstance(ourSlotInfoClass, vars.size()); + + int idx = 0; + for (DecompiledLocalVariable var : vars) { + final Object info = slotInfoConstructor.newInstance(var.getSlot(), (byte)var.getSignature().charAt(0)); + Array.set(arrayInstance, idx++, info); + } + + return arrayInstance; + } + + private static Method findMethod(Class aClass, String methodName) throws NoSuchMethodException { + for (Method method : aClass.getDeclaredMethods()) { + if (methodName.equals(method.getName())) { + return method; + } + } + throw new NoSuchMethodException(aClass.getName() + "." + methodName); + } + +} diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/FrameVariablesTree.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/FrameVariablesTree.java index 8f851f37cb6e..bfd501b4e715 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/FrameVariablesTree.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/FrameVariablesTree.java @@ -30,11 +30,10 @@ import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; import com.intellij.debugger.engine.evaluation.TextWithImports; import com.intellij.debugger.engine.evaluation.TextWithImportsImpl; import com.intellij.debugger.engine.events.DebuggerCommandImpl; +import com.intellij.debugger.engine.jdi.StackFrameProxy; import com.intellij.debugger.impl.DebuggerContextImpl; import com.intellij.debugger.impl.DebuggerSession; -import com.intellij.debugger.jdi.LocalVariableProxyImpl; -import com.intellij.debugger.jdi.StackFrameProxyImpl; -import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; +import com.intellij.debugger.jdi.*; import com.intellij.debugger.settings.ViewsGeneralSettings; import com.intellij.debugger.ui.impl.watch.*; import com.intellij.lang.java.JavaLanguage; @@ -53,9 +52,9 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.text.CharArrayUtil; import com.intellij.util.ui.tree.TreeModelAdapter; import com.intellij.xdebugger.XDebuggerBundle; -import com.sun.jdi.AbsentInformationException; -import com.sun.jdi.ObjectCollectedException; -import com.sun.jdi.Value; +import com.sun.jdi.*; +import gnu.trove.TIntObjectHashMap; +import gnu.trove.TObjectProcedure; import javax.swing.event.TreeModelEvent; import javax.swing.tree.TreeModel; @@ -167,10 +166,25 @@ public class FrameVariablesTree extends DebuggerTree { final Collection argValues = frame.getArgumentValues(); int index = 0; for (Value argValue : argValues) { - final ArgumentValueDescriptorImpl descriptor = myNodeManager.getArgumentValueDescriptor(stackDescriptor, index++, argValue); + final ArgumentValueDescriptorImpl descriptor = myNodeManager.getArgumentValueDescriptor(stackDescriptor, index++, argValue, null); final DebuggerTreeNodeImpl variableNode = myNodeManager.createNode(descriptor, evaluationContext); myChildren.add(variableNode); } + final List decompiled = collectVariablesFromBytecode(frame, argValues.size()); + if (!decompiled.isEmpty()) { + try { + final Map values = LocalVariablesUtil.fetchValues(frame.getStackFrame(), decompiled); + for (DecompiledLocalVariable var : decompiled) { + final Value value = values.get(var); + final ArgumentValueDescriptorImpl descriptor = myNodeManager.getArgumentValueDescriptor(stackDescriptor, var.getSlot(), value, var.getName()); + final DebuggerTreeNodeImpl variableNode = myNodeManager.createNode(descriptor, evaluationContext); + myChildren.add(variableNode); + } + } + catch (Exception ex) { + LOG.info(ex); + } + } myChildren.add(myNodeManager.createMessageNode(MessageDescriptor.LOCAL_VARIABLES_INFO_UNAVAILABLE)); } else { @@ -180,6 +194,56 @@ public class FrameVariablesTree extends DebuggerTree { } } + private static List collectVariablesFromBytecode(final StackFrameProxy frame, int argumentCount) throws EvaluateException { + if (!frame.getVirtualMachine().canGetBytecodes()) { + return Collections.emptyList(); + } + try { + final Location location = frame.location(); + final Method method = location.method(); + final Location methodLocation = method.location(); + if (methodLocation == null || methodLocation.codeIndex() < 0) { + // native or abstract method + return Collections.emptyList(); + } + final byte[] bytecodes = method.bytecodes(); + if (bytecodes != null && bytecodes.length > 0) { + final int firstLocalVariableSlot = argumentCount + (method.isStatic()? 0 : 1); + final long instructionIndex = location.codeIndex(); + final TIntObjectHashMap usedVars = new TIntObjectHashMap(); + new InstructionParser(bytecodes, instructionIndex) { + @Override + protected void localVariableInstructionFound(int opcode, int slot, String typeSignature) { + if (slot >= firstLocalVariableSlot) { + DecompiledLocalVariable variable = usedVars.get(slot); + if (variable == null || !typeSignature.equals(variable.getSignature())) { + variable = new DecompiledLocalVariable(slot, "slot_" + slot, typeSignature); + usedVars.put(slot, variable); + } + } + } + }.parse(); + + if (usedVars.isEmpty()) { + return Collections.emptyList(); + } + final List vars = new ArrayList(usedVars.size()); + usedVars.forEachValue(new TObjectProcedure() { + @Override + public boolean execute(DecompiledLocalVariable var) { + vars.add(var); + return true; + } + }); + Collections.sort(vars, DecompiledLocalVariable.COMPARATOR); + return vars; + } + } + catch (UnsupportedOperationException ignored) { + } + return Collections.emptyList(); + } + private static Map getVisibleVariables(final StackFrameDescriptorImpl stackDescriptor) throws EvaluateException { final StackFrameProxyImpl frame = stackDescriptor.getFrameProxy(); if (frame == null) { diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/ArgumentValueDescriptorImpl.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/ArgumentValueDescriptorImpl.java index 3b87a5f0d6b4..dae41df6feef 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/ArgumentValueDescriptorImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/ArgumentValueDescriptorImpl.java @@ -30,17 +30,21 @@ import com.intellij.util.IncorrectOperationException; import com.sun.jdi.PrimitiveValue; import com.sun.jdi.Value; +import java.util.Stack; + public class ArgumentValueDescriptorImpl extends ValueDescriptorImpl{ private final int myIndex; private final Value myValue; private String myName; private boolean myParameterNameCalcutated; + private final String myDefaultName; - public ArgumentValueDescriptorImpl(Project project, int index, Value value) { + public ArgumentValueDescriptorImpl(Project project, int index, Value value, String name) { super(project); myIndex = index; myValue = value; - myName = "arg" + String.valueOf(index); + myDefaultName = name != null ? name : "arg" + String.valueOf(index); + myName = myDefaultName; setLvalue(true); } @@ -61,6 +65,55 @@ public class ArgumentValueDescriptorImpl extends ValueDescriptorImpl{ myName = param.getName(); myParameterNameCalcutated = true; } + else { + // treat myIndex as a variable slot index + final PsiCodeBlock body = method.getBody(); + if (body != null) { + final StringBuilder nameBuilder = new StringBuilder(); + nameBuilder.append(myDefaultName); + try { + final int startSlot = params.getParametersCount() + (method.hasModifierProperty(PsiModifier.STATIC)? 0 : 1); + body.accept(new JavaRecursiveElementVisitor() { + private int myCurrentSlotIndex = startSlot; + private final Stack myIndexStack = new Stack(); + @Override + public void visitCodeBlock(PsiCodeBlock block) { + myIndexStack.push(myCurrentSlotIndex); + try { + super.visitCodeBlock(block); + } + finally { + myCurrentSlotIndex = myIndexStack.pop(); + } + } + + @Override + public void visitLocalVariable(PsiLocalVariable variable) { + if (myCurrentSlotIndex == myIndex) { + if (nameBuilder.length() == myDefaultName.length()) { + nameBuilder.append(": "); + } + else { + nameBuilder.append("|"); + } + nameBuilder.append(variable.getName()); + } + final PsiType varType = variable.getType(); + myCurrentSlotIndex += (varType == PsiType.DOUBLE || varType == PsiType.LONG)? 2 : 1; + } + + @Override + public void visitClass(PsiClass aClass) { + // skip local and anonymous classes + } + + }); + } + finally { + myName = nameBuilder.toString(); + } + } + } } } } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/NodeDescriptorFactoryImpl.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/NodeDescriptorFactoryImpl.java index ba0c43f4c3e4..a98afc7c9eb8 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/NodeDescriptorFactoryImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/NodeDescriptorFactoryImpl.java @@ -152,8 +152,8 @@ public class NodeDescriptorFactoryImpl implements NodeDescriptorFactory { return getDescriptor(parent, new LocalData((LocalVariableProxyImpl)local)); } - public ArgumentValueDescriptorImpl getArgumentValueDescriptor(NodeDescriptor parent, int index, Value value) { - return getDescriptor(parent, new ArgValueData(index, value)); + public ArgumentValueDescriptorImpl getArgumentValueDescriptor(NodeDescriptor parent, int index, Value value, final String name) { + return getDescriptor(parent, new ArgValueData(index, value, name)); } public StackFrameDescriptorImpl getStackFrameDescriptor(NodeDescriptorImpl parent, StackFrameProxyImpl frameProxy) { diff --git a/java/debugger/openapi/src/com/intellij/debugger/engine/jdi/VirtualMachineProxy.java b/java/debugger/openapi/src/com/intellij/debugger/engine/jdi/VirtualMachineProxy.java index 4f4cb8243652..ab116c9e2fc5 100644 --- a/java/debugger/openapi/src/com/intellij/debugger/engine/jdi/VirtualMachineProxy.java +++ b/java/debugger/openapi/src/com/intellij/debugger/engine/jdi/VirtualMachineProxy.java @@ -26,6 +26,8 @@ import java.util.List; public interface VirtualMachineProxy { List allClasses(); + boolean canGetBytecodes(); + boolean versionHigher(String version); boolean canWatchFieldModification();