Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(93)

Side by Side Diff: third_party/jmake/src/org/pantsbuild/jmake/ClassFileReader.java

Issue 1373723003: Fix javac --incremental by using jmake for dependency analysis (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@aidl
Patch Set: fix license check Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
2 *
3 * This program is distributed under the terms of
4 * the GNU General Public License Version 2. See the LICENSE file
5 * at the top of the source tree.
6 */
7 package org.pantsbuild.jmake;
8
9 import java.lang.reflect.Modifier;
10
11
12 /**
13 * This class implements reading a byte array representing a class file and conv erting it into ClassInfo.
14 *
15 * @author Misha Dmitriev
16 * 2 March 2005
17 */
18 public class ClassFileReader extends BinaryFileReader {
19
20 public static final int JAVA_MAGIC = -889275714; // 0xCAFEBABE
21 public static final int JAVA_MINOR_VERSION = 0;
22 public static final int JAVA_MIN_MAJOR_VERSION = 45;
23 public static final int JAVA_MIN_MINOR_VERSION = 3;
24 public static final int DEFAULT_MAJOR_VERSION = 46;
25 public static final int DEFAULT_MINOR_VERSION = 0;
26 public static final int JDK14_MAJOR_VERSION = 48;
27 public static final int JDK15_MAJOR_VERSION = 49;
28 public static final int JDK16_MAJOR_VERSION = 50;
29 public static final int JDK17_MAJOR_VERSION = 51;
30 public static final int JDK18_MAJOR_VERSION = 52;
31 public static final int CONSTANT_Utf8 = 1;
32 public static final int CONSTANT_Unicode = 2;
33 public static final int CONSTANT_Integer = 3;
34 public static final int CONSTANT_Float = 4;
35 public static final int CONSTANT_Long = 5;
36 public static final int CONSTANT_Double = 6;
37 public static final int CONSTANT_Class = 7;
38 public static final int CONSTANT_String = 8;
39 public static final int CONSTANT_Fieldref = 9;
40 public static final int CONSTANT_Methodref = 10;
41 public static final int CONSTANT_InterfaceMethodref = 11;
42 public static final int CONSTANT_NameandType = 12;
43 public static final int CONSTANT_MethodHandle = 15;
44 public static final int CONSTANT_MethodType = 16;
45 public static final int CONSTANT_InvokeDynamic = 18;
46 private ClassInfo classInfo = null;
47 private int cpOffsets[];
48 private Object cpObjectCache[];
49 private byte cpTags[];
50
51 public void readClassFile(byte[] classFile, ClassInfo classInfo, String clas sFileFullPath, boolean readFullInfo) {
52 initBuf(classFile, classFileFullPath);
53 this.classInfo = classInfo;
54
55 readPreamble();
56 readConstantPool(readFullInfo);
57 readIntermediate();
58 if (readFullInfo) {
59 readFields();
60 readMethods();
61 readAttributes();
62 }
63 }
64
65 private int versionWord(int major, int minor) {
66 return major * 1000 + minor;
67 }
68
69 private void readPreamble() {
70 int magic = nextInt();
71 if (magic != JAVA_MAGIC) {
72 throw classFileParseException("Illegal start of class file");
73 }
74 int minorVersion = nextChar();
75 int majorVersion = nextChar();
76 if (majorVersion > JDK14_MAJOR_VERSION ||
77 versionWord(majorVersion, minorVersion) <
78 versionWord(JAVA_MIN_MAJOR_VERSION, JAVA_MIN_MINOR_VERSION) ) {
79 if (majorVersion == JDK18_MAJOR_VERSION) {
80 classInfo.javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_18;
81 } else if (majorVersion == JDK17_MAJOR_VERSION) {
82 classInfo.javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_17;
83 } else if (majorVersion == JDK16_MAJOR_VERSION) {
84 classInfo.javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_16;
85 } else if (majorVersion == JDK15_MAJOR_VERSION) {
86 classInfo.javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_15;
87 } else {
88 throw classFileParseException("Wrong version: " + majorVersion + "." + minorVersion);
89 }
90 } else {
91 classInfo.javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_OLDEST;
92 }
93 }
94
95 private void readConstantPool(boolean readFullInfo) {
96 int classRefsNo = 0;
97 int fieldRefsNo = 0;
98 int methodRefsNo = 0;
99
100 cpOffsets = new int[nextChar()];
101 cpTags = new byte[cpOffsets.length];
102 int ofs, len, classIdx, nameAndTypeIdx, nameIdx, sigIdx, utf8Idx;
103 int i = 1;
104 while (i < cpOffsets.length) {
105 byte tag = buf[curBufPos++];
106 cpOffsets[i] = curBufPos;
107 cpTags[i] = tag;
108 i++;
109 switch (tag) {
110 case CONSTANT_Utf8:
111 len = nextChar();
112 curBufPos += len;
113 break;
114
115 case CONSTANT_Class:
116 classRefsNo++;
117 curBufPos += 2;
118 break;
119
120 case CONSTANT_String:
121 case CONSTANT_MethodType:
122 curBufPos += 2;
123 break;
124
125 case CONSTANT_Fieldref:
126 fieldRefsNo++;
127 curBufPos += 4;
128 break;
129
130 case CONSTANT_Methodref:
131 case CONSTANT_InterfaceMethodref:
132 methodRefsNo++;
133 curBufPos += 4;
134 break;
135
136 case CONSTANT_MethodHandle:
137 curBufPos += 3;
138 break;
139
140 case CONSTANT_NameandType:
141 case CONSTANT_Integer:
142 case CONSTANT_Float:
143 case CONSTANT_InvokeDynamic:
144 curBufPos += 4;
145 break;
146
147 case CONSTANT_Long:
148 case CONSTANT_Double:
149 curBufPos += 8;
150 i++;
151 break;
152
153 default:
154 throw classFileParseException("Bad constant pool tag: " + ta g + " at " + Integer.toString(curBufPos - 1));
155 }
156 }
157
158 cpObjectCache = new Object[cpOffsets.length];
159 if (!readFullInfo) {
160 return;
161 }
162
163 classInfo.cpoolRefsToClasses = new String[classRefsNo];
164 classInfo.isRefClassArray = new boolean[classRefsNo];
165 classInfo.cpoolRefsToFieldClasses = new String[fieldRefsNo];
166 classInfo.cpoolRefsToFieldNames = new String[fieldRefsNo];
167 classInfo.cpoolRefsToFieldSignatures = new String[fieldRefsNo];
168 classInfo.cpoolRefsToMethodClasses = new String[methodRefsNo];
169 classInfo.cpoolRefsToMethodNames = new String[methodRefsNo];
170 classInfo.cpoolRefsToMethodSignatures = new String[methodRefsNo];
171
172 int curClassRef = 0;
173 int curFieldRef = 0;
174 int curMethodRef = 0;
175
176 for (i = 0; i < cpOffsets.length; i++) {
177 ofs = cpOffsets[i];
178 switch (cpTags[i]) {
179 case CONSTANT_Class:
180 utf8Idx = getChar(ofs);
181 classInfo.cpoolRefsToClasses[curClassRef++] =
182 classNameAtCPIndex(utf8Idx, classInfo.isRefClassArra y, curClassRef - 1);
183 //System.out.println("Read cpool ref to class: " + classInfo .cpoolRefsToClasses[curClassRef-1]);
184 break;
185
186 case CONSTANT_Fieldref:
187 classIdx = getChar(ofs);
188 nameAndTypeIdx = getChar(ofs + 2);
189 if (cpTags[classIdx] != CONSTANT_Class || cpTags[nameAndType Idx] != CONSTANT_NameandType) {
190 badCPReference(ofs, i);
191 }
192 classInfo.cpoolRefsToFieldClasses[curFieldRef] =
193 classNameAtCPIndex(getChar(cpOffsets[classIdx]));
194
195 ofs = cpOffsets[nameAndTypeIdx];
196 nameIdx = getChar(ofs);
197 sigIdx = getChar(ofs + 2);
198 if (cpTags[nameIdx] != CONSTANT_Utf8 || cpTags[sigIdx] != CO NSTANT_Utf8) {
199 badCPReference(ofs, i);
200 }
201 classInfo.cpoolRefsToFieldNames[curFieldRef] =
202 utf8AtCPIndex(nameIdx);
203 classInfo.cpoolRefsToFieldSignatures[curFieldRef] =
204 signatureAtCPIndex(sigIdx);
205 //System.out.println("Read cpool ref to field: " + classInfo .cpoolRefsToFieldNames[curFieldRef] + " " +
206 // classInfo.cpoolRefsToFieldSignatures[cu rFieldRef]);
207 curFieldRef++;
208 break;
209
210 case CONSTANT_Methodref:
211 case CONSTANT_InterfaceMethodref:
212 classIdx = getChar(ofs);
213 nameAndTypeIdx = getChar(ofs + 2);
214 if (cpTags[classIdx] != CONSTANT_Class || cpTags[nameAndType Idx] != CONSTANT_NameandType) {
215 badCPReference(ofs, i);
216 }
217 classInfo.cpoolRefsToMethodClasses[curMethodRef] =
218 classNameAtCPIndex(getChar(cpOffsets[classIdx]));
219
220 ofs = cpOffsets[nameAndTypeIdx];
221 nameIdx = getChar(ofs);
222 sigIdx = getChar(ofs + 2);
223 if (cpTags[nameIdx] != CONSTANT_Utf8 || cpTags[sigIdx] != CO NSTANT_Utf8) {
224 badCPReference(ofs, i);
225 }
226 classInfo.cpoolRefsToMethodNames[curMethodRef] =
227 utf8AtCPIndex(nameIdx);
228 classInfo.cpoolRefsToMethodSignatures[curMethodRef] =
229 signatureAtCPIndex(sigIdx);
230 //System.out.println("Read cpool ref to method: " + classInf o.cpoolRefsToMethodNames[curMethodRef] + " " +
231 // classInfo.cpoolRefsToMethodSignatures[c urMethodRef]);
232 curMethodRef++;
233 break;
234 }
235 }
236 }
237
238 private void readIntermediate() {
239 int i, classIdx, superClassIdx;
240
241 classInfo.accessFlags = nextChar();
242 classIdx = nextChar();
243 if (cpTags[classIdx] != CONSTANT_Class) {
244 throw classFileParseException("Bad reference to this class name");
245 }
246 classInfo.name = classNameAtCPIndex(getChar(cpOffsets[classIdx]));
247 superClassIdx = nextChar();
248 if (!"java/lang/Object".equals(classInfo.name)) {
249 if (cpTags[superClassIdx] != CONSTANT_Class) {
250 throw classFileParseException("Bad reference to super class name ");
251 }
252 classInfo.superName =
253 classNameAtCPIndex(getChar(cpOffsets[superClassIdx]));
254 }
255
256 char intfCount = nextChar();
257 if (intfCount != 0) {
258 classInfo.interfaces = new String[intfCount];
259 for (i = 0; i < intfCount; i++) {
260 classIdx = nextChar();
261 if (cpTags[classIdx] != CONSTANT_Class) {
262 throw classFileParseException("Bad reference to an implement ed interface");
263 }
264 classInfo.interfaces[i] =
265 classNameAtCPIndex(getChar(cpOffsets[classIdx]));
266 }
267 }
268 }
269
270 private void readFields() {
271 int i, j;
272
273 char definedFieldCount = nextChar();
274 if (definedFieldCount == 0) {
275 return;
276 }
277
278 String names[] = new String[definedFieldCount];
279 String signatures[] = new String[definedFieldCount];
280 char accessFlags[] = new char[definedFieldCount];
281
282 // We are not going to record information on private fields which have e ither primitive or non-project-class
283 // (typically core-class) types. Such fields cannot affect anything exce pt their own class, so we don't need them.
284 int ri = 0;
285
286 for (i = 0; i < definedFieldCount; i++) {
287 char flags = nextChar();
288 String name = utf8AtCPIndex(nextChar());
289 String sig = signatureAtCPIndex(nextChar());
290
291 boolean recordField =
292 !(Modifier.isPrivate(flags) &&
293 (ClassInfo.isPrimitiveFieldSig(sig) || classInfo.isNonProjec tClassTypeFieldSig(sig)));
294
295 int attrCount = nextChar();
296 for (j = 0; j < attrCount; j++) {
297 int attrNameIdx = nextChar();
298 int attrLen = nextInt();
299 if (recordField && utf8AtCPIndex(attrNameIdx).equals("ConstantVa lue") &&
300 Modifier.isFinal(flags)) {
301 if (classInfo.primitiveConstantInitValues == null) {
302 classInfo.primitiveConstantInitValues =
303 new Object[definedFieldCount];
304 }
305 int constValueIdx = nextChar();
306 switch (cpTags[constValueIdx]) {
307 case CONSTANT_String:
308 classInfo.primitiveConstantInitValues[ri] =
309 utf8AtCPIndex(getChar(cpOffsets[constValueId x]));
310 break;
311
312 case CONSTANT_Integer:
313 classInfo.primitiveConstantInitValues[ri] =
314 Integer.valueOf(getInt(cpOffsets[constValueI dx]));
315 break;
316
317 case CONSTANT_Long:
318 classInfo.primitiveConstantInitValues[ri] =
319 Long.valueOf(getLong(cpOffsets[constValueIdx ]));
320 break;
321
322 case CONSTANT_Float:
323 classInfo.primitiveConstantInitValues[ri] =
324 Float.valueOf(getFloat(cpOffsets[constValueI dx]));
325 break;
326
327 case CONSTANT_Double:
328 classInfo.primitiveConstantInitValues[ri] =
329 Double.valueOf(getDouble(cpOffsets[constValu eIdx]));
330 break;
331
332 default:
333 badCPEntry(constValueIdx);
334 }
335
336 } else {
337 curBufPos += attrLen;
338 }
339 }
340
341 if (recordField) {
342 names[ri] = name;
343 signatures[ri] = sig;
344 accessFlags[ri] = flags;
345 ri++;
346 }
347 }
348
349 if (ri == definedFieldCount) {
350 classInfo.fieldNames = names;
351 classInfo.fieldSignatures = signatures;
352 classInfo.fieldAccessFlags = accessFlags;
353 } else if (ri > 0) {
354 classInfo.fieldNames = new String[ri];
355 classInfo.fieldSignatures = new String[ri];
356 classInfo.fieldAccessFlags = new char[ri];
357 System.arraycopy(names, 0, classInfo.fieldNames, 0, ri);
358 System.arraycopy(signatures, 0, classInfo.fieldSignatures, 0, ri);
359 System.arraycopy(accessFlags, 0, classInfo.fieldAccessFlags, 0, ri);
360 }
361 }
362
363 private void readMethods() {
364 int i, j;
365
366 char methodCount = nextChar();
367 if (methodCount == 0) {
368 return;
369 }
370
371 String names[] = new String[methodCount];
372 String signatures[] = new String[methodCount];
373 char accessFlags[] = new char[methodCount];
374
375 for (i = 0; i < methodCount; i++) {
376 accessFlags[i] = nextChar();
377 names[i] = utf8AtCPIndex(nextChar());
378 signatures[i] = signatureAtCPIndex(nextChar());
379
380 int attrCount = nextChar();
381 for (j = 0; j < attrCount; j++) {
382 int attrNameIdx = nextChar();
383 int attrLen = nextInt();
384 if (utf8AtCPIndex(attrNameIdx).equals("Exceptions")) {
385 if (classInfo.checkedExceptions == null) {
386 classInfo.checkedExceptions = new String[methodCount][];
387 }
388 int nExceptions = nextChar();
389 String exceptions[] = new String[nExceptions];
390 for (int k = 0; k < nExceptions; k++) {
391 int excClassIdx = nextChar();
392 if (cpTags[excClassIdx] != CONSTANT_Class) {
393 badCPEntry(excClassIdx);
394 }
395 exceptions[k] =
396 classNameAtCPIndex(getChar(cpOffsets[excClassIdx ]));
397 }
398 classInfo.checkedExceptions[i] = exceptions;
399 } else {
400 curBufPos += attrLen;
401 }
402 }
403 }
404
405 classInfo.methodNames = names;
406 classInfo.methodSignatures = signatures;
407 classInfo.methodAccessFlags = accessFlags;
408 }
409
410 /**
411 * This method actually reads only the information related to the nested cla sses, and
412 * records only those of them which are first level nested classes of this c lass. The class
413 * may also reference other classes which are not package members through th e same
414 * InnerClasses attribute - their names would be processed when their respec tive enclosing
415 * classes are read.
416 */
417 private void readAttributes() {
418 String nestedClassPrefix = classInfo.name + "$";
419
420 char attrCount = nextChar();
421
422 for (int i = 0; i < attrCount; i++) {
423 int attrNameIdx = nextChar();
424 int attrLen = nextInt();
425 if (utf8AtCPIndex(attrNameIdx).equals("InnerClasses")) {
426 int nOfClasses = nextChar();
427 String nestedClasses[] = new String[nOfClasses];
428 char nestedClassAccessFlags[] = new char[nOfClasses];
429 boolean nestedClassNonMember[] = new boolean[nOfClasses];
430 int curIdx = 0;
431 for (int j = 0; j < nOfClasses; j++) {
432 int innerClassInfoIdx = nextChar();
433 int outerClassInfoIdx = nextChar();
434 int innerClassNameIdx = nextChar();
435 char innerClassAccessFlags = nextChar();
436
437 // Even if a class is private or non-member (innerClassAcces sFlags has private bit set or
438 // outerClassInfoIdx == 0), we still should take this class into account, since it may e.g. extend
439 // a public class/implement a public interface, which, in tu rn, may be changed incompatibly.
440
441 String nestedClassFullName = classNameAtCPIndex(getChar(cpOf fsets[innerClassInfoIdx]));
442
443 // We are only interested the nested classes whose enclosing class is this one.
444 if (!nestedClassFullName.startsWith(nestedClassPrefix))
445 continue;
446
447 // We are only interested in the directly nested classes of this class.
448 String nestedClassNameSuffix = nestedClassFullName.substring (nestedClassPrefix.length());
449
450 if (innerClassNameIdx == 0) {
451 // Nested class is anonymous. Suffix must be all digits.
452 if (findFirstNonDigit(nestedClassNameSuffix) != -1)
453 continue;
454 } else {
455 // Nested class is named.
456 String nestedClassSimpleName = utf8AtCPIndex(innerClassN ameIdx);
457 // The simple case is Outer$Inner.
458 if (!nestedClassNameSuffix.equals(nestedClassSimpleName) ) {
459 // The more complicated case is a local class. In JD K 1.5+ These are named,
460 // e.g., Outer$1Inner. Pre-JDK 1.5 they are named e. g., Outer$1$Inner.
461 int p = findFirstNonDigit(nestedClassNameSuffix);
462 if (p == -1)
463 continue;
464 if (classInfo.javacTargetRelease == Utils.JAVAC_TARG ET_RELEASE_OLDEST &&
465 nestedClassNameSuffix.charAt(p++) != '$')
466 continue;
467 if (!nestedClassNameSuffix.substring(p).equals(neste dClassSimpleName))
468 continue;
469 }
470 }
471
472 // The name has passed all checks, so register it.
473
474 nestedClasses[curIdx] = nestedClassFullName;
475 nestedClassAccessFlags[curIdx] = innerClassAccessFlags;
476 nestedClassNonMember[curIdx] = (outerClassInfoIdx == 0);
477 curIdx++;
478 }
479 if (curIdx == nOfClasses) {
480 classInfo.nestedClasses = nestedClasses;
481 classInfo.nestedClassAccessFlags = nestedClassAccessFlags;
482 classInfo.nestedClassNonMember = nestedClassNonMember;
483 } else if (curIdx > 0) {
484 // We found fewer nested classes for this class than we orig inally expected, but still more than 0.
485 // Create a new array to fit their number exactly.
486 classInfo.nestedClasses = new String[curIdx];
487 classInfo.nestedClassAccessFlags = new char[curIdx];
488 classInfo.nestedClassNonMember = new boolean[curIdx];
489 System.arraycopy(nestedClasses, 0, classInfo.nestedClasses, 0, curIdx);
490 System.arraycopy(nestedClassAccessFlags, 0, classInfo.nested ClassAccessFlags, 0, curIdx);
491 System.arraycopy(nestedClassNonMember, 0, classInfo.nestedCl assNonMember, 0, curIdx);
492 }
493 } else {
494 curBufPos += attrLen;
495 }
496 }
497 }
498
499 private int findFirstNonDigit(String s) {
500 for (int i = 0; i < s.length(); i++) {
501 if (!Character.isDigit(s.charAt(i)))
502 return i;
503 }
504 return -1;
505 }
506
507 private String utf8AtCPIndex(int idx) {
508 if (cpTags[idx] != CONSTANT_Utf8) {
509 throw classFileParseException("Constant pool entry " + idx + " shoul d be UTF8 constant");
510 }
511 if (cpObjectCache[idx] == null) {
512 int utf8Len = getChar(cpOffsets[idx]);
513 // String interning reduces the size of the disk database very signi ficantly
514 // (by one-third in one observed case), and also speeds up database search.
515 cpObjectCache[idx] =
516 (new String(buf, cpOffsets[idx] + 2, utf8Len)).intern();
517 }
518 return (String) cpObjectCache[idx];
519 }
520
521 private String classNameAtCPIndex(int idx) {
522 return classNameAtCPIndex(idx, null, 0);
523 }
524
525 /**
526 * Read class name at the given CONSTANT_Utf8 constant pool index, and retur n it
527 * trimmed of the possible '[' and 'L' prefixes and the ';' suffix.
528 */
529 private String classNameAtCPIndex(int idx, boolean isRefClassArray[], int is ArrayIdx) {
530 if (cpTags[idx] != CONSTANT_Utf8) {
531 throw classFileParseException("Constant pool entry " + idx + " shoul d be UTF8 constant");
532 }
533 boolean isArray = false;
534 if (cpObjectCache[idx] == null) {
535 int utf8Len = getChar(cpOffsets[idx]);
536 int stPos = cpOffsets[idx] + 2;
537 int initStPos = stPos;
538 while (buf[stPos] == '[') {
539 stPos++;
540 }
541 if (stPos != initStPos) {
542 isArray = true;
543 if (buf[stPos] == 'L') {
544 stPos++;
545 utf8Len--; // To get rid of the terminating ';'
546 }
547 }
548 utf8Len = utf8Len - (stPos - initStPos);
549 cpObjectCache[idx] = (new String(buf, stPos, utf8Len)).intern();
550 if (isRefClassArray != null) {
551 isRefClassArray[isArrayIdx] = isArray;
552 }
553 }
554 return (String) cpObjectCache[idx];
555 }
556
557 // We replace all "Lclassname;" in signatures with "@classname#" to simplify signature parsing during reference checking
558 private String signatureAtCPIndex(int idx) {
559 if (cpTags[idx] != CONSTANT_Utf8) {
560 throw classFileParseException("Constant pool entry " + idx + " shoul d be UTF8 constant");
561 }
562 if (cpObjectCache[idx] == null) {
563 int utf8Len = getChar(cpOffsets[idx]);
564 byte tmp[] = new byte[utf8Len];
565 System.arraycopy(buf, cpOffsets[idx] + 2, tmp, 0, utf8Len);
566 boolean inClassName = false;
567 for (int i = 0; i < utf8Len; i++) {
568 if (!inClassName) {
569 if (tmp[i] == 'L') {
570 tmp[i] = '@';
571 inClassName = true;
572 }
573 } else if (tmp[i] == ';') {
574 tmp[i] = '#';
575 inClassName = false;
576 }
577 }
578 cpObjectCache[idx] = (new String(tmp)).intern();
579 }
580 return (String) cpObjectCache[idx];
581 }
582
583 private void badCPReference(int ofs, int i) {
584 throw classFileParseException("Bad constant pool reference: " + ofs + " from entry " + i);
585 }
586
587 private void badCPEntry(int entryNo) {
588 throw classFileParseException("Constant pool entry " + entryNo + " : inv alid type");
589 }
590
591 private PrivateException classFileParseException(String msg) {
592 return new PrivateException(new PublicExceptions.ClassFileParseException (
593 "Error reading class file " + fileFullPath + ":\n" + msg));
594 }
595 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698