Repository navigation
Expand file tree
/
Copy pathLineRemap.java
More file actions
97 lines (90 loc) · 4.65 KB
/
Copy pathLineRemap.java
File metadata and controls
97 lines (90 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// Makes JaCoCo's HTML report line up with Vineflower-decompiled sources.
//
// Vanilla classes carry Mojang's original line numbers, which don't match the
// decompiled source. Vineflower (--bytecode-source-mapping --dump-code-lines)
// records original->decompiled line pairs per source file. This tool rewrites
// the LineNumberTable of every in-scope class to the decompiled lines and writes
// the result to <classes-out>. The server runs on these classes, so coverage
// data and the report come from the same bytecode (JaCoCo's probe placement
// depends on line numbers). Only debug info changes, never behaviour.
//
// Usage: java -cp jacococli.jar LineRemap.java <server.jar> <sources.jar> <classes-out> <pkg-prefix>...
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.*;
import java.util.*;
import java.util.zip.*;
import org.jacoco.cli.internal.asm.*;
public class LineRemap {
public static void main(String[] args) throws Exception {
Path serverJar = Path.of(args[0]), sourcesJar = Path.of(args[1]), classesOut = Path.of(args[2]);
List<String> prefixes = Arrays.asList(args).subList(3, args.length);
Map<String, Map<Integer, Integer>> mappings = readMappings(sourcesJar);
int remapped = 0, unmapped = 0;
try (ZipFile zip = new ZipFile(serverJar.toFile())) {
for (ZipEntry e : Collections.list(zip.entries())) {
String name = e.getName();
if (!name.endsWith(".class") || prefixes.stream().noneMatch(name::startsWith)) continue;
byte[] original = zip.getInputStream(e).readAllBytes();
ClassReader reader = new ClassReader(original);
String pkg = name.substring(0, name.lastIndexOf('/') + 1);
String[] sourceFile = new String[1];
reader.accept(new ClassVisitor(Opcodes.ASM9) {
@Override public void visitSource(String source, String debug) { sourceFile[0] = source; }
}, ClassReader.SKIP_CODE);
Map<Integer, Integer> lines = sourceFile[0] == null ? null : mappings.get(pkg + sourceFile[0]);
byte[] out = original;
if (lines != null) {
out = rewrite(reader, lines);
remapped++;
} else {
unmapped++;
}
Path dest = classesOut.resolve(name);
Files.createDirectories(dest.getParent());
Files.write(dest, out);
}
}
System.out.printf("remapped %d classes, %d without a line mapping%n", remapped, unmapped);
}
// Original line -> decompiled line, keyed by "net/minecraft/.../Foo.java".
static Map<String, Map<Integer, Integer>> readMappings(Path sourcesJar) throws IOException {
Map<String, Map<Integer, Integer>> result = new HashMap<>();
try (ZipInputStream in = new ZipInputStream(Files.newInputStream(sourcesJar))) {
for (ZipEntry e; (e = in.getNextEntry()) != null; ) {
byte[] extra = e.getExtra();
if (extra == null || extra.length < 5) continue;
ByteBuffer buf = ByteBuffer.wrap(extra).order(ByteOrder.LITTLE_ENDIAN);
if (buf.getShort() != 0x4646) continue;
buf.getShort(); // length
buf.get(); // version
Map<Integer, Integer> lines = new HashMap<>();
while (buf.remaining() >= 4) {
int from = Short.toUnsignedInt(buf.getShort());
int to = Short.toUnsignedInt(buf.getShort());
lines.merge(from, to, Math::min);
}
result.put(e.getName(), lines);
}
}
return result;
}
// Lines Vineflower didn't map are dropped, so JaCoCo attributes those
// instructions to the previous mapped line in the method.
static byte[] rewrite(ClassReader reader, Map<Integer, Integer> lines) {
ClassWriter writer = new ClassWriter(0);
reader.accept(new ClassVisitor(Opcodes.ASM9, writer) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] exceptions) {
return new MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, desc, sig, exceptions)) {
@Override public void visitLineNumber(int line, Label start) {
Integer mapped = lines.get(line);
if (mapped != null) super.visitLineNumber(mapped, start);
}
};
}
}, 0);
return writer.toByteArray();
}
}