changeset 101:9813956b6840

Fixed line number table handling for DEX files. * dex/Code.java: Simplified line number table handling. * (findLabels): Removed obsolete hacks for line number labels. * dex/DebugInfo.java (getLineNumbers): Returns a separate line number table. * (interpret): Implemented handling of DBG_SET_FILE operation. * (emitLineNumber): Switched to new table, thereby fixing duplication issue. * (LineNumber): Internal representation of line number entries. * rewriter/DexRewriter.java (visitLineNumber): Implemented.
author Michael Starzinger <michi@complang.tuwien.ac.at>
date Mon, 28 Mar 2011 22:59:38 +0200
parents 8d6b719ff9a1
children 8f8bd597644c
files src/main/java/org/icedrobot/daneel/dex/Code.java src/main/java/org/icedrobot/daneel/dex/DebugInfo.java src/main/java/org/icedrobot/daneel/rewriter/DexRewriter.java
diffstat 3 files changed, 72 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/org/icedrobot/daneel/dex/Code.java	Mon Mar 28 22:11:23 2011 +0200
+++ b/src/main/java/org/icedrobot/daneel/dex/Code.java	Mon Mar 28 22:59:38 2011 +0200
@@ -44,7 +44,7 @@
 import java.util.List;
 import java.util.Map;
 
-import org.icedrobot.daneel.dex.DebugInfo.LineNumberLabel;
+import org.icedrobot.daneel.dex.DebugInfo.LineNumber;
 import org.icedrobot.daneel.dex.DebugInfo.LocalVariable;
 import org.icedrobot.daneel.util.BufferUtil;
 import org.icedrobot.daneel.util.TypeUtil;
@@ -101,14 +101,6 @@
         // Keep a separate buffer for the instructions array.
         insns = (ShortBuffer) buffer.asShortBuffer().limit(insnsSize);
 
-        // Parse any associated debug info. Remember to do this before finding
-        // labels of branch targets, because it generates line number labels.
-        if (debugInfoOff != 0) {
-            ByteBuffer buf = dex.getDataBuffer(debugInfoOff);
-            debugInfo = DebugInfo.parse(buf, dex, this);
-        } else
-            debugInfo = null;
-
         // Find labels inside the instruction stream and also parse possible
         // in-code structures at such positions.
         findLabels(buffer);
@@ -153,6 +145,13 @@
                 }
             }
         }
+
+        // Parse any associated debug information.
+        if (debugInfoOff != 0) {
+            ByteBuffer buf = dex.getDataBuffer(debugInfoOff);
+            debugInfo = DebugInfo.parse(buf, dex, this);
+        } else
+            debugInfo = null;
     }
 
     /**
@@ -196,6 +195,10 @@
                 visitor.visitLocalVariable(local.name, local.type,
                         local.startLabel, local.endLabel, local.regNum);
 
+        // Visit line number information if available.
+        if (debugInfo != null)
+            for (LineNumber line : debugInfo.getLineNumbers())
+                visitor.visitLineNumber(line.source, line.line, line.label);
     }
 
     /**
@@ -237,13 +240,6 @@
             if ((label = labelMap.get(pos)) != null)
                 v.visitLabel(label);
 
-            // Visit a line number entry at the current position.
-            if (label instanceof LineNumberLabel) {
-                LineNumberLabel line = (LineNumberLabel) label;
-                // XXX Correctly pass source file as well.
-                v.visitLineNumber(null, line.line, label);
-            }
-
             // Switch over all possible opcodes.
             switch (op) {
             case NOP:
@@ -832,7 +828,7 @@
      * @param label The label to be associated with that position.
      * @throws DexParseException In case there already is a label.
      */
-    void putLabel(int pos, Label label) {
+    private void putLabel(int pos, Label label) {
         if (pos < 0 || pos >= insnsSize)
             throw new DexParseException("Label position out of range: " + pos);
         if (labelMap.containsKey(pos))
@@ -924,10 +920,6 @@
                 // Syntax: op vAA, +BBBBBBBB
                 off = (s2 & 0xffff) | (s3 << 16);
                 buffer.position(buffer.position() + (pos + off) * 2);
-                // XXX This is a hack --->
-                if (labelMap.get(pos + off) instanceof LineNumberLabel)
-                    labelMap.remove(pos + off);
-                // XXX <--- This is a hack.
                 putLabel(pos + off, new PackedSwitchLabel(buffer, pos));
                 buffer.reset();
                 break;
@@ -937,10 +929,6 @@
                 // Syntax: op vAA, +BBBBBBBB
                 off = (s2 & 0xffff) | (s3 << 16);
                 buffer.position(buffer.position() + (pos + off) * 2);
-                // XXX This is a hack --->
-                if (labelMap.get(pos + off) instanceof LineNumberLabel)
-                    labelMap.remove(pos + off);
-                // XXX <--- This is a hack.
                 putLabel(pos + off, new SparseSwitchLabel(buffer, pos));
                 buffer.reset();
                 break;
@@ -961,19 +949,11 @@
      */
     private boolean skipInCodeData(ShortBuffer insns, int pos) {
         Label label = labelMap.get(pos);
-        if (label instanceof PackedSwitchLabel) {
-            insns.position(pos + ((PackedSwitchLabel) label).length());
-            return true;
-        }
-        if (label instanceof SparseSwitchLabel) {
-            insns.position(pos + ((SparseSwitchLabel) label).length());
+        if (label instanceof InCodeDataLabel) {
+            insns.position(pos + ((InCodeDataLabel) label).length());
             return true;
-        }
-        if (label instanceof FillArrayDataLabel) {
-            insns.position(pos + ((FillArrayDataLabel) label).length());
-            return true;
-        }
-        return false;
+        } else
+            return false;
     }
 
     /**
@@ -1000,7 +980,7 @@
     /**
      * In-code data structure for packed-switch instructions.
      */
-    private class PackedSwitchLabel extends Label {
+    private class PackedSwitchLabel extends InCodeDataLabel {
         private final int size;
         final int firstKey;
         final Label[] targets;
@@ -1015,20 +995,16 @@
                 targets[i] = putLabel(pos + buffer.getInt(), true);
         }
 
+        @Override
         public int length() {
             return (size * 2) + 4;
         }
-
-        @Override
-        public boolean isJumpTarget() {
-            return false;
-        }
     };
 
     /**
      * In-code data structure for sparse-switch instructions.
      */
-    private class SparseSwitchLabel extends Label {
+    private class SparseSwitchLabel extends InCodeDataLabel {
         private final int size;
         final int[] keys;
         final Label[] targets;
@@ -1043,20 +1019,16 @@
                 targets[i] = putLabel(pos + buffer.getInt(), true);
         }
 
+        @Override
         public int length() {
             return (size * 4) + 2;
         }
-
-        @Override
-        public boolean isJumpTarget() {
-            return false;
-        }
     };
 
     /**
      * In-code data structure for fill-array-data instructions.
      */
-    private static class FillArrayDataLabel extends Label {
+    private static class FillArrayDataLabel extends InCodeDataLabel {
         final int elementWidth;
         final int size;
         final ByteBuffer data;
@@ -1069,9 +1041,18 @@
             data = (ByteBuffer) buffer.slice().limit(size * elementWidth);
         }
 
+        @Override
         public int length() {
             return (size * elementWidth + 1) / 2 + 4;
         }
+    };
+
+    /**
+     * A label as defined by the interface, but representing in-code data of a
+     * particular length that can be skipped.
+     */
+    static abstract class InCodeDataLabel extends Label {
+        public abstract int length();
 
         @Override
         public boolean isJumpTarget() {
@@ -1083,7 +1064,7 @@
      * A label as defined by the interface, but enriched with additional debug
      * information.
      */
-    static class DebugLabel extends Label {
+    private static class DebugLabel extends Label {
         private final int pos;
         private boolean jumpTarget;
 
--- a/src/main/java/org/icedrobot/daneel/dex/DebugInfo.java	Mon Mar 28 22:11:23 2011 +0200
+++ b/src/main/java/org/icedrobot/daneel/dex/DebugInfo.java	Mon Mar 28 22:59:38 2011 +0200
@@ -41,7 +41,7 @@
 import java.util.LinkedList;
 import java.util.List;
 
-import org.icedrobot.daneel.dex.Code.DebugLabel;
+import org.icedrobot.daneel.dex.Code.InCodeDataLabel;
 import org.icedrobot.daneel.util.BufferUtil;
 import org.icedrobot.daneel.util.TypeUtil;
 
@@ -88,6 +88,8 @@
 
     private final List<LocalVariable> localVariables;
 
+    private final List<LineNumber> lineNumbers;
+
     private DebugInfo(ByteBuffer buffer, DexFile dex, Code code) {
         this.dex = dex;
         this.code = code;
@@ -105,6 +107,7 @@
         // Interpret all subsequent state machine bytecodes to compute local
         // variable and line number information.
         localVariables = new LinkedList<LocalVariable>();
+        lineNumbers = new LinkedList<LineNumber>();
         interpret(buffer);
     }
 
@@ -124,13 +127,25 @@
      * Returns the list of all local variable information as it was emitted by
      * the byte-coded state machine.
      * 
-     * @return The list as specified above.
+     * @return The list as specified above, never {@code null}.
      */
     public List<LocalVariable> getLocalVariables() {
         return localVariables;
     }
 
     /**
+     * Returns the list of all line numbers as emitted by the byte-coded state
+     * machine. Entries are sorted by instruction address low-to-high. Every
+     * entry denotes the original source file and line for the instructions
+     * following the given label.
+     * 
+     * @return The list as specified above, never {@code null}.
+     */
+    public List<LineNumber> getLineNumbers() {
+        return lineNumbers;
+    }
+
+    /**
      * Interprets the byte-coded state machine that is part of a {@code
      * debug_info_item} and emits all the local variable and line number
      * information.
@@ -142,7 +157,7 @@
         // The five state machine registers.
         int addr = 0;
         int line = lineStart;
-        // We ignore "sourceFile" for now.
+        String sourceFile = null;
         // We ignore "prologueEnd" for now.
         // We ignore "epilogueBegin" for now.
 
@@ -241,15 +256,15 @@
                 break;
 
             case DBG_SET_FILE:
-                // nameIdx = BufferUtil.getULEB128(buffer) - 1;
-                // break;
-                throw new UnsupportedOperationException("Not yet implemented!");
+                nameIdx = BufferUtil.getULEB128(buffer) - 1;
+                sourceFile = resolveString(nameIdx);
+                break;
 
             default:
                 int adjustedOpcode = opcode - DBG_FIRST_SPECIAL;
                 line += (adjustedOpcode % 15) - 4;
                 addr += (adjustedOpcode / 15);
-                emitLineNumber(line, addr);
+                emitLineNumber(line, sourceFile, addr);
                 break;
             }
         }
@@ -303,22 +318,19 @@
     }
 
     /**
-     * Emits a new line number entry. Line numbers are stored as part of the
-     * label they are associated with.
+     * Emits a new line number entry. Every entry denotes the original source
+     * file and line for the instructions following the given offset.
      * 
      * @param line The given line number.
+     * @param source The source file name the line number makes reference to.
      * @param addr The given instruction offset in 16-bit code units.
      */
-    private void emitLineNumber(int line, int addr) {
-        Label label = new LineNumberLabel(addr, line);
-        try {
-            code.putLabel(addr, label);
-        } catch (DexParseException e) {
-            // XXX This can happen in case two line numbers are at the same
-            // address. Think about how to solve, fix it and remove this
-            // try-catch guard.
-            System.out.printf("DUPLICATE: line=%d, address=%d\n", line, addr);
-        }
+    private void emitLineNumber(int line, String source, int addr) {
+        Label label = code.putLabel(addr, false);
+        if (label instanceof InCodeDataLabel)
+            return;
+        LineNumber lineNumber = new LineNumber(line, source, label);
+        lineNumbers.add(lineNumber);
     }
 
     /**
@@ -344,20 +356,18 @@
     };
 
     /**
-     * A label as defined by the interface, but enriched with additional debug
-     * and line number information.
+     * An internal representation of line number information as emitted by the
+     * {@code debug_info_item} state machine.
      */
-    static class LineNumberLabel extends DebugLabel {
+    static class LineNumber {
         final int line;
+        final String source;
+        final Label label;
 
-        public LineNumberLabel(int pos, int line) {
-            super(pos, false);
+        public LineNumber(int line, String source, Label label) {
             this.line = line;
-        }
-
-        @Override
-        public String toString() {
-            return String.format("%s[line=%d]", super.toString(), line);
+            this.source = source;
+            this.label = label;
         }
     };
 }
--- a/src/main/java/org/icedrobot/daneel/rewriter/DexRewriter.java	Mon Mar 28 22:11:23 2011 +0200
+++ b/src/main/java/org/icedrobot/daneel/rewriter/DexRewriter.java	Mon Mar 28 22:59:38 2011 +0200
@@ -327,7 +327,7 @@
 
         @Override
         public void visitLineNumber(String source, int line, Label start) {
-            // XXX Ignore debug information for now.
+            mv.visitLineNumber(line, getASMLabel(start));
         }
 
         @Override