• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package annotations.io;
2 
3 /*>>>
4 import org.checkerframework.checker.nullness.qual.*;
5 */
6 
7 import java.io.*;
8 import java.lang.annotation.RetentionPolicy;
9 import java.util.ArrayList;
10 import java.util.List;
11 import java.util.regex.Matcher;
12 import java.util.regex.Pattern;
13 
14 import annotations.Annotation;
15 import annotations.AnnotationBuilder;
16 import annotations.AnnotationFactory;
17 import annotations.Annotations;
18 import annotations.el.*;
19 
20 import com.sun.tools.javac.code.TargetType;
21 import com.sun.tools.javac.code.TypeAnnotationPosition;
22 
23 import plume.FileIOException;
24 
25 /**
26  * <code>JavapParser</code> provides a static method that parses a class dump
27  * in the form produced by <code>xjavap -s -verbose -annotations</code> and adds
28  * the annotations to an {@link AScene}, using the scene's
29  * {@link AnnotationFactory} to build individual annotations.
30  * If the scene's {@link AnnotationFactory} announces that it does not want an
31  * annotation found in the javap output, that annotation is skipped. Annotations
32  * from the javap output are merged into the scene; it is an error if both the
33  * scene and the javap output contain annotations of the same type on the same
34  * element.
35  *
36  * <p>
37  * THIS CLASS IS NOT FINISHED YET!
38  *
39  * <p>
40  * This class does not yet perform any error checking.  Expect strange
41  * behavior and/or exceptions if you give it bad input.
42  */
43 public final class JavapParser {
44     private static final String SECTION_TITLE_PREFIX = "  ";
45     private static final String SECTION_DATA_PREFIX = "   ";
46     private static final String CONST_POOL_DATA_PREFIX = "const #";
47 
48     private final AScene scene;
49 
50     private final BufferedReader bin;
51     private String line; // null means end-of-file
52 
53     private int lineNo = 0; // TEMP
54 
nextLine()55     private void nextLine() throws IOException {
56         do {
57             line = bin.readLine();
58             lineNo++;
59         } while (line != null && line.equals(""));
60     }
61 
trim(String prefix)62     private void trim(String prefix) {
63         if (line.startsWith(prefix)) {
64             line = line.substring(prefix.length());
65         }
66     }
67 
inMember()68     private boolean inMember() {
69         return line.startsWith(SECTION_TITLE_PREFIX);
70     }
71 
inData()72     private boolean inData() {
73         return line.startsWith(SECTION_DATA_PREFIX) ||
74             line.startsWith(CONST_POOL_DATA_PREFIX);
75     }
76 
77     private enum TargetMode {
78         ORIGINAL, PARAMETER, EXTENDED
79     }
80 
81     // This name comes from the section of the javap output that is being read.
82     private enum AnnotationSection {
83         RVA("RuntimeVisibleAnnotations", RetentionPolicy.RUNTIME, TargetMode.ORIGINAL),
84         RIA("RuntimeInvisibleAnnotations", RetentionPolicy.CLASS, TargetMode.ORIGINAL),
85         RVPA("RuntimeVisibleParameterAnnotations", RetentionPolicy.RUNTIME, TargetMode.PARAMETER),
86         RIPA("RuntimeInvisibleParameterAnnotations", RetentionPolicy.CLASS, TargetMode.PARAMETER),
87         RVEA("RuntimeVisibleTypeAnnotations", RetentionPolicy.RUNTIME, TargetMode.EXTENDED),
88         RIEA("RuntimeInvisibleTypeAnnotations", RetentionPolicy.CLASS, TargetMode.EXTENDED),
89         ;
90 
91         final String secTitle;
92         final RetentionPolicy retention;
93         final TargetMode locMode;
94 
AnnotationSection(String secTitle, RetentionPolicy retention, TargetMode locMode)95         AnnotationSection(String secTitle, RetentionPolicy retention, TargetMode locMode) {
96             this.secTitle = secTitle;
97             this.retention = retention;
98             this.locMode = locMode;
99         }
100     }
101 
parseAnnotationHead()102     private String parseAnnotationHead() throws IOException, ParseException {
103         String annoTypeName = line.substring(
104                 line.indexOf(annotationHead) + annotationHead.length(),
105                 line.length() - 1).replace('/', '.');
106         nextLine();
107         return annoTypeName;
108     }
109 
110     private static final String annotationHead = "//Annotation L"; // TEMP
111     private static final String tagHead = "type = "; // TEMP
112 
parseAnnotationBody( AnnotationBuilder ab, String indent)113     private Annotation parseAnnotationBody(
114             AnnotationBuilder ab,
115             String indent) throws IOException, ParseException {
116         // Grab the fields
117         String fieldIndent = indent + " ";
118         while (line.startsWith(fieldIndent)) {
119             String line2 = line.substring(fieldIndent.length());
120             // Let the caller deal with location information, if any
121             if (line2.startsWith("target") || line2.startsWith("parameter")) {
122                 break;
123             }
124             String fieldName =
125                 line2.substring(line2.indexOf("//") + "//".length());
126             nextLine();
127             char tag = line.charAt(line.indexOf(tagHead) + tagHead.length());
128             switch (tag) {
129             case '[':
130                 break;
131             case '@':
132                 break;
133             case 'c':
134                 break;
135             case 'e':
136                 break;
137             }
138             // FINISH
139         }
140         return ab.finish();
141     }
142 
143     private static final String paramIdxHead = "parameter = ";
144     private static final String offsetHead = "offset = ";
145     private static final String typeIndexHead = "type_index = ";
146     private static final Pattern localLocRegex =
147         Pattern.compile("^\\s*start_pc = (\\d+), length = (\\d+), index = (\\d+)$");
148     private static final String itlnHead = "location = ";
149 
parseOffset()150     private int parseOffset() throws IOException, ParseException {
151         int offset = Integer.parseInt(
152                 line.substring(line.indexOf(offsetHead) + offsetHead.length()));
153         nextLine();
154         return offset;
155     }
156 
parseTypeIndex()157     private int parseTypeIndex() throws IOException, ParseException {
158         int typeIndex = Integer.parseInt(
159                 line.substring(line.indexOf(typeIndexHead) + typeIndexHead.length()));
160         nextLine();
161         return typeIndex;
162     }
163 
parseInnerTypeLocationNums()164     private List<Integer> parseInnerTypeLocationNums() throws IOException, ParseException {
165         String numsStr
166             = line.substring(line.indexOf(itlnHead) + itlnHead.length());
167         List<Integer> nums = new ArrayList<Integer>();
168         for (;;) {
169             int comma = numsStr.indexOf(',');
170             if (comma == -1) {
171                 nums.add(Integer.parseInt(numsStr));
172                 break;
173             }
174             nums.add(Integer.parseInt(numsStr.substring(0, comma)));
175             numsStr = numsStr.substring(comma + 2);
176         }
177         nextLine();
178         return nums;
179     }
180 
chooseSubElement(AElement member, AnnotationSection sec)181     private AElement chooseSubElement(AElement member, AnnotationSection sec) throws IOException, ParseException {
182         switch (sec.locMode) {
183         case ORIGINAL:
184             // There can be no location information.
185             return member;
186         case PARAMETER:
187         {
188             // should have a "parameter = "
189             int paramIdx = Integer.parseInt(
190                     line.substring(
191                     line.indexOf(paramIdxHead) + paramIdxHead.length()));
192             nextLine();
193             return ((AMethod) member).parameters.vivify(paramIdx);
194         }
195         case EXTENDED:
196             // should have a "target = "
197             String targetTypeName =
198                 line.substring(line.indexOf("//") + "//".length());
199             TargetType targetType;
200             TargetType tt = TargetType.valueOf(targetTypeName);
201             if (tt != null) {
202                 targetType = tt;
203             } else {
204                 throw new RuntimeException("null target type");
205             }
206             nextLine();
207             ATypeElement subOuterType;
208             AElement subElement;
209             switch (targetType) {
210             case FIELD:
211             case METHOD_RETURN:
212                 subOuterType = (ATypeElement) member;
213                 break;
214             case METHOD_RECEIVER:
215                 subOuterType = ((AMethod) member).receiver.type;
216                 break;
217             case METHOD_FORMAL_PARAMETER:
218                 int paramIdx = Integer.parseInt(
219                         line.substring(
220                         line.indexOf(paramIdxHead) + paramIdxHead.length()));
221                 nextLine();
222                 subOuterType = ((AMethod) member).parameters.vivify(paramIdx).type;
223                 break;
224             case LOCAL_VARIABLE:
225             case RESOURCE_VARIABLE:
226                 int index, scopeStart, scopeLength;
227                 Matcher m = localLocRegex.matcher(line);
228                 m.matches();
229                 index = Integer.parseInt(m.group(1));
230                 scopeStart = Integer.parseInt(m.group(2));
231                 scopeLength = Integer.parseInt(m.group(3));
232                 LocalLocation ll =
233                     new LocalLocation(index, scopeStart, scopeLength);
234                 nextLine();
235                 subOuterType = ((AMethod) member).body.locals.vivify(ll).type;
236                 break;
237             case CAST:
238             {
239                 int offset = parseOffset();
240                 int typeIndex = parseTypeIndex();
241                 subOuterType = ((AMethod) member).body.typecasts.vivify(RelativeLocation.createOffset(offset, typeIndex));
242                 break;
243             }
244             case INSTANCEOF:
245             {
246                 int offset = parseOffset();
247                 subOuterType = ((AMethod) member).body.instanceofs.vivify(RelativeLocation.createOffset(offset, 0));
248                 break;
249             }
250             case NEW:
251             {
252                 int offset = parseOffset();
253                 subOuterType = ((AMethod) member).body.news.vivify(RelativeLocation.createOffset(offset, 0));
254                 break;
255             }
256             default:
257                 throw new AssertionError();
258             }
259             // TODO: update location representation
260             // if (targetType.) {
261                 List<Integer> location = parseInnerTypeLocationNums();
262                 InnerTypeLocation itl = new InnerTypeLocation(TypeAnnotationPosition.getTypePathFromBinary(location));
263                 subElement = subOuterType.innerTypes.vivify(itl);
264             // } else
265             //    subElement = subOuterType;
266             return subElement;
267         default:
268             throw new AssertionError();
269         }
270     }
271 
parseAnnotationSection(AElement member, AnnotationSection sec)272     private void parseAnnotationSection(AElement member, AnnotationSection sec) throws IOException, ParseException {
273         // FILL
274         while (inData()) {
275             String annoTypeName = parseAnnotationHead();
276             RetentionPolicy retention = sec.retention;
277             AnnotationBuilder ab = AnnotationFactory.saf.beginAnnotation(annoTypeName, Annotations.getRetentionPolicyMetaAnnotationSet(retention));
278             if (ab == null) {
279                 // don't care about the result
280                 // but need to skip over it anyway
281                 parseAnnotationBody(
282                         AnnotationFactory.saf.beginAnnotation(annoTypeName, Annotations.noAnnotations),
283                         SECTION_DATA_PREFIX);
284             } else {
285                 // Wrap it in a TLA with the appropriate retention policy
286                 Annotation a = parseAnnotationBody(ab, SECTION_DATA_PREFIX);
287                 // Now we need to parse the location information to determine
288                 // which element gets the annotation.
289                 AElement annoMember = chooseSubElement(member, sec);
290                 annoMember.tlAnnotationsHere.add(a);
291             }
292         }
293     }
294 
parseMember(AElement member)295     private void parseMember(AElement member) throws IOException, ParseException {
296         while (inMember()) {
297             // New section
298             String secTitle =
299                 line.substring(2, line.indexOf(':'));
300             AnnotationSection sec0 = null;
301             for (AnnotationSection s : AnnotationSection.values()) {
302                 if (s.secTitle.equals(secTitle)) {
303                     sec0 = s;
304                 }
305             }
306             if (sec0 != null) {
307                 AnnotationSection sec = sec0;
308                 nextLine();
309                 System.out.println("Got section " + secTitle);
310                 parseAnnotationSection(member, sec);
311             } else {
312                 System.out.println("Got unrecognized section " + secTitle);
313                 nextLine();
314                 // Skip the section
315                 while (inData()) {
316                     nextLine();
317                 }
318             }
319         }
320     }
321 
parseMethodBody(AElement clazz, String methodName)322     private void parseMethodBody(AElement clazz, String methodName) throws IOException, ParseException {
323         String sig = line.substring((SECTION_TITLE_PREFIX + "Signature: ").length());
324         nextLine();
325         String methodKey = methodName + sig;
326         System.out.println("Got method " + methodKey); // TEMP
327         parseMember(((AClass) clazz).methods.vivify(methodKey));
328     }
329 
330     // the "clazz" might actually be a package in case of "interface package-info"
parseClass(AElement clazz)331     private void parseClass(AElement clazz) throws IOException, ParseException {
332         parseMember(clazz);
333 
334         nextLine(); // {
335 
336         while (!line.equals("}")) {
337             // new member
338             if (line.indexOf("static {}") >= 0) {
339                 nextLine();
340                 parseMethodBody(clazz, "<clinit>");
341             } else {
342                 int lparen = line.indexOf('(');
343                 if (lparen == -1) {
344                     // field
345                     int space = line.lastIndexOf(' ');
346                     String fieldName = line.substring(space + 1, line.length() - 1);
347                     nextLine();
348                     System.out.println("Got field " + fieldName); // TEMP
349                     parseMember(((AClass) clazz).fields.vivify(fieldName));
350                 } else {
351                     // method
352                     int space = line.lastIndexOf(' ', lparen);
353                     String methodName = line.substring(space + 1, lparen);
354                     nextLine();
355                     parseMethodBody(clazz, methodName);
356                 }
357             }
358         }
359         nextLine(); // }
360     }
361 
parse()362     private void parse() throws IOException, ParseException {
363         try { // TEMP
364         nextLine(); // get the first line
365 
366         while (line != null) {
367             // new class
368             nextLine();
369             trim("public ");
370             trim("protected ");
371             trim("private ");
372             trim("abstract ");
373             trim("final ");
374             trim("class ");
375             trim("interface ");
376             int nameEnd = line.indexOf(' ');
377             String className = (nameEnd == -1) ? line
378                     : line.substring(0, line.indexOf(' '));
379             String pp = annotations.io.IOUtils.packagePart(className), bp = annotations.io.IOUtils.basenamePart(className);
380             nextLine();
381             if (bp.equals("package-info")) {
382                 parseClass(scene.packages.vivify(pp));
383             } else {
384                 parseClass(scene.classes.vivify(className));
385             }
386         }
387         } catch (RuntimeException e) {
388             throw new RuntimeException("Line " + lineNo, e);
389         }
390     }
391 
JavapParser(Reader in, AScene scene)392     private JavapParser(Reader in, AScene scene) {
393         bin = new BufferedReader(in);
394 
395         this.scene = scene;
396     }
397 
398     /**
399      * Transfers annotations from <code>in</code> to <code>scene</code>.
400      */
parse(Reader in, AScene scene)401     public static void parse(Reader in, AScene scene) throws IOException, ParseException {
402         new JavapParser(in, scene).parse();
403     }
404 
parse(String filename, AScene scene)405     public static void parse(String filename, AScene scene) throws IOException, FileIOException {
406         LineNumberReader lnr = new LineNumberReader(new FileReader(filename));
407         try {
408             parse(lnr, scene);
409         } catch (ParseException e) {
410             throw new FileIOException(lnr, filename, e);
411         }
412     }
413 }
414