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