|
1 /* |
|
2 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*- |
|
26 |
|
27 import java.util.*; |
|
28 import java.util.jar.*; |
|
29 import java.lang.reflect.*; |
|
30 import java.io.*; |
|
31 import xmlkit.XMLKit.Element; |
|
32 |
|
33 /* |
|
34 * @author jrose |
|
35 */ |
|
36 public class ClassReader extends ClassSyntax { |
|
37 |
|
38 private static final CommandLineParser CLP = new CommandLineParser("" |
|
39 + "-source: +> = \n" |
|
40 + "-dest: +> = \n" |
|
41 + "-encoding: +> = \n" |
|
42 + "-jcov $ \n -nojcov !-jcov \n" |
|
43 + "-verbose $ \n -noverbose !-verbose \n" |
|
44 + "-pretty $ \n -nopretty !-pretty \n" |
|
45 + "-keepPath $ \n -nokeepPath !-keepPath \n" |
|
46 + "-keepCP $ \n -nokeepCP !-keepCP \n" |
|
47 + "-keepBytes $ \n -nokeepBytes !-keepBytes \n" |
|
48 + "-parseBytes $ \n -noparseBytes !-parseBytes \n" |
|
49 + "-resolveRefs $ \n -noresolveRefs !-resolveRefs \n" |
|
50 + "-keepOrder $ \n -nokeepOrder !-keepOrder \n" |
|
51 + "-keepSizes $ \n -nokeepSizes !-keepSizes \n" |
|
52 + "-continue $ \n -nocontinue !-continue \n" |
|
53 + "-attrDef & \n" |
|
54 + "-@ >-@ . \n" |
|
55 + "- +? \n" |
|
56 + "\n"); |
|
57 |
|
58 public static void main(String[] ava) throws IOException { |
|
59 ArrayList<String> av = new ArrayList<String>(Arrays.asList(ava)); |
|
60 HashMap<String, String> props = new HashMap<String, String>(); |
|
61 props.put("-encoding:", "UTF8"); // default |
|
62 props.put("-keepOrder", null); // CLI default |
|
63 props.put("-pretty", "1"); // CLI default |
|
64 props.put("-continue", "1"); // CLI default |
|
65 CLP.parse(av, props); |
|
66 //System.out.println(props+" ++ "+av); |
|
67 File source = asFile(props.get("-source:")); |
|
68 File dest = asFile(props.get("-dest:")); |
|
69 String encoding = props.get("-encoding:"); |
|
70 boolean contError = props.containsKey("-continue"); |
|
71 ClassReader options = new ClassReader(); |
|
72 options.copyOptionsFrom(props); |
|
73 /* |
|
74 if (dest == null && av.size() > 1) { |
|
75 dest = File.createTempFile("TestOut", ".dir", new File(".")); |
|
76 dest.delete(); |
|
77 if (!dest.mkdir()) |
|
78 throw new RuntimeException("Cannot create "+dest); |
|
79 System.out.println("Writing results to "+dest); |
|
80 } |
|
81 */ |
|
82 if (av.isEmpty()) { |
|
83 av.add("doit"); //to enter this loop |
|
84 } |
|
85 boolean readList = false; |
|
86 for (String a : av) { |
|
87 if (readList) { |
|
88 readList = false; |
|
89 InputStream fin; |
|
90 if (a.equals("-")) { |
|
91 fin = System.in; |
|
92 } else { |
|
93 fin = new FileInputStream(a); |
|
94 } |
|
95 |
|
96 BufferedReader files = makeReader(fin, encoding); |
|
97 for (String file; (file = files.readLine()) != null;) { |
|
98 doFile(file, source, dest, options, encoding, contError); |
|
99 } |
|
100 if (fin != System.in) { |
|
101 fin.close(); |
|
102 } |
|
103 } else if (a.equals("-@")) { |
|
104 readList = true; |
|
105 } else if (a.startsWith("-")) { |
|
106 throw new RuntimeException("Bad flag argument: " + a); |
|
107 } else if (source.getName().endsWith(".jar")) { |
|
108 doJar(a, source, dest, options, encoding, contError); |
|
109 } else { |
|
110 doFile(a, source, dest, options, encoding, contError); |
|
111 } |
|
112 } |
|
113 } |
|
114 |
|
115 private static File asFile(String str) { |
|
116 return (str == null) ? null : new File(str); |
|
117 } |
|
118 |
|
119 private static void doFile(String a, |
|
120 File source, File dest, |
|
121 ClassReader options, String encoding, |
|
122 boolean contError) throws IOException { |
|
123 if (!contError) { |
|
124 doFile(a, source, dest, options, encoding); |
|
125 } else { |
|
126 try { |
|
127 doFile(a, source, dest, options, encoding); |
|
128 } catch (Exception ee) { |
|
129 System.out.println("Error processing " + source + ": " + ee); |
|
130 } |
|
131 } |
|
132 } |
|
133 |
|
134 private static void doJar(String a, File source, File dest, ClassReader options, |
|
135 String encoding, Boolean contError) throws IOException { |
|
136 try { |
|
137 JarFile jf = new JarFile(source); |
|
138 for (JarEntry je : Collections.list((Enumeration<JarEntry>) jf.entries())) { |
|
139 String name = je.getName(); |
|
140 if (!name.endsWith(".class")) { |
|
141 continue; |
|
142 } |
|
143 doStream(name, jf.getInputStream(je), dest, options, encoding); |
|
144 } |
|
145 } catch (IOException ioe) { |
|
146 if (contError) { |
|
147 System.out.println("Error processing " + source + ": " + ioe); |
|
148 } else { |
|
149 throw ioe; |
|
150 } |
|
151 } |
|
152 } |
|
153 |
|
154 private static void doStream(String a, InputStream in, File dest, |
|
155 ClassReader options, String encoding) throws IOException { |
|
156 |
|
157 File f = new File(a); |
|
158 ClassReader cr = new ClassReader(options); |
|
159 Element e = cr.readFrom(in); |
|
160 |
|
161 OutputStream out; |
|
162 if (dest == null) { |
|
163 //System.out.println(e.prettyString()); |
|
164 out = System.out; |
|
165 } else { |
|
166 File outf = new File(dest, f.isAbsolute() ? f.getName() : f.getPath()); |
|
167 String outName = outf.getName(); |
|
168 File outSubdir = outf.getParentFile(); |
|
169 outSubdir.mkdirs(); |
|
170 int extPos = outName.lastIndexOf('.'); |
|
171 if (extPos > 0) { |
|
172 outf = new File(outSubdir, outName.substring(0, extPos) + ".xml"); |
|
173 } |
|
174 out = new FileOutputStream(outf); |
|
175 } |
|
176 |
|
177 Writer outw = makeWriter(out, encoding); |
|
178 if (options.pretty || !options.keepOrder) { |
|
179 e.writePrettyTo(outw); |
|
180 } else { |
|
181 e.writeTo(outw); |
|
182 } |
|
183 if (out == System.out) { |
|
184 outw.write("\n"); |
|
185 outw.flush(); |
|
186 } else { |
|
187 outw.close(); |
|
188 } |
|
189 } |
|
190 |
|
191 private static void doFile(String a, |
|
192 File source, File dest, |
|
193 ClassReader options, String encoding) throws IOException { |
|
194 File inf = new File(source, a); |
|
195 if (dest != null && options.verbose) { |
|
196 System.out.println("Reading " + inf); |
|
197 } |
|
198 |
|
199 BufferedInputStream in = new BufferedInputStream(new FileInputStream(inf)); |
|
200 |
|
201 doStream(a, in, dest, options, encoding); |
|
202 |
|
203 } |
|
204 |
|
205 public static BufferedReader makeReader(InputStream in, String encoding) throws IOException { |
|
206 // encoding in DEFAULT, '', UTF8, 8BIT, , or any valid encoding name |
|
207 if (encoding.equals("8BIT")) { |
|
208 encoding = EIGHT_BIT_CHAR_ENCODING; |
|
209 } |
|
210 if (encoding.equals("UTF8")) { |
|
211 encoding = UTF8_ENCODING; |
|
212 } |
|
213 if (encoding.equals("DEFAULT")) { |
|
214 encoding = null; |
|
215 } |
|
216 if (encoding.equals("-")) { |
|
217 encoding = null; |
|
218 } |
|
219 Reader inw; |
|
220 in = new BufferedInputStream(in); // add buffering |
|
221 if (encoding == null) { |
|
222 inw = new InputStreamReader(in); |
|
223 } else { |
|
224 inw = new InputStreamReader(in, encoding); |
|
225 } |
|
226 return new BufferedReader(inw); // add buffering |
|
227 } |
|
228 |
|
229 public static Writer makeWriter(OutputStream out, String encoding) throws IOException { |
|
230 // encoding in DEFAULT, '', UTF8, 8BIT, , or any valid encoding name |
|
231 if (encoding.equals("8BIT")) { |
|
232 encoding = EIGHT_BIT_CHAR_ENCODING; |
|
233 } |
|
234 if (encoding.equals("UTF8")) { |
|
235 encoding = UTF8_ENCODING; |
|
236 } |
|
237 if (encoding.equals("DEFAULT")) { |
|
238 encoding = null; |
|
239 } |
|
240 if (encoding.equals("-")) { |
|
241 encoding = null; |
|
242 } |
|
243 Writer outw; |
|
244 if (encoding == null) { |
|
245 outw = new OutputStreamWriter(out); |
|
246 } else { |
|
247 outw = new OutputStreamWriter(out, encoding); |
|
248 } |
|
249 return new BufferedWriter(outw); // add buffering |
|
250 } |
|
251 |
|
252 public Element result() { |
|
253 return cfile; |
|
254 } |
|
255 protected InputStream in; |
|
256 protected ByteArrayOutputStream buf = new ByteArrayOutputStream(1024); |
|
257 protected byte cpTag[]; |
|
258 protected String cpName[]; |
|
259 protected String[] callables; // varies |
|
260 public static final String REF_PREFIX = "#"; |
|
261 // input options |
|
262 public boolean pretty = false; |
|
263 public boolean verbose = false; |
|
264 public boolean keepPath = false; |
|
265 public boolean keepCP = false; |
|
266 public boolean keepBytes = false; |
|
267 public boolean parseBytes = true; |
|
268 public boolean resolveRefs = true; |
|
269 public boolean keepOrder = true; |
|
270 public boolean keepSizes = false; |
|
271 |
|
272 public ClassReader() { |
|
273 super.cfile = new Element("ClassFile"); |
|
274 } |
|
275 |
|
276 public ClassReader(ClassReader options) { |
|
277 this(); |
|
278 copyOptionsFrom(options); |
|
279 } |
|
280 |
|
281 public void copyOptionsFrom(ClassReader options) { |
|
282 pretty = options.pretty; |
|
283 verbose = options.verbose; |
|
284 keepPath = options.keepPath; |
|
285 keepCP = options.keepCP; |
|
286 keepBytes = options.keepBytes; |
|
287 parseBytes = options.parseBytes; |
|
288 resolveRefs = options.resolveRefs; |
|
289 keepSizes = options.keepSizes; |
|
290 keepOrder = options.keepOrder; |
|
291 attrTypes = options.attrTypes; |
|
292 } |
|
293 |
|
294 public void copyOptionsFrom(Map<String, String> options) { |
|
295 if (options.containsKey("-pretty")) { |
|
296 pretty = (options.get("-pretty") != null); |
|
297 } |
|
298 if (options.containsKey("-verbose")) { |
|
299 verbose = (options.get("-verbose") != null); |
|
300 } |
|
301 if (options.containsKey("-keepPath")) { |
|
302 keepPath = (options.get("-keepPath") != null); |
|
303 } |
|
304 if (options.containsKey("-keepCP")) { |
|
305 keepCP = (options.get("-keepCP") != null); |
|
306 } |
|
307 if (options.containsKey("-keepBytes")) { |
|
308 keepBytes = (options.get("-keepBytes") != null); |
|
309 } |
|
310 if (options.containsKey("-parseBytes")) { |
|
311 parseBytes = (options.get("-parseBytes") != null); |
|
312 } |
|
313 if (options.containsKey("-resolveRefs")) { |
|
314 resolveRefs = (options.get("-resolveRefs") != null); |
|
315 } |
|
316 if (options.containsKey("-keepSizes")) { |
|
317 keepSizes = (options.get("-keepSizes") != null); |
|
318 } |
|
319 if (options.containsKey("-keepOrder")) { |
|
320 keepOrder = (options.get("-keepOrder") != null); |
|
321 } |
|
322 if (options.containsKey("-attrDef")) { |
|
323 addAttrTypes(options.get("-attrDef").split(" ")); |
|
324 } |
|
325 if (options.get("-jcov") != null) { |
|
326 addJcovAttrTypes(); |
|
327 } |
|
328 } |
|
329 |
|
330 public Element readFrom(InputStream in) throws IOException { |
|
331 this.in = in; |
|
332 // read the file header |
|
333 int magic = u4(); |
|
334 if (magic != 0xCAFEBABE) { |
|
335 throw new RuntimeException("bad magic number " + Integer.toHexString(magic)); |
|
336 } |
|
337 cfile.setAttr("magic", "" + magic); |
|
338 int minver = u2(); |
|
339 int majver = u2(); |
|
340 cfile.setAttr("minver", "" + minver); |
|
341 cfile.setAttr("majver", "" + majver); |
|
342 readCP(); |
|
343 readClass(); |
|
344 return result(); |
|
345 } |
|
346 |
|
347 public Element readFrom(File file) throws IOException { |
|
348 InputStream in = null; |
|
349 try { |
|
350 in = new FileInputStream(file); |
|
351 Element e = readFrom(new BufferedInputStream(in)); |
|
352 if (keepPath) { |
|
353 e.setAttr("path", file.toString()); |
|
354 } |
|
355 return e; |
|
356 } finally { |
|
357 if (in != null) { |
|
358 in.close(); |
|
359 } |
|
360 } |
|
361 } |
|
362 |
|
363 private void readClass() throws IOException { |
|
364 klass = new Element("Class"); |
|
365 cfile.add(klass); |
|
366 int flags = u2(); |
|
367 String thisk = cpRef(); |
|
368 String superk = cpRef(); |
|
369 klass.setAttr("name", thisk); |
|
370 boolean flagsSync = ((flags & Modifier.SYNCHRONIZED) != 0); |
|
371 flags &= ~Modifier.SYNCHRONIZED; |
|
372 String flagString = flagString(flags, klass); |
|
373 if (!flagsSync) { |
|
374 if (flagString.length() > 0) { |
|
375 flagString += " "; |
|
376 } |
|
377 flagString += "!synchronized"; |
|
378 } |
|
379 klass.setAttr("flags", flagString); |
|
380 klass.setAttr("super", superk); |
|
381 for (int len = u2(), i = 0; i < len; i++) { |
|
382 String interk = cpRef(); |
|
383 klass.add(new Element("Interface", "name", interk)); |
|
384 } |
|
385 Element fields = readMembers("Field"); |
|
386 klass.addAll(fields); |
|
387 Element methods = readMembers("Method"); |
|
388 if (!keepOrder) { |
|
389 methods.sort(); |
|
390 } |
|
391 klass.addAll(methods); |
|
392 readAttributesFor(klass); |
|
393 klass.trimToSize(); |
|
394 if (keepSizes) { |
|
395 attachTo(cfile, formatAttrSizes()); |
|
396 } |
|
397 if (paddingSize != 0) { |
|
398 cfile.setAttr("padding", "" + paddingSize); |
|
399 } |
|
400 } |
|
401 |
|
402 private Element readMembers(String kind) throws IOException { |
|
403 int len = u2(); |
|
404 Element members = new Element(len); |
|
405 for (int i = 0; i < len; i++) { |
|
406 Element member = new Element(kind); |
|
407 int flags = u2(); |
|
408 String name = cpRef(); |
|
409 String type = cpRef(); |
|
410 member.setAttr("name", name); |
|
411 member.setAttr("type", type); |
|
412 member.setAttr("flags", flagString(flags, member)); |
|
413 readAttributesFor(member); |
|
414 member.trimToSize(); |
|
415 members.add(member); |
|
416 } |
|
417 return members; |
|
418 } |
|
419 |
|
420 protected String flagString(int flags, Element holder) { |
|
421 // Superset of Modifier.toString. |
|
422 int kind = 0; |
|
423 if (holder.getName() == "Field") { |
|
424 kind = 1; |
|
425 } |
|
426 if (holder.getName() == "Method") { |
|
427 kind = 2; |
|
428 } |
|
429 StringBuffer sb = new StringBuffer(); |
|
430 for (int i = 0; flags != 0; i++, flags >>>= 1) { |
|
431 if ((flags & 1) != 0) { |
|
432 if (sb.length() > 0) { |
|
433 sb.append(' '); |
|
434 } |
|
435 if (i < modifierNames.length) { |
|
436 String[] names = modifierNames[i]; |
|
437 String name = (kind < names.length) ? names[kind] : null; |
|
438 for (String name2 : names) { |
|
439 if (name != null) { |
|
440 break; |
|
441 } |
|
442 name = name2; |
|
443 } |
|
444 sb.append(name); |
|
445 } else { |
|
446 sb.append("#").append(1 << i); |
|
447 } |
|
448 } |
|
449 } |
|
450 return sb.toString(); |
|
451 } |
|
452 |
|
453 private void readAttributesFor(Element x) throws IOException { |
|
454 Element prevCurrent; |
|
455 Element y = new Element(); |
|
456 if (x.getName() == "Code") { |
|
457 prevCurrent = currentCode; |
|
458 currentCode = x; |
|
459 } else { |
|
460 prevCurrent = currentMember; |
|
461 currentMember = x; |
|
462 } |
|
463 for (int len = u2(), i = 0; i < len; i++) { |
|
464 int ref = u2(); |
|
465 String uname = cpName(ref).intern(); |
|
466 String refName = uname; |
|
467 if (!resolveRefs) { |
|
468 refName = (REF_PREFIX + ref).intern(); |
|
469 } |
|
470 String qname = (x.getName() + "." + uname).intern(); |
|
471 String wname = ("*." + uname).intern(); |
|
472 String type = attrTypes.get(qname); |
|
473 if (type == null || "".equals(type)) { |
|
474 type = attrTypes.get(wname); |
|
475 } |
|
476 if ("".equals(type)) { |
|
477 type = null; |
|
478 } |
|
479 int size = u4(); |
|
480 int[] countVar = attrSizes.get(qname); |
|
481 if (countVar == null) { |
|
482 attrSizes.put(qname, countVar = new int[2]); |
|
483 } |
|
484 countVar[0] += 1; |
|
485 countVar[1] += size; |
|
486 buf.reset(); |
|
487 for (int j = 0; j < size; j++) { |
|
488 buf.write(u1()); |
|
489 } |
|
490 if (type == null && size == 0) { |
|
491 y.add(new Element(uname)); // <Bridge>, etc. |
|
492 } else if (type == null) { |
|
493 //System.out.println("Warning: No attribute type description: "+qname); |
|
494 // write cdata attribute |
|
495 Element a = new Element("Attribute", |
|
496 new String[]{"Name", refName}, |
|
497 buf.toString(EIGHT_BIT_CHAR_ENCODING)); |
|
498 a.addContent(getCPDigest()); |
|
499 y.add(a); |
|
500 } else if (type.equals("")) { |
|
501 // ignore this attribute... |
|
502 } else { |
|
503 InputStream in0 = in; |
|
504 int fileSize0 = fileSize; |
|
505 ByteArrayInputStream in1 = new ByteArrayInputStream(buf.toByteArray()); |
|
506 boolean ok = false; |
|
507 try { |
|
508 in = in1; |
|
509 // parse according to type desc. |
|
510 Element aval; |
|
511 if (type.equals("<Code>...")) { |
|
512 // delve into Code attribute |
|
513 aval = readCode(); |
|
514 } else if (type.equals("<Frame>...")) { |
|
515 // delve into StackMap attribute |
|
516 aval = readStackMap(false); |
|
517 } else if (type.equals("<FrameX>...")) { |
|
518 // delve into StackMap attribute |
|
519 aval = readStackMap(true); |
|
520 } else if (type.startsWith("[")) { |
|
521 aval = readAttributeCallables(type); |
|
522 } else { |
|
523 aval = readAttribute(type); |
|
524 } |
|
525 //System.out.println("attachTo 1 "+y+" <- "+aval); |
|
526 attachTo(y, aval); |
|
527 if (false |
|
528 && in1.available() != 0) { |
|
529 throw new RuntimeException("extra bytes in " + qname + " :" + in1.available()); |
|
530 } |
|
531 ok = true; |
|
532 } finally { |
|
533 in = in0; |
|
534 fileSize = fileSize0; |
|
535 if (!ok) { |
|
536 System.out.println("*** Failed to read " + type); |
|
537 } |
|
538 } |
|
539 } |
|
540 } |
|
541 if (x.getName() == "Code") { |
|
542 currentCode = prevCurrent; |
|
543 } else { |
|
544 currentMember = prevCurrent; |
|
545 } |
|
546 if (!keepOrder) { |
|
547 y.sort(); |
|
548 y.sortAttrs(); |
|
549 } |
|
550 //System.out.println("attachTo 2 "+x+" <- "+y); |
|
551 attachTo(x, y); |
|
552 } |
|
553 private int fileSize = 0; |
|
554 private int paddingSize = 0; |
|
555 private HashMap<String, int[]> attrSizes = new HashMap<String, int[]>(); |
|
556 |
|
557 private Element formatAttrSizes() { |
|
558 Element e = new Element("Sizes"); |
|
559 e.setAttr("fileSize", "" + fileSize); |
|
560 for (Map.Entry<String, int[]> ie : attrSizes.entrySet()) { |
|
561 int[] countVar = ie.getValue(); |
|
562 e.add(new Element("AttrSize", |
|
563 "name", ie.getKey().toString(), |
|
564 "count", "" + countVar[0], |
|
565 "size", "" + countVar[1])); |
|
566 } |
|
567 return e; |
|
568 } |
|
569 |
|
570 private void attachTo(Element x, Object aval0) { |
|
571 if (aval0 == null) { |
|
572 return; |
|
573 } |
|
574 //System.out.println("attachTo "+x+" : "+aval0); |
|
575 if (!(aval0 instanceof Element)) { |
|
576 x.add(aval0); |
|
577 return; |
|
578 } |
|
579 Element aval = (Element) aval0; |
|
580 if (!aval.isAnonymous()) { |
|
581 x.add(aval); |
|
582 return; |
|
583 } |
|
584 for (int imax = aval.attrSize(), i = 0; i < imax; i++) { |
|
585 //%% |
|
586 attachAttrTo(x, aval.getAttrName(i), aval.getAttr(i)); |
|
587 } |
|
588 x.addAll(aval); |
|
589 } |
|
590 |
|
591 private void attachAttrTo(Element x, String aname, String aval) { |
|
592 //System.out.println("attachAttrTo "+x+" : "+aname+"="+aval); |
|
593 String aval0 = x.getAttr(aname); |
|
594 if (aval0 != null) { |
|
595 aval = aval0 + " " + aval; |
|
596 } |
|
597 x.setAttr(aname, aval); |
|
598 } |
|
599 |
|
600 private Element readAttributeCallables(String type) throws IOException { |
|
601 assert (callables == null); |
|
602 callables = getBodies(type); |
|
603 Element res = readAttribute(callables[0]); |
|
604 callables = null; |
|
605 return res; |
|
606 } |
|
607 |
|
608 private Element readAttribute(String type) throws IOException { |
|
609 //System.out.println("readAttribute "+type); |
|
610 Element aval = new Element(); |
|
611 String nextAttrName = null; |
|
612 for (int len = type.length(), next, i = 0; i < len; i = next) { |
|
613 String value; |
|
614 switch (type.charAt(i)) { |
|
615 case '<': |
|
616 assert (nextAttrName == null); |
|
617 next = type.indexOf('>', ++i); |
|
618 String form = type.substring(i, next++); |
|
619 if (form.indexOf('=') < 0) { |
|
620 // elem_placement = '<' elemname '>' |
|
621 assert (aval.attrSize() == 0); |
|
622 assert (aval.isAnonymous()); |
|
623 aval.setName(form.intern()); |
|
624 } else { |
|
625 // attr_placement = '<' attrname '=' (value)? '>' |
|
626 int eqPos = form.indexOf('='); |
|
627 nextAttrName = form.substring(0, eqPos).intern(); |
|
628 if (eqPos != form.length() - 1) { |
|
629 value = form.substring(eqPos + 1); |
|
630 attachAttrTo(aval, nextAttrName, value); |
|
631 nextAttrName = null; |
|
632 } |
|
633 // ...else subsequent type parsing will find the attr value |
|
634 // and add it as "nextAttrName". |
|
635 } |
|
636 continue; |
|
637 case '(': |
|
638 next = type.indexOf(')', ++i); |
|
639 int callee = Integer.parseInt(type.substring(i, next++)); |
|
640 attachTo(aval, readAttribute(callables[callee])); |
|
641 continue; |
|
642 case 'N': // replication = 'N' int '[' type ... ']' |
|
643 { |
|
644 int count = getInt(type.charAt(i + 1), false); |
|
645 assert (count >= 0); |
|
646 next = i + 2; |
|
647 String type1 = getBody(type, next); |
|
648 next += type1.length() + 2; // skip body and brackets |
|
649 for (int j = 0; j < count; j++) { |
|
650 attachTo(aval, readAttribute(type1)); |
|
651 } |
|
652 } |
|
653 continue; |
|
654 case 'T': // union = 'T' any_int union_case* '(' ')' '[' body ']' |
|
655 int tagValue; |
|
656 if (type.charAt(++i) == 'S') { |
|
657 tagValue = getInt(type.charAt(++i), true); |
|
658 } else { |
|
659 tagValue = getInt(type.charAt(i), false); |
|
660 } |
|
661 attachAttrTo(aval, "tag", "" + tagValue); // always named "tag" |
|
662 ++i; // skip the int type char |
|
663 // union_case = '(' uc_tag (',' uc_tag)* ')' '[' body ']' |
|
664 // uc_tag = ('-')? digit+ |
|
665 for (boolean foundCase = false;; i = next) { |
|
666 assert (type.charAt(i) == '('); |
|
667 next = type.indexOf(')', ++i); |
|
668 assert (next >= i); |
|
669 if (type.charAt(next - 1) == '\\' |
|
670 && type.charAt(next - 2) != '\\') // Skip an escaped paren. |
|
671 { |
|
672 next = type.indexOf(')', next + 1); |
|
673 } |
|
674 String caseStr = type.substring(i, next++); |
|
675 String type1 = getBody(type, next); |
|
676 next += type1.length() + 2; // skip body and brackets |
|
677 boolean lastCase = (caseStr.length() == 0); |
|
678 if (!foundCase |
|
679 && (lastCase || matchTag(tagValue, caseStr))) { |
|
680 foundCase = true; |
|
681 // Execute this body. |
|
682 attachTo(aval, readAttribute(type1)); |
|
683 } |
|
684 if (lastCase) { |
|
685 break; |
|
686 } |
|
687 } |
|
688 continue; |
|
689 case 'B': |
|
690 case 'H': |
|
691 case 'I': // int = oneof "BHI" |
|
692 next = i + 1; |
|
693 value = "" + getInt(type.charAt(i), false); |
|
694 break; |
|
695 case 'K': |
|
696 assert ("IJFDLQ".indexOf(type.charAt(i + 1)) >= 0); |
|
697 assert (type.charAt(i + 2) == 'H'); // only H works for now |
|
698 next = i + 3; |
|
699 value = cpRef(); |
|
700 break; |
|
701 case 'R': |
|
702 assert ("CSDFMIU?".indexOf(type.charAt(i + 1)) >= 0); |
|
703 assert (type.charAt(i + 2) == 'H'); // only H works for now |
|
704 next = i + 3; |
|
705 value = cpRef(); |
|
706 break; |
|
707 case 'P': // bci = 'P' int |
|
708 next = i + 2; |
|
709 value = "" + getInt(type.charAt(i + 1), false); |
|
710 break; |
|
711 case 'S': // signed_int = 'S' int |
|
712 next = i + 2; |
|
713 value = "" + getInt(type.charAt(i + 1), true); |
|
714 break; |
|
715 case 'F': |
|
716 next = i + 2; |
|
717 value = flagString(getInt(type.charAt(i + 1), false), currentMember); |
|
718 break; |
|
719 default: |
|
720 throw new RuntimeException("bad attr format '" + type.charAt(i) + "': " + type); |
|
721 } |
|
722 // store the value |
|
723 if (nextAttrName != null) { |
|
724 attachAttrTo(aval, nextAttrName, value); |
|
725 nextAttrName = null; |
|
726 } else { |
|
727 attachTo(aval, value); |
|
728 } |
|
729 } |
|
730 //System.out.println("readAttribute => "+aval); |
|
731 assert (nextAttrName == null); |
|
732 return aval; |
|
733 } |
|
734 |
|
735 private int getInt(char ch, boolean signed) throws IOException { |
|
736 if (signed) { |
|
737 switch (ch) { |
|
738 case 'B': |
|
739 return (byte) u1(); |
|
740 case 'H': |
|
741 return (short) u2(); |
|
742 case 'I': |
|
743 return (int) u4(); |
|
744 } |
|
745 } else { |
|
746 switch (ch) { |
|
747 case 'B': |
|
748 return u1(); |
|
749 case 'H': |
|
750 return u2(); |
|
751 case 'I': |
|
752 return u4(); |
|
753 } |
|
754 } |
|
755 assert ("BHIJ".indexOf(ch) >= 0); |
|
756 return 0; |
|
757 } |
|
758 |
|
759 private Element readCode() throws IOException { |
|
760 int stack = u2(); |
|
761 int local = u2(); |
|
762 int length = u4(); |
|
763 StringBuilder sb = new StringBuilder(length); |
|
764 for (int i = 0; i < length; i++) { |
|
765 sb.append((char) u1()); |
|
766 } |
|
767 String bytecodes = sb.toString(); |
|
768 Element e = new Element("Code", |
|
769 "stack", "" + stack, |
|
770 "local", "" + local); |
|
771 Element bytes = new Element("Bytes", (String[]) null, bytecodes); |
|
772 if (keepBytes) { |
|
773 e.add(bytes); |
|
774 } |
|
775 if (parseBytes) { |
|
776 e.add(parseByteCodes(bytecodes)); |
|
777 } |
|
778 for (int len = u2(), i = 0; i < len; i++) { |
|
779 int start = u2(); |
|
780 int end = u2(); |
|
781 int catsh = u2(); |
|
782 String clasz = cpRef(); |
|
783 e.add(new Element("Handler", |
|
784 "start", "" + start, |
|
785 "end", "" + end, |
|
786 "catch", "" + catsh, |
|
787 "class", clasz)); |
|
788 } |
|
789 readAttributesFor(e); |
|
790 e.trimToSize(); |
|
791 return e; |
|
792 } |
|
793 |
|
794 private Element parseByteCodes(String bytecodes) { |
|
795 Element e = InstructionSyntax.parse(bytecodes); |
|
796 for (Element ins : e.elements()) { |
|
797 Number ref = ins.getAttrNumber("ref"); |
|
798 if (ref != null && resolveRefs) { |
|
799 int id = ref.intValue(); |
|
800 String val = cpName(id); |
|
801 if (ins.getName().startsWith("ldc")) { |
|
802 // Yuck: Arb. string cannot be an XML attribute. |
|
803 ins.add(val); |
|
804 val = ""; |
|
805 byte tag = (id >= 0 && id < cpTag.length) ? cpTag[id] : 0; |
|
806 if (tag != 0) { |
|
807 ins.setAttrLong("tag", tag); |
|
808 } |
|
809 } |
|
810 if (ins.getName() == "invokeinterface" |
|
811 && computeInterfaceNum(val) == ins.getAttrLong("num")) { |
|
812 ins.setAttr("num", null); // garbage bytes |
|
813 } |
|
814 ins.setAttr("ref", null); |
|
815 ins.setAttr("val", val); |
|
816 } |
|
817 } |
|
818 return e; |
|
819 } |
|
820 |
|
821 private Element readStackMap(boolean hasXOption) throws IOException { |
|
822 Element result = new Element(); |
|
823 Element bytes = currentCode.findElement("Bytes"); |
|
824 assert (bytes != null && bytes.size() == 1); |
|
825 int byteLength = ((String) bytes.get(0)).length(); |
|
826 boolean uoffsetIsU4 = (byteLength >= (1 << 16)); |
|
827 boolean ulocalvarIsU4 = currentCode.getAttrLong("local") >= (1 << 16); |
|
828 boolean ustackIsU4 = currentCode.getAttrLong("stack") >= (1 << 16); |
|
829 if (hasXOption || uoffsetIsU4 || ulocalvarIsU4 || ustackIsU4) { |
|
830 Element flags = new Element("StackMapFlags"); |
|
831 if (hasXOption) { |
|
832 flags.setAttr("hasXOption", "true"); |
|
833 } |
|
834 if (uoffsetIsU4) { |
|
835 flags.setAttr("uoffsetIsU4", "true"); |
|
836 } |
|
837 if (ulocalvarIsU4) { |
|
838 flags.setAttr("ulocalvarIsU4", "true"); |
|
839 } |
|
840 if (ustackIsU4) { |
|
841 flags.setAttr("ustackIsU4", "true"); |
|
842 } |
|
843 currentCode.add(flags); |
|
844 } |
|
845 int frame_count = (uoffsetIsU4 ? u4() : u2()); |
|
846 for (int i = 0; i < frame_count; i++) { |
|
847 int bci = (uoffsetIsU4 ? u4() : u2()); |
|
848 int flags = (hasXOption ? u1() : 0); |
|
849 Element frame = new Element("Frame"); |
|
850 result.add(frame); |
|
851 if (flags != 0) { |
|
852 frame.setAttr("flags", "" + flags); |
|
853 } |
|
854 frame.setAttr("bci", "" + bci); |
|
855 // Scan local and stack types in this frame: |
|
856 final int LOCALS = 0, STACK = 1; |
|
857 for (int j = LOCALS; j <= STACK; j++) { |
|
858 int typeSize; |
|
859 if (j == LOCALS) { |
|
860 typeSize = (ulocalvarIsU4 ? u4() : u2()); |
|
861 } else { // STACK |
|
862 typeSize = (ustackIsU4 ? u4() : u2()); |
|
863 } |
|
864 Element types = new Element(j == LOCALS ? "Local" : "Stack"); |
|
865 for (int k = 0; k < typeSize; k++) { |
|
866 int tag = u1(); |
|
867 Element type = new Element(itemTagName(tag)); |
|
868 types.add(type); |
|
869 switch (tag) { |
|
870 case ITEM_Object: |
|
871 type.setAttr("class", cpRef()); |
|
872 break; |
|
873 case ITEM_Uninitialized: |
|
874 case ITEM_ReturnAddress: |
|
875 type.setAttr("bci", "" + (uoffsetIsU4 ? u4() : u2())); |
|
876 break; |
|
877 } |
|
878 } |
|
879 if (types.size() > 0) { |
|
880 frame.add(types); |
|
881 } |
|
882 } |
|
883 } |
|
884 return result; |
|
885 } |
|
886 |
|
887 private void readCP() throws IOException { |
|
888 int cpLen = u2(); |
|
889 cpTag = new byte[cpLen]; |
|
890 cpName = new String[cpLen]; |
|
891 int cpTem[][] = new int[cpLen][]; |
|
892 for (int i = 1; i < cpLen; i++) { |
|
893 cpTag[i] = (byte) u1(); |
|
894 switch (cpTag[i]) { |
|
895 case CONSTANT_Utf8: |
|
896 buf.reset(); |
|
897 for (int len = u2(), j = 0; j < len; j++) { |
|
898 buf.write(u1()); |
|
899 } |
|
900 cpName[i] = buf.toString(UTF8_ENCODING); |
|
901 break; |
|
902 case CONSTANT_Integer: |
|
903 cpName[i] = String.valueOf((int) u4()); |
|
904 break; |
|
905 case CONSTANT_Float: |
|
906 cpName[i] = String.valueOf(Float.intBitsToFloat(u4())); |
|
907 break; |
|
908 case CONSTANT_Long: |
|
909 cpName[i] = String.valueOf(u8()); |
|
910 i += 1; |
|
911 break; |
|
912 case CONSTANT_Double: |
|
913 cpName[i] = String.valueOf(Double.longBitsToDouble(u8())); |
|
914 i += 1; |
|
915 break; |
|
916 case CONSTANT_Class: |
|
917 case CONSTANT_String: |
|
918 cpTem[i] = new int[]{u2()}; |
|
919 break; |
|
920 case CONSTANT_Fieldref: |
|
921 case CONSTANT_Methodref: |
|
922 case CONSTANT_InterfaceMethodref: |
|
923 case CONSTANT_NameAndType: |
|
924 cpTem[i] = new int[]{u2(), u2()}; |
|
925 break; |
|
926 } |
|
927 } |
|
928 for (int i = 1; i < cpLen; i++) { |
|
929 switch (cpTag[i]) { |
|
930 case CONSTANT_Class: |
|
931 case CONSTANT_String: |
|
932 cpName[i] = cpName[cpTem[i][0]]; |
|
933 break; |
|
934 case CONSTANT_NameAndType: |
|
935 cpName[i] = cpName[cpTem[i][0]] + " " + cpName[cpTem[i][1]]; |
|
936 break; |
|
937 } |
|
938 } |
|
939 // do fieldref et al after nameandtype are all resolved |
|
940 for (int i = 1; i < cpLen; i++) { |
|
941 switch (cpTag[i]) { |
|
942 case CONSTANT_Fieldref: |
|
943 case CONSTANT_Methodref: |
|
944 case CONSTANT_InterfaceMethodref: |
|
945 cpName[i] = cpName[cpTem[i][0]] + " " + cpName[cpTem[i][1]]; |
|
946 break; |
|
947 } |
|
948 } |
|
949 cpool = new Element("ConstantPool", cpName.length); |
|
950 for (int i = 0; i < cpName.length; i++) { |
|
951 if (cpName[i] == null) { |
|
952 continue; |
|
953 } |
|
954 cpool.add(new Element(cpTagName(cpTag[i]), |
|
955 new String[]{"id", "" + i}, |
|
956 cpName[i])); |
|
957 } |
|
958 if (keepCP) { |
|
959 cfile.add(cpool); |
|
960 } |
|
961 } |
|
962 |
|
963 private String cpRef() throws IOException { |
|
964 int ref = u2(); |
|
965 if (resolveRefs) { |
|
966 return cpName(ref); |
|
967 } else { |
|
968 return REF_PREFIX + ref; |
|
969 } |
|
970 } |
|
971 |
|
972 private String cpName(int id) { |
|
973 if (id >= 0 && id < cpName.length) { |
|
974 return cpName[id]; |
|
975 } else { |
|
976 return "[CP#" + Integer.toHexString(id) + "]"; |
|
977 } |
|
978 } |
|
979 |
|
980 private long u8() throws IOException { |
|
981 return ((long) u4() << 32) + (((long) u4() << 32) >>> 32); |
|
982 } |
|
983 |
|
984 private int u4() throws IOException { |
|
985 return (u2() << 16) + u2(); |
|
986 } |
|
987 |
|
988 private int u2() throws IOException { |
|
989 return (u1() << 8) + u1(); |
|
990 } |
|
991 |
|
992 private int u1() throws IOException { |
|
993 int x = in.read(); |
|
994 if (x < 0) { |
|
995 paddingSize++; |
|
996 return 0; // error recovery |
|
997 } |
|
998 fileSize++; |
|
999 assert (x == (x & 0xFF)); |
|
1000 return x; |
|
1001 } |
|
1002 } |
|
1003 |