blob: c0bf4aef5d6bca3dbde79bbfef42c2c13aa64f80 [file] [log] [blame]
package annotations.io;
/*>>>
import org.checkerframework.checker.nullness.qual.*;
*/
import java.io.*;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import annotations.Annotation;
import annotations.AnnotationBuilder;
import annotations.AnnotationFactory;
import annotations.Annotations;
import annotations.el.*;
import com.sun.tools.javac.code.TargetType;
import com.sun.tools.javac.code.TypeAnnotationPosition;
import plume.FileIOException;
/**
* <code>JavapParser</code> provides a static method that parses a class dump
* in the form produced by <code>xjavap -s -verbose -annotations</code> and adds
* the annotations to an {@link AScene}, using the scene's
* {@link AnnotationFactory} to build individual annotations.
* If the scene's {@link AnnotationFactory} announces that it does not want an
* annotation found in the javap output, that annotation is skipped. Annotations
* from the javap output are merged into the scene; it is an error if both the
* scene and the javap output contain annotations of the same type on the same
* element.
*
* <p>
* THIS CLASS IS NOT FINISHED YET!
*
* <p>
* This class does not yet perform any error checking. Expect strange
* behavior and/or exceptions if you give it bad input.
*/
public final class JavapParser {
private static final String SECTION_TITLE_PREFIX = " ";
private static final String SECTION_DATA_PREFIX = " ";
private static final String CONST_POOL_DATA_PREFIX = "const #";
private final AScene scene;
private final BufferedReader bin;
private String line; // null means end-of-file
private int lineNo = 0; // TEMP
private void nextLine() throws IOException {
do {
line = bin.readLine();
lineNo++;
} while (line != null && line.equals(""));
}
private void trim(String prefix) {
if (line.startsWith(prefix)) {
line = line.substring(prefix.length());
}
}
private boolean inMember() {
return line.startsWith(SECTION_TITLE_PREFIX);
}
private boolean inData() {
return line.startsWith(SECTION_DATA_PREFIX) ||
line.startsWith(CONST_POOL_DATA_PREFIX);
}
private enum TargetMode {
ORIGINAL, PARAMETER, EXTENDED
}
// This name comes from the section of the javap output that is being read.
private enum AnnotationSection {
RVA("RuntimeVisibleAnnotations", RetentionPolicy.RUNTIME, TargetMode.ORIGINAL),
RIA("RuntimeInvisibleAnnotations", RetentionPolicy.CLASS, TargetMode.ORIGINAL),
RVPA("RuntimeVisibleParameterAnnotations", RetentionPolicy.RUNTIME, TargetMode.PARAMETER),
RIPA("RuntimeInvisibleParameterAnnotations", RetentionPolicy.CLASS, TargetMode.PARAMETER),
RVEA("RuntimeVisibleTypeAnnotations", RetentionPolicy.RUNTIME, TargetMode.EXTENDED),
RIEA("RuntimeInvisibleTypeAnnotations", RetentionPolicy.CLASS, TargetMode.EXTENDED),
;
final String secTitle;
final RetentionPolicy retention;
final TargetMode locMode;
AnnotationSection(String secTitle, RetentionPolicy retention, TargetMode locMode) {
this.secTitle = secTitle;
this.retention = retention;
this.locMode = locMode;
}
}
private String parseAnnotationHead() throws IOException, ParseException {
String annoTypeName = line.substring(
line.indexOf(annotationHead) + annotationHead.length(),
line.length() - 1).replace('/', '.');
nextLine();
return annoTypeName;
}
private static final String annotationHead = "//Annotation L"; // TEMP
private static final String tagHead = "type = "; // TEMP
private Annotation parseAnnotationBody(
AnnotationBuilder ab,
String indent) throws IOException, ParseException {
// Grab the fields
String fieldIndent = indent + " ";
while (line.startsWith(fieldIndent)) {
String line2 = line.substring(fieldIndent.length());
// Let the caller deal with location information, if any
if (line2.startsWith("target") || line2.startsWith("parameter")) {
break;
}
String fieldName =
line2.substring(line2.indexOf("//") + "//".length());
nextLine();
char tag = line.charAt(line.indexOf(tagHead) + tagHead.length());
switch (tag) {
case '[':
break;
case '@':
break;
case 'c':
break;
case 'e':
break;
}
// FINISH
}
return ab.finish();
}
private static final String paramIdxHead = "parameter = ";
private static final String offsetHead = "offset = ";
private static final String typeIndexHead = "type_index = ";
private static final Pattern localLocRegex =
Pattern.compile("^\\s*start_pc = (\\d+), length = (\\d+), index = (\\d+)$");
private static final String itlnHead = "location = ";
private int parseOffset() throws IOException, ParseException {
int offset = Integer.parseInt(
line.substring(line.indexOf(offsetHead) + offsetHead.length()));
nextLine();
return offset;
}
private int parseTypeIndex() throws IOException, ParseException {
int typeIndex = Integer.parseInt(
line.substring(line.indexOf(typeIndexHead) + typeIndexHead.length()));
nextLine();
return typeIndex;
}
private List<Integer> parseInnerTypeLocationNums() throws IOException, ParseException {
String numsStr
= line.substring(line.indexOf(itlnHead) + itlnHead.length());
List<Integer> nums = new ArrayList<Integer>();
for (;;) {
int comma = numsStr.indexOf(',');
if (comma == -1) {
nums.add(Integer.parseInt(numsStr));
break;
}
nums.add(Integer.parseInt(numsStr.substring(0, comma)));
numsStr = numsStr.substring(comma + 2);
}
nextLine();
return nums;
}
private AElement chooseSubElement(AElement member, AnnotationSection sec) throws IOException, ParseException {
switch (sec.locMode) {
case ORIGINAL:
// There can be no location information.
return member;
case PARAMETER:
{
// should have a "parameter = "
int paramIdx = Integer.parseInt(
line.substring(
line.indexOf(paramIdxHead) + paramIdxHead.length()));
nextLine();
return ((AMethod) member).parameters.vivify(paramIdx);
}
case EXTENDED:
// should have a "target = "
String targetTypeName =
line.substring(line.indexOf("//") + "//".length());
TargetType targetType;
TargetType tt = TargetType.valueOf(targetTypeName);
if (tt != null) {
targetType = tt;
} else {
throw new RuntimeException("null target type");
}
nextLine();
ATypeElement subOuterType;
AElement subElement;
switch (targetType) {
case FIELD:
case METHOD_RETURN:
subOuterType = (ATypeElement) member;
break;
case METHOD_RECEIVER:
subOuterType = ((AMethod) member).receiver.type;
break;
case METHOD_FORMAL_PARAMETER:
int paramIdx = Integer.parseInt(
line.substring(
line.indexOf(paramIdxHead) + paramIdxHead.length()));
nextLine();
subOuterType = ((AMethod) member).parameters.vivify(paramIdx).type;
break;
case LOCAL_VARIABLE:
case RESOURCE_VARIABLE:
int index, scopeStart, scopeLength;
Matcher m = localLocRegex.matcher(line);
m.matches();
index = Integer.parseInt(m.group(1));
scopeStart = Integer.parseInt(m.group(2));
scopeLength = Integer.parseInt(m.group(3));
LocalLocation ll =
new LocalLocation(index, scopeStart, scopeLength);
nextLine();
subOuterType = ((AMethod) member).body.locals.vivify(ll).type;
break;
case CAST:
{
int offset = parseOffset();
int typeIndex = parseTypeIndex();
subOuterType = ((AMethod) member).body.typecasts.vivify(RelativeLocation.createOffset(offset, typeIndex));
break;
}
case INSTANCEOF:
{
int offset = parseOffset();
subOuterType = ((AMethod) member).body.instanceofs.vivify(RelativeLocation.createOffset(offset, 0));
break;
}
case NEW:
{
int offset = parseOffset();
subOuterType = ((AMethod) member).body.news.vivify(RelativeLocation.createOffset(offset, 0));
break;
}
default:
throw new AssertionError();
}
// TODO: update location representation
// if (targetType.) {
List<Integer> location = parseInnerTypeLocationNums();
InnerTypeLocation itl = new InnerTypeLocation(TypeAnnotationPosition.getTypePathFromBinary(location));
subElement = subOuterType.innerTypes.vivify(itl);
// } else
// subElement = subOuterType;
return subElement;
default:
throw new AssertionError();
}
}
private void parseAnnotationSection(AElement member, AnnotationSection sec) throws IOException, ParseException {
// FILL
while (inData()) {
String annoTypeName = parseAnnotationHead();
RetentionPolicy retention = sec.retention;
AnnotationBuilder ab = AnnotationFactory.saf.beginAnnotation(annoTypeName, Annotations.getRetentionPolicyMetaAnnotationSet(retention));
if (ab == null) {
// don't care about the result
// but need to skip over it anyway
parseAnnotationBody(
AnnotationFactory.saf.beginAnnotation(annoTypeName, Annotations.noAnnotations),
SECTION_DATA_PREFIX);
} else {
// Wrap it in a TLA with the appropriate retention policy
Annotation a = parseAnnotationBody(ab, SECTION_DATA_PREFIX);
// Now we need to parse the location information to determine
// which element gets the annotation.
AElement annoMember = chooseSubElement(member, sec);
annoMember.tlAnnotationsHere.add(a);
}
}
}
private void parseMember(AElement member) throws IOException, ParseException {
while (inMember()) {
// New section
String secTitle =
line.substring(2, line.indexOf(':'));
AnnotationSection sec0 = null;
for (AnnotationSection s : AnnotationSection.values()) {
if (s.secTitle.equals(secTitle)) {
sec0 = s;
}
}
if (sec0 != null) {
AnnotationSection sec = sec0;
nextLine();
System.out.println("Got section " + secTitle);
parseAnnotationSection(member, sec);
} else {
System.out.println("Got unrecognized section " + secTitle);
nextLine();
// Skip the section
while (inData()) {
nextLine();
}
}
}
}
private void parseMethodBody(AElement clazz, String methodName) throws IOException, ParseException {
String sig = line.substring((SECTION_TITLE_PREFIX + "Signature: ").length());
nextLine();
String methodKey = methodName + sig;
System.out.println("Got method " + methodKey); // TEMP
parseMember(((AClass) clazz).methods.vivify(methodKey));
}
// the "clazz" might actually be a package in case of "interface package-info"
private void parseClass(AElement clazz) throws IOException, ParseException {
parseMember(clazz);
nextLine(); // {
while (!line.equals("}")) {
// new member
if (line.indexOf("static {}") >= 0) {
nextLine();
parseMethodBody(clazz, "<clinit>");
} else {
int lparen = line.indexOf('(');
if (lparen == -1) {
// field
int space = line.lastIndexOf(' ');
String fieldName = line.substring(space + 1, line.length() - 1);
nextLine();
System.out.println("Got field " + fieldName); // TEMP
parseMember(((AClass) clazz).fields.vivify(fieldName));
} else {
// method
int space = line.lastIndexOf(' ', lparen);
String methodName = line.substring(space + 1, lparen);
nextLine();
parseMethodBody(clazz, methodName);
}
}
}
nextLine(); // }
}
private void parse() throws IOException, ParseException {
try { // TEMP
nextLine(); // get the first line
while (line != null) {
// new class
nextLine();
trim("public ");
trim("protected ");
trim("private ");
trim("abstract ");
trim("final ");
trim("class ");
trim("interface ");
int nameEnd = line.indexOf(' ');
String className = (nameEnd == -1) ? line
: line.substring(0, line.indexOf(' '));
String pp = annotations.io.IOUtils.packagePart(className), bp = annotations.io.IOUtils.basenamePart(className);
nextLine();
if (bp.equals("package-info")) {
parseClass(scene.packages.vivify(pp));
} else {
parseClass(scene.classes.vivify(className));
}
}
} catch (RuntimeException e) {
throw new RuntimeException("Line " + lineNo, e);
}
}
private JavapParser(Reader in, AScene scene) {
bin = new BufferedReader(in);
this.scene = scene;
}
/**
* Transfers annotations from <code>in</code> to <code>scene</code>.
*/
public static void parse(Reader in, AScene scene) throws IOException, ParseException {
new JavapParser(in, scene).parse();
}
public static void parse(String filename, AScene scene) throws IOException, FileIOException {
LineNumberReader lnr = new LineNumberReader(new FileReader(filename));
try {
parse(lnr, scene);
} catch (ParseException e) {
throw new FileIOException(lnr, filename, e);
}
}
}