decompiler: fixed bytecode mapping for anonymous classes

This commit is contained in:
Egor.Ushakov 2014-10-17 15:41:17 +04:00
parent b3681fe952
commit 32e7765874
9 changed files with 95 additions and 52 deletions

View File

@ -43,10 +43,7 @@ import org.jetbrains.java.decompiler.struct.gen.VarType;
import org.jetbrains.java.decompiler.struct.gen.generics.*; import org.jetbrains.java.decompiler.struct.gen.generics.*;
import org.jetbrains.java.decompiler.util.InterpreterUtil; import org.jetbrains.java.decompiler.util.InterpreterUtil;
import java.util.HashMap; import java.util.*;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class ClassWriter { public class ClassWriter {
@ -160,11 +157,11 @@ public class ClassWriter {
DecompilerContext.getLogger().endWriteClass(); DecompilerContext.getLogger().endWriteClass();
} }
public void classToJava(ClassNode node, TextBuffer buffer, int indent) { public void classToJava(ClassNode node, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) {
ClassNode outerNode = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); ClassNode outerNode = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE);
DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node); DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node);
int total_offset_lines = 0; final int startLine = tracer != null ? tracer.getCurrentSourceLine() : 0;
BytecodeMappingTracer dummy_tracer = new BytecodeMappingTracer(); BytecodeMappingTracer dummy_tracer = new BytecodeMappingTracer();
try { try {
@ -176,14 +173,12 @@ public class ClassWriter {
DecompilerContext.getLogger().startWriteClass(cl.qualifiedName); DecompilerContext.getLogger().startWriteClass(cl.qualifiedName);
String lineSeparator = DecompilerContext.getNewLineSeparator();
// write class definition // write class definition
int start_class_def = buffer.length(); int start_class_def = buffer.length();
writeClassDefinition(node, buffer, indent); writeClassDefinition(node, buffer, indent);
// // count lines in class definition the easiest way // // count lines in class definition the easiest way
// total_offset_lines = buffer.substring(start_class_def).toString().split(lineSeparator, -1).length - 1; // startLine = buffer.substring(start_class_def).toString().split(lineSeparator, -1).length - 1;
boolean hasContent = false; boolean hasContent = false;
@ -199,14 +194,14 @@ public class ClassWriter {
if (isEnum) { if (isEnum) {
if (enumFields) { if (enumFields) {
buffer.append(','); buffer.append(',');
buffer.append(lineSeparator); buffer.appendLineSeparator();
} }
enumFields = true; enumFields = true;
} }
else if (enumFields) { else if (enumFields) {
buffer.append(';'); buffer.append(';');
buffer.append(lineSeparator); buffer.appendLineSeparator();
buffer.append(lineSeparator); buffer.appendLineSeparator();
enumFields = false; enumFields = false;
} }
@ -217,11 +212,11 @@ public class ClassWriter {
if (enumFields) { if (enumFields) {
buffer.append(';'); buffer.append(';');
buffer.append(lineSeparator); buffer.appendLineSeparator();
} }
// FIXME: fields don't matter at the moment // FIXME: fields don't matter at the moment
total_offset_lines = buffer.count(lineSeparator, start_class_def); //startLine = buffer.countLines(start_class_def);
// methods // methods
for (StructMethod mt : cl.getMethods()) { for (StructMethod mt : cl.getMethods()) {
@ -232,15 +227,15 @@ public class ClassWriter {
int position = buffer.length(); int position = buffer.length();
if (hasContent) { if (hasContent) {
buffer.append(lineSeparator); buffer.appendLineSeparator();
} }
BytecodeMappingTracer method_tracer = new BytecodeMappingTracer(total_offset_lines); BytecodeMappingTracer method_tracer = new BytecodeMappingTracer(buffer.countLines() + startLine);
boolean methodSkipped = !methodToJava(node, mt, buffer, indent + 1, method_tracer); boolean methodSkipped = !methodToJava(node, mt, buffer, indent + 1, method_tracer);
if (!methodSkipped) { if (!methodSkipped) {
hasContent = true; hasContent = true;
DecompilerContext.getBytecodeSourceMapper().addTracer(cl.qualifiedName, DecompilerContext.getBytecodeSourceMapper().addTracer(cl.qualifiedName,
InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor()), method_tracer); InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor()), method_tracer);
total_offset_lines = (method_tracer.getCurrentSourceLine() + 1); // zero-based line index //startLine = (method_tracer.getCurrentSourceLine() + 1); // zero-based line index
} }
else { else {
buffer.setLength(position); buffer.setLength(position);
@ -257,9 +252,9 @@ public class ClassWriter {
if (hide) continue; if (hide) continue;
if (hasContent) { if (hasContent) {
buffer.append(lineSeparator); buffer.appendLineSeparator();
} }
classToJava(inner, buffer, indent + 1); classToJava(inner, buffer, indent + 1, tracer);
hasContent = true; hasContent = true;
} }
@ -268,7 +263,7 @@ public class ClassWriter {
buffer.appendIndent(indent).append('}'); buffer.appendIndent(indent).append('}');
if (node.type != ClassNode.CLASS_ANONYMOUS) { if (node.type != ClassNode.CLASS_ANONYMOUS) {
buffer.append(lineSeparator); buffer.appendLineSeparator();
} }
} }
finally { finally {
@ -794,7 +789,7 @@ public class ClassWriter {
} }
buffer.append(';'); buffer.append(';');
buffer.append(lineSeparator); buffer.appendLineSeparator();
} }
else { else {
if (!clinit && !dinit) { if (!clinit && !dinit) {
@ -811,7 +806,7 @@ public class ClassWriter {
if (root != null && !methodWrapper.decompiledWithErrors) { // check for existence if (root != null && !methodWrapper.decompiledWithErrors) { // check for existence
try { try {
tracer.incrementCurrentSourceLine(buffer.count(lineSeparator, start_index_method)); tracer.incrementCurrentSourceLine(buffer.countLines(start_index_method));
int startLine = tracer.getCurrentSourceLine(); int startLine = tracer.getCurrentSourceLine();
TextBuffer code = root.toJava(indent + 1, tracer); TextBuffer code = root.toJava(indent + 1, tracer);
@ -845,31 +840,36 @@ public class ClassWriter {
// save total lines // save total lines
// TODO: optimize // TODO: optimize
tracer.setCurrentSourceLine(buffer.count(lineSeparator, start_index_method)); tracer.setCurrentSourceLine(buffer.countLines(start_index_method));
return !hideMethod; return !hideMethod;
} }
private void mapLines(TextBuffer code, StructLineNumberTableAttribute table, BytecodeMappingTracer tracer, int startLine) { private void mapLines(TextBuffer code, StructLineNumberTableAttribute table, BytecodeMappingTracer tracer, int startLine) {
// build line start offsets map // build line start offsets map
HashMap<Integer, Integer> lineStartOffsets = new HashMap<Integer, Integer>(); HashMap<Integer, Set<Integer>> lineStartOffsets = new HashMap<Integer, Set<Integer>>();
for (Map.Entry<Integer, Integer> entry : tracer.getMapping().entrySet()) { for (Map.Entry<Integer, Integer> entry : tracer.getMapping().entrySet()) {
Integer lineNumber = entry.getValue() - startLine; Integer lineNumber = entry.getValue() - startLine;
Integer curr = lineStartOffsets.get(lineNumber); Set<Integer> curr = lineStartOffsets.get(lineNumber);
if (curr == null || curr > entry.getKey()) { if (curr == null) {
lineStartOffsets.put(lineNumber, entry.getKey()); curr = new TreeSet<Integer>(); // requires natural sorting!
} }
curr.add(entry.getKey());
lineStartOffsets.put(lineNumber, curr);
} }
String lineSeparator = DecompilerContext.getNewLineSeparator(); String lineSeparator = DecompilerContext.getNewLineSeparator();
StringBuilder text = code.getOriginalText(); StringBuilder text = code.getOriginalText();
int pos = text.indexOf(lineSeparator); int pos = text.indexOf(lineSeparator);
int lineNumber = 0; int lineNumber = 0;
while (pos != -1) { while (pos != -1) {
Integer startOffset = lineStartOffsets.get(lineNumber); Set<Integer> startOffsets = lineStartOffsets.get(lineNumber);
if (startOffset != null) { if (startOffsets != null) {
int number = table.findLineNumber(startOffset); for (Integer offset : startOffsets) {
int number = table.findLineNumber(offset);
if (number >= 0) { if (number >= 0) {
code.setLineMapping(number, pos); code.setLineMapping(number, pos);
break;
}
} }
} }
pos = text.indexOf(lineSeparator, pos+1); pos = text.indexOf(lineSeparator, pos+1);

View File

@ -256,7 +256,7 @@ public class ClassesProcessor {
new NestedMemberAccess().propagateMemberAccess(root); new NestedMemberAccess().propagateMemberAccess(root);
TextBuffer classBuffer = new TextBuffer(AVERAGE_CLASS_SIZE); TextBuffer classBuffer = new TextBuffer(AVERAGE_CLASS_SIZE);
new ClassWriter().classToJava(root, classBuffer, 0); new ClassWriter().classToJava(root, classBuffer, 0, null);
String lineSeparator = DecompilerContext.getNewLineSeparator(); String lineSeparator = DecompilerContext.getNewLineSeparator();
int total_offset_lines = 0; int total_offset_lines = 0;

View File

@ -115,7 +115,7 @@ public class TextBuffer {
while (currentLine < srcLines.length) { while (currentLine < srcLines.length) {
String line = srcLines[currentLine]; String line = srcLines[currentLine];
int lineEnd = currentLineStartOffset + line.length() + myLineSeparator.length(); int lineEnd = currentLineStartOffset + line.length() + myLineSeparator.length();
if (markOffset >= currentLineStartOffset && markOffset <= lineEnd) { if (markOffset <= lineEnd) {
int requiredLine = markLine - 1; int requiredLine = markLine - 1;
int linesToAdd = requiredLine - dumpedLines; int linesToAdd = requiredLine - dumpedLines;
dumpedLines = requiredLine; dumpedLines = requiredLine;
@ -223,6 +223,14 @@ public class TextBuffer {
return this; return this;
} }
public int countLines() {
return countLines(0);
}
public int countLines(int from) {
return count(myLineSeparator, from);
}
public int count(String substring, int from) { public int count(String substring, int from) {
int count = 0, length = substring.length(), p = from; int count = 0, length = substring.length(), p = from;
while ((p = myStringBuilder.indexOf(substring, p)) > 0) { while ((p = myStringBuilder.indexOf(substring, p)) > 0) {

View File

@ -270,9 +270,11 @@ public class NewExprent extends Exprent {
} }
Exprent methodObject = constructor == null ? null : constructor.getInstance(); Exprent methodObject = constructor == null ? null : constructor.getInstance();
new ClassWriter().classLambdaToJava(child, buf, methodObject, indent); new ClassWriter().classLambdaToJava(child, buf, methodObject, indent);
tracer.incrementCurrentSourceLine(buf.countLines());
} }
else { else {
new ClassWriter().classToJava(child, buf, indent); new ClassWriter().classToJava(child, buf, indent, tracer);
tracer.incrementCurrentSourceLine(buf.countLines());
} }
} }
else if (directArrayInit) { else if (directArrayInit) {

View File

@ -17,7 +17,6 @@ package org.jetbrains.java.decompiler.modules.decompiler.exps;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
import org.jetbrains.java.decompiler.code.CodeConstants; import org.jetbrains.java.decompiler.code.CodeConstants;
import org.jetbrains.java.decompiler.main.ClassWriter; import org.jetbrains.java.decompiler.main.ClassWriter;
@ -92,7 +91,7 @@ public class VarExprent extends Exprent {
if (classdef) { if (classdef) {
ClassNode child = DecompilerContext.getClassProcessor().getMapRootClasses().get(vartype.value); ClassNode child = DecompilerContext.getClassProcessor().getMapRootClasses().get(vartype.value);
new ClassWriter().classToJava(child, buffer, indent); new ClassWriter().classToJava(child, buffer, indent, tracer);
} }
else { else {
String name = null; String name = null;

View File

@ -3,6 +3,11 @@ package pkg;
public class TestClassSimpleBytecodeMapping { public class TestClassSimpleBytecodeMapping {
public int test() { public int test() {
System.out.println("before"); System.out.println("before");
this.run(new Runnable() {
public void run() {
System.out.println("Runnable");
}
});
if(Math.random() > 0.0D) { if(Math.random() > 0.0D) {
System.out.println("0"); System.out.println("0");
return 0; return 0;
@ -11,26 +16,42 @@ public class TestClassSimpleBytecodeMapping {
return 1; return 1;
} }
} }
void run(Runnable var1) {
var1.run();
}
} }
class pkg/TestClassSimpleBytecodeMapping{ class pkg/TestClassSimpleBytecodeMapping{
method run (Ljava/lang/Runnable;)V{
1 20
}
method test ()I{ method test ()I{
0 4 0 4
3 4 3 4
5 4 5 4
8 5 11 5
b 5 14 10
c 5 17 10
d 5 18 10
10 6 19 10
13 6 1c 11
15 6 1f 11
18 7 21 11
19 7 24 12
1a 9 25 12
1d 9 26 14
1f 9 29 14
22 10 2b 14
23 10 2e 15
2f 15
}
}
class pkg/TestClassSimpleBytecodeMapping$1{
method run ()V{
0 7
3 7
5 7
} }
} }

View File

@ -1,5 +1,8 @@
package pkg; package pkg;
import java.lang.Override;
import java.lang.Runnable;
public class TestClassSimpleBytecodeMapping { public class TestClassSimpleBytecodeMapping {
public TestClassSimpleBytecodeMapping() {} public TestClassSimpleBytecodeMapping() {}
@ -8,6 +11,13 @@ public class TestClassSimpleBytecodeMapping {
System.out.println("before"); System.out.println("before");
run(new Runnable() {
@Override
public void run() {
System.out.println("Runnable");
}
});
if(Math.random() > 0) { if(Math.random() > 0) {
System.out.println("0"); System.out.println("0");
return 0; return 0;
@ -17,4 +27,7 @@ public class TestClassSimpleBytecodeMapping {
} }
} }
void run(Runnable r) {
r.run();
}
} }