1 import java.io.*;
2 
3 class InstallSDE {
4     static final boolean verbose = true;
5     static final String nameSDE = "SourceDebugExtension";
6 
7     byte[] orig;
8     byte[] sdeAttr;
9     byte[] gen;
10 
11     int origPos = 0;
12     int genPos = 0;
13 
14     int sdeIndex;
15 
main(String[] args)16     public static void main(String[] args) throws IOException {
17         if (args.length == 2) {
18             install(new File(args[0]), new File(args[1]));
19         } else if (args.length == 3) {
20             install(new File(args[0]), new File(args[1]), new File(args[2]));
21         } else {
22             abort("Usage: <command> <input class file> " +
23                                "<attribute file> <output class file name>\n" +
24                   "<command> <input/output class file> <attribute file>");
25         }
26     }
27 
install(File inClassFile, File attrFile, File outClassFile)28     static void install(File inClassFile, File attrFile, File outClassFile)
29                                                             throws IOException {
30         new InstallSDE(inClassFile, attrFile, outClassFile);
31     }
32 
install(File inOutClassFile, File attrFile)33     static void install(File inOutClassFile, File attrFile) throws IOException {
34         File tmpFile = new File(inOutClassFile.getPath() + "tmp-out");
35         File tmpInOutClassFile = new File(inOutClassFile.getPath() + "tmp-in");
36 
37         // Workaround delayed file deletes on Windows using a tmp file name
38         if (!inOutClassFile.renameTo(tmpInOutClassFile)) {
39             throw new IOException("tmp copy of inOutClassFile failed");
40         }
41 
42         new InstallSDE(tmpInOutClassFile, attrFile, tmpFile);
43 
44         if (!tmpInOutClassFile.delete()) {
45             throw new IOException("tmpInOutClassFile.delete() failed");
46         }
47         if (!tmpFile.renameTo(inOutClassFile)) {
48             throw new IOException("tmpFile.renameTo(inOutClassFile) failed");
49         }
50     }
51 
abort(String msg)52     static void abort(String msg) {
53         System.err.println(msg);
54         System.exit(1);
55     }
56 
InstallSDE(File inClassFile, File attrFile, File outClassFile)57     InstallSDE(File inClassFile, File attrFile, File outClassFile) throws IOException {
58         if (!inClassFile.exists()) {
59             abort("no such file: " + inClassFile);
60         }
61         if (!attrFile.exists()) {
62             abort("no such file: " + attrFile);
63         }
64 
65         // get the bytes
66         orig = readWhole(inClassFile);
67         sdeAttr = readWhole(attrFile);
68         gen = new byte[orig.length + sdeAttr.length + 100];
69 
70         // do it
71         addSDE();
72 
73         // write result
74         FileOutputStream outStream = new FileOutputStream(outClassFile);
75         outStream.write(gen, 0, genPos);
76         outStream.close();
77     }
78 
readWhole(File input)79     byte[] readWhole(File input) throws IOException {
80         FileInputStream inStream = new FileInputStream(input);
81         int len = (int)input.length();
82         byte[] bytes = new byte[len];
83         if (inStream.read(bytes, 0, len) != len) {
84             abort("expected size: " + len);
85         }
86         inStream.close();
87         return bytes;
88     }
89 
addSDE()90     void addSDE() throws UnsupportedEncodingException {
91         int i;
92         copy(4 + 2 + 2); // magic min/maj version
93         int constantPoolCountPos = genPos;
94         int constantPoolCount = readU2();
95         writeU2(constantPoolCount);
96         // copy old constant pool return index of SDE symbol, if found
97         sdeIndex = copyConstantPool(constantPoolCount);
98         if (sdeIndex < 0) {
99             // if "SourceDebugExtension" symbol not there add it
100             writeUtf8ForSDE();
101 
102             // increment the countantPoolCount
103             sdeIndex = constantPoolCount;
104             ++constantPoolCount;
105             randomAccessWriteU2(constantPoolCountPos, constantPoolCount);
106 
107             if (verbose) {
108                 System.out.println("SourceDebugExtension not found, installed at: " +
109                                    sdeIndex);
110             }
111         } else {
112             if (verbose) {
113                 System.out.println("SourceDebugExtension found at: " +
114                                    sdeIndex);
115             }
116         }
117         copy(2 + 2 + 2);  // access, this, super
118         int interfaceCount = readU2();
119         writeU2(interfaceCount);
120         if (verbose) {
121             System.out.println("interfaceCount: " + interfaceCount);
122         }
123         copy(interfaceCount * 2);
124         copyMembers(); // fields
125         copyMembers(); // methods
126         int attrCountPos = genPos;
127         int attrCount = readU2();
128         writeU2(attrCount);
129         if (verbose) {
130             System.out.println("class attrCount: " + attrCount);
131         }
132         // copy the class attributes, return true if SDE attr found (not copied)
133         if (!copyAttrs(attrCount)) {
134             // we will be adding SDE and it isn't already counted
135             ++attrCount;
136             randomAccessWriteU2(attrCountPos, attrCount);
137             if (verbose) {
138                 System.out.println("class attrCount incremented");
139             }
140         }
141         writeAttrForSDE(sdeIndex);
142     }
143 
copyMembers()144     void copyMembers() {
145         int count = readU2();
146         writeU2(count);
147         if (verbose) {
148             System.out.println("members count: " + count);
149         }
150         for (int i = 0; i < count; ++i) {
151             copy(6); // access, name, descriptor
152             int attrCount = readU2();
153             writeU2(attrCount);
154             if (verbose) {
155                 System.out.println("member attr count: " + attrCount);
156             }
157             copyAttrs(attrCount);
158         }
159     }
160 
copyAttrs(int attrCount)161     boolean copyAttrs(int attrCount) {
162         boolean sdeFound = false;
163         for (int i = 0; i < attrCount; ++i) {
164             int nameIndex = readU2();
165             // don't write old SDE
166             if (nameIndex == sdeIndex) {
167                 sdeFound = true;
168                 if (verbose) {
169                     System.out.println("SDE attr found");
170                 }
171             } else {
172                 writeU2(nameIndex);  // name
173                 int len = readU4();
174                 writeU4(len);
175                 copy(len);
176                 if (verbose) {
177                     System.out.println("attr len: " + len);
178                 }
179             }
180         }
181         return sdeFound;
182     }
183 
writeAttrForSDE(int index)184     void writeAttrForSDE(int index) {
185         writeU2(index);
186         writeU4(sdeAttr.length);
187         for (int i = 0; i < sdeAttr.length; ++i) {
188             writeU1(sdeAttr[i]);
189         }
190     }
191 
randomAccessWriteU2(int pos, int val)192     void randomAccessWriteU2(int pos, int val) {
193         int savePos = genPos;
194         genPos = pos;
195         writeU2(val);
196         genPos = savePos;
197     }
198 
readU1()199     int readU1() {
200         return ((int)orig[origPos++]) & 0xFF;
201     }
202 
readU2()203     int readU2() {
204          int res = readU1();
205         return (res << 8) + readU1();
206     }
207 
readU4()208     int readU4() {
209         int res = readU2();
210         return (res << 16) + readU2();
211     }
212 
writeU1(int val)213     void writeU1(int val) {
214         gen[genPos++] = (byte)val;
215     }
216 
writeU2(int val)217     void writeU2(int val) {
218         writeU1(val >> 8);
219         writeU1(val & 0xFF);
220     }
221 
writeU4(int val)222     void writeU4(int val) {
223         writeU2(val >> 16);
224         writeU2(val & 0xFFFF);
225     }
226 
copy(int count)227     void copy(int count) {
228         for (int i = 0; i < count; ++i) {
229             gen[genPos++] = orig[origPos++];
230         }
231     }
232 
readBytes(int count)233     byte[] readBytes(int count) {
234         byte[] bytes = new byte[count];
235         for (int i = 0; i < count; ++i) {
236             bytes[i] = orig[origPos++];
237         }
238         return bytes;
239     }
240 
writeBytes(byte[] bytes)241     void writeBytes(byte[] bytes) {
242         for (int i = 0; i < bytes.length; ++i) {
243             gen[genPos++] = bytes[i];
244         }
245     }
246 
copyConstantPool(int constantPoolCount)247     int copyConstantPool(int constantPoolCount) throws UnsupportedEncodingException {
248         int sdeIndex = -1;
249         // copy const pool index zero not in class file
250         for (int i = 1; i < constantPoolCount; ++i) {
251             int tag = readU1();
252             writeU1(tag);
253             switch (tag) {
254                 case 7:  // Class
255                 case 8:  // String
256                     copy(2);
257                     break;
258                 case 9:  // Field
259                 case 10: // Method
260                 case 11: // InterfaceMethod
261                 case 3:  // Integer
262                 case 4:  // Float
263                 case 12: // NameAndType
264                 case 18: // InvokeDynamic
265                     copy(4);
266                     break;
267                 case 5:  // Long
268                 case 6:  // Double
269                     copy(8);
270                     break;
271                 case 15: // MethodHandle
272                     copy(3);
273                     break;
274                 case 1:  // Utf8
275                     int len = readU2();
276                     writeU2(len);
277                     byte[] utf8 = readBytes(len);
278                     String str = new String(utf8, "UTF-8");
279                     if (verbose) {
280                         System.out.println(i + " read class attr -- '" + str + "'");
281                     }
282                     if (str.equals(nameSDE)) {
283                         sdeIndex = i;
284                     }
285                     writeBytes(utf8);
286                     break;
287                 default:
288                     abort("unexpected tag: " + tag);
289                     break;
290             }
291         }
292         return sdeIndex;
293     }
294 
writeUtf8ForSDE()295     void writeUtf8ForSDE() {
296         int len = nameSDE.length();
297         writeU1(1); // Utf8 tag
298         writeU2(len);
299         for (int i = 0; i < len; ++i) {
300             writeU1(nameSDE.charAt(i));
301         }
302     }
303 }
304