package org.checkerframework.framework.util; /*>>> import org.checkerframework.checker.nullness.qual.Nullable; */ import com.sun.source.tree.ClassTree; import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.PackageSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type.ArrayType; import com.sun.tools.javac.code.Type.ClassType; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.ArrayAccess; import org.checkerframework.dataflow.analysis.FlowExpressions.ClassName; import org.checkerframework.dataflow.analysis.FlowExpressions.FieldAccess; import org.checkerframework.dataflow.analysis.FlowExpressions.LocalVariable; import org.checkerframework.dataflow.analysis.FlowExpressions.MethodCall; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.analysis.FlowExpressions.ThisReference; import org.checkerframework.dataflow.analysis.FlowExpressions.ValueLiteral; import org.checkerframework.dataflow.cfg.node.ClassNameNode; import org.checkerframework.dataflow.cfg.node.ImplicitThisLiteralNode; import org.checkerframework.dataflow.cfg.node.LocalVariableNode; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.dataflow.cfg.node.ObjectCreationNode; import org.checkerframework.framework.source.Result; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.InternalUtils; import org.checkerframework.javacutil.Pair; import org.checkerframework.javacutil.Resolver; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; import org.checkerframework.javacutil.trees.TreeBuilder; /** * A collection of helper methods to parse a string that represents a restricted Java expression. * Such expressions can be found in annotations (e.g., to specify a pre- or postcondition). * * @author Stefan Heule */ public class FlowExpressionParseUtil { /** * Regular expression for an identifier. Permits '$' in the name, though that character never * appears in Java source code. */ protected static final String identifierRegex = "[a-zA-Z_$][a-zA-Z_$0-9]*"; /** Regular expression for a formal parameter use. */ protected static final String parameterRegex = "#([1-9][0-9]*)"; /** Unanchored; can be used to find all formal parameter uses. */ protected static final Pattern unanchoredParameterPattern = Pattern.compile(parameterRegex); /** Returns a Pattern, anchored at the beginning and end, for the regex. */ private static Pattern anchored(String regex) { return Pattern.compile("^" + regex + "$"); } // Each of the below patterns is anchored with ^...$. /** Matches a parameter */ protected static final Pattern parameterPattern = anchored(parameterRegex); /** * Matches 'this', the self reference. Does not allow "#0" because people reading the code might * assume the numbering starts at 0 and assume that #0 is the first formal parameter. */ protected static final Pattern thisPattern = anchored("this"); /** Matches 'super' */ protected static final Pattern superPattern = anchored("super"); /** Matches an identifier */ protected static final Pattern identifierPattern = anchored(identifierRegex); /** Matches a method call. Capturing groups 1 and 2 are the method and arguments. */ protected static final Pattern methodPattern = anchored("(" + identifierRegex + ")\\((.*)\\)"); /** Matches an array access. Capturing groups 1 and 2 are the array and index. */ protected static final Pattern arrayPattern = anchored("(.*)\\[(.*)\\]"); /** Matches a field access. Capturing groups 1 and 2 are the object and field. */ protected static final Pattern memberselect = anchored("([^.]+)\\.(.+)"); /** Matches integer literals */ protected static final Pattern intPattern = anchored("[-+]?[0-9]+"); /** Matches long literals */ protected static final Pattern longPattern = anchored("[-+]?[0-9]+[Ll]"); /** Matches string literals */ // Regex can be found at, for example, http://stackoverflow.com/a/481587/173852 protected static final Pattern stringPattern = anchored("\"(?:[^\"\\\\]|\\\\.)*\""); /** Matches the null literal */ protected static final Pattern nullPattern = anchored("null"); /** Matches an expression contained in matching start and end parentheses */ protected static final Pattern parenthesesPattern = anchored("\\((.*)\\)"); /** * Parse a string and return its representation as a {@link Receiver}, or throw an {@link * FlowExpressionParseException}. * * @param expression flow expression to parse * @param context information about any receiver and arguments * @param localScope path to local scope to use * @param useLocalScope whether {@code localScope} should be used to resolve identifiers */ public static FlowExpressions.Receiver parse( String expression, FlowExpressionContext context, TreePath localScope, boolean useLocalScope) throws FlowExpressionParseException { context.useLocalScope = useLocalScope; FlowExpressions.Receiver result = parseHelper(expression, context, localScope); if (result instanceof ClassName && !expression.endsWith("class")) { throw constructParserException( expression, "a class name cannot terminate a flow expression string"); } return result; } private static FlowExpressions.Receiver parseHelper( String expression, FlowExpressionContext context, TreePath path) throws FlowExpressionParseException { expression = expression.trim(); ProcessingEnvironment env = context.checkerContext.getProcessingEnvironment(); Types types = env.getTypeUtils(); if (isNullLiteral(expression, context)) { return parseNullLiteral(expression, types); } else if (isIntLiteral(expression, context)) { return parseIntLiteral(expression, types); } else if (isLongLiteral(expression, context)) { return parseLongLiteral(expression, types); } else if (isStringLiteral(expression, context)) { return parseStringLiteral(expression, types, env.getElementUtils()); } else if (isThisLiteral(expression, context)) { return parseThis(expression, context); } else if (isSuperLiteral(expression, context)) { return parseSuper(expression, types, context); } else if (isIdentifier(expression, context)) { return parseIdentifier(expression, env, path, context); } else if (isParameter(expression, context)) { return parseParameter(expression, context); } else if (isArray(expression, context)) { return parseArray(expression, context, path); } else if (isMethod(expression, context)) { return parseMethod(expression, context, path, env); } else if (isMemberSelect(expression, context)) { return parseMemberSelect(expression, env, context, path); } else if (isParentheses(expression, context)) { return parseParentheses(expression, context, path); } else { throw constructParserException(expression, "could not parse string"); } } private static boolean isMemberSelect(String s, FlowExpressionContext context) { Matcher dotMatcher = memberselect.matcher(s); return dotMatcher.matches(); } private static Receiver parseMemberSelect( String s, ProcessingEnvironment env, FlowExpressionContext context, TreePath path) throws FlowExpressionParseException { Matcher dotMatcher = memberselect.matcher(s); if (!dotMatcher.matches()) { assert false : "isMemberSelect must be called first"; } Receiver receiver; String memberSelected; Resolver resolver = new Resolver(env); // Attempt to match a package and class name first. Pair<ClassName, String> classAndRemainingString = matchPackageAndClassNameWithinExpression(s, resolver, path); if (classAndRemainingString != null) { receiver = classAndRemainingString.first; memberSelected = classAndRemainingString.second; if (memberSelected == null) { throw constructParserException( s, "a class cannot terminate a flow expression string"); } } else { String receiverString = dotMatcher.group(1); memberSelected = dotMatcher.group(2); receiver = parseHelper(receiverString, context, path); } if (memberSelected.equals("class")) { if (receiver instanceof FlowExpressions.ClassName && !context.parsingMember) { return receiver; } else { throw constructParserException(s, "class is not a legal identifier"); } } // Parse the rest, with a new receiver. FlowExpressionContext newContext = context.copyChangeToParsingMemberOfReceiver(receiver); return parseHelper(memberSelected, newContext, path); } //######## private static boolean isNullLiteral(String s, FlowExpressionContext context) { if (context.parsingMember) { return false; } Matcher nullMatcher = nullPattern.matcher(s); return nullMatcher.matches(); } private static Receiver parseNullLiteral(String expression, Types types) { return new ValueLiteral(types.getNullType(), (Object) null); } private static boolean isIntLiteral(String s, FlowExpressionContext context) { if (context.parsingMember) { return false; } Matcher intMatcher = intPattern.matcher(s); return intMatcher.matches(); } private static Receiver parseIntLiteral(String s, Types types) { int val = Integer.parseInt(s); return new ValueLiteral(types.getPrimitiveType(TypeKind.INT), val); } private static boolean isLongLiteral(String s, FlowExpressionContext context) { if (context.parsingMember) { return false; } Matcher longMatcher = longPattern.matcher(s); return longMatcher.matches(); } private static Receiver parseLongLiteral(String s, Types types) { //Remove L or l at the end of a long literal s = s.substring(0, s.length() - 1); long val = Long.parseLong(s); return new ValueLiteral(types.getPrimitiveType(TypeKind.LONG), val); } private static boolean isStringLiteral(String s, FlowExpressionContext context) { if (context.parsingMember) { return false; } Matcher stringMatcher = stringPattern.matcher(s); return stringMatcher.matches(); } private static Receiver parseStringLiteral(String s, Types types, Elements elements) { TypeElement stringTypeElem = elements.getTypeElement("java.lang.String"); return new ValueLiteral( types.getDeclaredType(stringTypeElem), s.substring(1, s.length() - 1)); } private static boolean isThisLiteral(String s, FlowExpressionContext context) { if (context.parsingMember) { // TODO: this is probably wrong because you could have and inner class receiver // Outer.this return false; } Matcher thisMatcher = thisPattern.matcher(s); return thisMatcher.matches(); } private static Receiver parseThis(String s, FlowExpressionContext context) { if (!(context.receiver == null || context.receiver.containsUnknown())) { // "this" is the receiver of the context return context.receiver; } else { return new ThisReference(context.receiver == null ? null : context.receiver.getType()); } } private static boolean isSuperLiteral(String s, FlowExpressionContext context) { if (context.parsingMember) { return false; } Matcher superMatcher = superPattern.matcher(s); return superMatcher.matches(); } private static Receiver parseSuper(String s, Types types, FlowExpressionContext context) throws FlowExpressionParseException { // super literal List<? extends TypeMirror> superTypes = types.directSupertypes(context.receiver.getType()); // find class supertype TypeMirror superType = null; for (TypeMirror t : superTypes) { // ignore interface types if (!(t instanceof ClassType)) { continue; } ClassType tt = (ClassType) t; if (!tt.isInterface()) { superType = t; break; } } if (superType == null) { throw constructParserException(s, "super class not found"); } return new ThisReference(superType); } private static boolean isIdentifier(String s, FlowExpressionContext context) { Matcher identifierMatcher = identifierPattern.matcher(s); return identifierMatcher.matches(); } private static Receiver parseIdentifier( String s, ProcessingEnvironment env, TreePath path, FlowExpressionContext context) throws FlowExpressionParseException { Resolver resolver = new Resolver(env); if (!context.parsingMember && context.useLocalScope) { // Attempt to match a local variable within the scope of the // given path before attempting to match a field. VariableElement varElem = resolver.findLocalVariableOrParameterOrField(s, path); if (varElem != null) { if (varElem.getKind() == ElementKind.FIELD) { boolean isOriginalReceiver = context.receiver instanceof ThisReference; return getReceiverField(s, context, isOriginalReceiver, varElem); } else { return new LocalVariable(varElem); } } } // field access TypeMirror receiverType = context.receiver.getType(); boolean originalReceiver = true; VariableElement fieldElem = null; if (receiverType.getKind() == TypeKind.ARRAY && s.equals("length")) { fieldElem = resolver.findField(s, receiverType, path); } // Search for field in each enclosing class. while (receiverType.getKind() == TypeKind.DECLARED) { fieldElem = resolver.findField(s, receiverType, path); if (fieldElem != null) { break; } receiverType = getTypeOfEnclosingClass((DeclaredType) receiverType); originalReceiver = false; } if (fieldElem != null && fieldElem.getKind() == ElementKind.FIELD) { return getReceiverField(s, context, originalReceiver, fieldElem); } // Class name Element classElem = resolver.findClass(s, path); TypeMirror classType = ElementUtils.getType(classElem); if (classType != null) { return new ClassName(classType); } throw constructParserException(s, "identifier not found"); } private static Receiver getReceiverField( String s, FlowExpressionContext context, boolean originalReceiver, VariableElement fieldElem) throws FlowExpressionParseException { TypeMirror receiverType = context.receiver.getType(); TypeMirror fieldType = ElementUtils.getType(fieldElem); if (ElementUtils.isStatic(fieldElem)) { Element classElem = fieldElem.getEnclosingElement(); Receiver staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); return new FieldAccess(staticClassReceiver, fieldType, fieldElem); } Receiver locationOfField; if (originalReceiver) { locationOfField = context.receiver; } else { locationOfField = FlowExpressions.internalReprOf( context.checkerContext.getAnnotationProvider(), new ImplicitThisLiteralNode(receiverType)); } if (locationOfField instanceof ClassName) { throw constructParserException( s, "a non-static field cannot have a class name as a receiver."); } return new FieldAccess(locationOfField, fieldType, fieldElem); } private static boolean isParameter(String s, FlowExpressionContext contex) { if (contex.parsingMember) { return false; } Matcher parameterMatcher = parameterPattern.matcher(s); return parameterMatcher.matches(); } private static Receiver parseParameter(String s, FlowExpressionContext context) throws FlowExpressionParseException { Matcher parameterMatcher = parameterPattern.matcher(s); if (!parameterMatcher.matches()) { return null; } if (context.arguments == null) { throw constructParserException(s, "No parameter found."); } int idx = -1; try { idx = Integer.parseInt(parameterMatcher.group(1)); } catch (NumberFormatException e) { // cannot occur by the way the pattern is defined (matches only numbers) assert false; } if (idx > context.arguments.size()) { throw new FlowExpressionParseException( Result.failure("flowexpr.parse.index.too.big", Integer.toString(idx))); } return context.arguments.get(idx - 1); } private static boolean isMethod(String s, FlowExpressionContext contex) { Matcher methodMatcher = methodPattern.matcher(s); return methodMatcher.matches(); } private static Receiver parseMethod( String s, FlowExpressionContext context, TreePath path, ProcessingEnvironment env) throws FlowExpressionParseException { Matcher methodMatcher = methodPattern.matcher(s); if (!methodMatcher.matches()) { return null; } String methodName = methodMatcher.group(1); // parse parameter list String parameterList = methodMatcher.group(2); List<Receiver> parameters = ParameterListParser.parseParameterList( parameterList, true, context.copyAndUseOuterReceiver(), path); // get types for parameters List<TypeMirror> parameterTypes = new ArrayList<>(); for (Receiver p : parameters) { parameterTypes.add(p.getType()); } ExecutableElement methodElement = null; try { Element element = null; // try to find the correct method Resolver resolver = new Resolver(env); TypeMirror receiverType = context.receiver.getType(); if (receiverType.getKind() == TypeKind.ARRAY) { element = resolver.findMethod(methodName, receiverType, path, parameterTypes); } // Search for method in each enclosing class. while (receiverType.getKind() == TypeKind.DECLARED) { element = resolver.findMethod(methodName, receiverType, path, parameterTypes); if (element.getKind() == ElementKind.METHOD) { break; } receiverType = getTypeOfEnclosingClass((DeclaredType) receiverType); } if (element == null) { throw constructParserException(s, "element==null"); } if (element.getKind() != ElementKind.METHOD) { throw constructParserException(s, "element.getKind()==" + element.getKind()); } methodElement = (ExecutableElement) element; for (int i = 0; i < parameters.size(); i++) { VariableElement formal = methodElement.getParameters().get(i); TypeMirror formalType = formal.asType(); Receiver actual = parameters.get(i); TypeMirror actualType = actual.getType(); // boxing necessary if (TypesUtils.isBoxedPrimitive(formalType) && TypesUtils.isPrimitive(actualType)) { MethodSymbol valueOfMethod = TreeBuilder.getValueOfMethod(env, formalType); List<Receiver> p = new ArrayList<>(); p.add(actual); Receiver boxedParam = new MethodCall(formalType, valueOfMethod, new ClassName(formalType), p); parameters.set(i, boxedParam); } } } catch (Throwable t) { throw constructParserException(s, t); } assert methodElement != null; // TODO: reinstate this test, but issue a warning that the user // can override, rather than halting parsing which the user cannot override. /*if (!PurityUtils.isDeterministic(context.checkerContext.getAnnotationProvider(), methodElement)) { throw new FlowExpressionParseException(Result.failure( "flowexpr.method.not.deterministic", methodElement.getSimpleName())); }*/ if (ElementUtils.isStatic(methodElement)) { Element classElem = methodElement.getEnclosingElement(); Receiver staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); return new MethodCall( ElementUtils.getType(methodElement), methodElement, staticClassReceiver, parameters); } else { if (context.receiver instanceof ClassName) { throw constructParserException( s, "a non-static method call cannot have a class name as a receiver."); } TypeMirror methodType = InternalUtils.substituteMethodReturnType( ElementUtils.getType(methodElement), context.receiver.getType()); return new MethodCall(methodType, methodElement, context.receiver, parameters); } } private static boolean isArray(String s, FlowExpressionContext context) { Matcher arraymatcher = arrayPattern.matcher(s); return arraymatcher.matches(); } private static Receiver parseArray(String s, FlowExpressionContext context, TreePath path) throws FlowExpressionParseException { Matcher arraymatcher = arrayPattern.matcher(s); if (!arraymatcher.matches()) { return null; } String receiverStr = arraymatcher.group(1); String indexStr = arraymatcher.group(2); Receiver receiver = parseHelper(receiverStr, context, path); FlowExpressionContext contextForIndex = context.copyAndUseOuterReceiver(); Receiver index = parseHelper(indexStr, contextForIndex, path); TypeMirror receiverType = receiver.getType(); if (!(receiverType instanceof ArrayType)) { throw constructParserException( s, String.format("receiver not an array: %s : %s", receiver, receiverType)); } TypeMirror componentType = ((ArrayType) receiverType).getComponentType(); ArrayAccess result = new ArrayAccess(componentType, receiver, index); return result; } private static boolean isParentheses(String s, FlowExpressionContext contex) { Matcher parenthesesMatcher = parenthesesPattern.matcher(s); return parenthesesMatcher.matches(); } private static Receiver parseParentheses(String s, FlowExpressionContext context, TreePath path) throws FlowExpressionParseException { Matcher parenthesesMatcher = parenthesesPattern.matcher(s); if (!parenthesesMatcher.matches()) { return null; } String expressionString = parenthesesMatcher.group(1); // Do not modify the value of recursiveCall, since a parenthesis match is essentially // a match to a no-op and should not semantically affect the parsing. return parseHelper(expressionString, context, path); } /** * Matches a substring of {@code expression} to a package and class name (starting from the * beginning of the string). * * @param expression the expression string that may start with a package and class name * @param resolver the {@code Resolver} for the current processing environment * @param path the tree path to the local scope * @return {@code null} if the expression string did not start with a package name; otherwise a * {@code Pair} containing the {@code ClassName} for the matched class, and the remaining * substring of the expression (possibly null) after the package and class name. * @throws FlowExpressionParseException if the entire expression string matches a package name * (but no class name), or if a package name was matched but the class could not be found * within the package (e.g., {@code "myExistingPackage.myNonExistentClass"}). */ private static Pair<ClassName, String> matchPackageAndClassNameWithinExpression( String expression, Resolver resolver, TreePath path) throws FlowExpressionParseException { Pair<PackageSymbol, String> packageSymbolAndRemainingString = matchPackageNameWithinExpression(expression, resolver, path); if (packageSymbolAndRemainingString == null) { return null; } PackageSymbol packageSymbol = packageSymbolAndRemainingString.first; String packageRemainingString = packageSymbolAndRemainingString.second; Matcher dotMatcher = memberselect.matcher(packageRemainingString); String classNameString; String remainingString; if (dotMatcher.matches()) { classNameString = dotMatcher.group(1); remainingString = dotMatcher.group(2); } else { classNameString = packageRemainingString; remainingString = null; } ClassSymbol classSymbol; try { classSymbol = resolver.findClassInPackage(classNameString, packageSymbol, path); } catch (Throwable t) { throw constructParserException( expression, " findClassInPackage threw an exception when looking up class " + classNameString + " in package " + packageSymbol.toString(), t); } if (classSymbol == null) { throw constructParserException( expression, "classSymbol==null when looking up class " + classNameString + " in package " + packageSymbol.toString()); } TypeMirror classType = ElementUtils.getType(classSymbol); if (classType == null) { throw constructParserException( expression, "classType==null when looking for class symbol " + classSymbol); } return Pair.of(new ClassName(classType), remainingString); } /** * Greedily matches the longest substring of {@code expression} to a package (starting from the * beginning of the string). * * @param expression the expression string that may start with a package name * @param resolver the {@code Resolver} for the current processing environment * @param path the tree path to the local scope * @return {@code null} if the expression string did not start with a package name; otherwise a * {@code Pair} containing the {@code PackageSymbol} for the matched package, and the * remaining substring of the expression (always non-null) after the package name * @throws FlowExpressionParseException if the entire expression string matches a package name */ private static Pair<PackageSymbol, String> matchPackageNameWithinExpression( String expression, Resolver resolver, TreePath path) throws FlowExpressionParseException { Matcher dotMatcher = memberselect.matcher(expression); // To proceed past this point, at the minimum the expression must be composed of packageName.className . // Do not remove the call to matches(), otherwise the dotMatcher groups will not be filled in. if (!dotMatcher.matches()) { return null; } String packageName = dotMatcher.group(1); String remainingString = dotMatcher.group(2), remainingStringIfPackageMatched = remainingString; PackageSymbol result = null; // the result of this method call while (true) { // At this point, packageName is one component longer than result, // and that extra component appears in remainingString but not in remainingStringIfPackageMatched. // In other words, result and remainingStringIfPackageMatched are consistent, // and packageName and remainingString are consistent. // Try to set result to account for the extra component in packageName. PackageSymbol longerResult; try { longerResult = resolver.findPackage(packageName, path); } catch (Throwable t) { throw constructParserException( expression, "findPackage threw an exception when looking up package " + packageName, t); } if (longerResult == null) { break; } result = longerResult; remainingString = remainingStringIfPackageMatched; dotMatcher = memberselect.matcher(remainingString); if (dotMatcher.matches()) { packageName += "." + dotMatcher.group(1); remainingStringIfPackageMatched = dotMatcher.group(2); } else { // There are no dots in remainingString, so we are done. // Fail if the whole string represents a package, otherwise return. PackageSymbol wholeExpressionAsPackage; try { wholeExpressionAsPackage = resolver.findPackage(expression, path); } catch (Throwable t) { throw constructParserException( expression, "findPackage threw an exception when looking up package " + expression, t); } if (wholeExpressionAsPackage != null) { // The entire expression matches a package name. throw constructParserException( expression, "a flow expression string cannot be just a package name"); } break; } } if (result == null) { return null; } // an exception would have been thrown above if the entire expression is a package name assert remainingString != null; return Pair.of(result, remainingString); } /** * A very simple parser for parameter lists, i.e. strings of the form {@code a, b, c} for some * expressions {@code a}, {@code b} and {@code c}. * * @author Stefan Heule */ private static class ParameterListParser { /** * Parse a parameter list and return the parameters as a list (or throw a {@link * FlowExpressionParseException}). */ private static List<Receiver> parseParameterList( String parameterString, boolean allowEmptyList, FlowExpressionContext context, TreePath path) throws FlowExpressionParseException { ArrayList<Receiver> result = new ArrayList<>(); // the index of the character in 'parameterString' that the parser // is currently looking at int idx = 0; // how deeply are method calls nested at this point? callLevel is 0 // in the beginning, and increases with every method call by 1. For // instance it would be 2 at the end of the following string: // "get(get(1,2," int callLevel = 0; // is the parser currently in a string literal? boolean inString = false; while (true) { // end of string reached if (idx == parameterString.length()) { // finish current param if (inString) { throw constructParserException(parameterString, "unterminated string"); } else if (callLevel > 0) { throw constructParserException( parameterString, "unterminated method invocation, callLevel==" + callLevel); } else { finishParam(parameterString, allowEmptyList, context, path, result, idx); return result; } } // get next character char next = parameterString.charAt(idx); idx++; // case split on character switch (next) { case ',': if (inString) { // stay in same state and consume the character } else { if (callLevel == 0) { // parse first parameter finishParam( parameterString, allowEmptyList, context, path, result, idx - 1); // parse remaining parameters List<Receiver> rest = parseParameterList( parameterString.substring(idx), false, context, path); result.addAll(rest); return result; } else { // not the outermost method call, defer parsing of // this parameter list to recursive call. } } break; case '"': // start or finish string inString = !inString; break; case '(': if (inString) { // stay in same state and consume the character } else { callLevel++; } break; case ')': if (inString) { // stay in same state and consume the character } else { if (callLevel == 0) { throw constructParserException(parameterString, "callLevel==0"); } else { callLevel--; } } break; default: // stay in same state and consume the character break; } } } private static void finishParam( String parameterString, boolean allowEmptyList, FlowExpressionContext context, TreePath path, ArrayList<Receiver> result, int idx) throws FlowExpressionParseException { if (idx == 0) { if (allowEmptyList) { return; } else { throw constructParserException(parameterString, "empty parameter list; idx==0"); } } else { result.add(parseHelper(parameterString.substring(0, idx), context, path)); } } } /** * @return a list of 1-based indices of all formal parameters that occur in {@code s}. Each * formal parameter occurs in s as a string like "#1" or "#4". This routine does not do * proper parsing; for instance, if "#2" appears within a string in s, then 2 would still be * in the result list. */ public static List<Integer> parameterIndices(String s) { List<Integer> result = new ArrayList<>(); Matcher matcher = unanchoredParameterPattern.matcher(s); while (matcher.find()) { int idx = Integer.parseInt(matcher.group(1)); result.add(idx); } return result; } /////////////////////////////////////////////////////////////////////////// /// Contexts /// /** * Context used to parse a flow expression. When parsing flow expression E in annotation * {@code @A(E)}, The context is the program element that is annotated by {@code @A(E)}. */ public static class FlowExpressionContext { public final Receiver receiver; public final List<Receiver> arguments; public final Receiver outerReceiver; public final BaseContext checkerContext; /* Whether or not the FlowExpressionParser is parsing the "member" part of a member select*/ public final boolean parsingMember; /* Whether the TreePath should be used to find identifiers.*/ public boolean useLocalScope; /** * Creates context for parsing a flow expression. * * @param receiver used to replace "this" in a flow expression and used to resolve * identifiers in the flow expression with an implicit "this" * @param arguments used to replace parameter references, e.g. #1, in flow expressions, null * if no arguments * @param checkerContext used to create {@link FlowExpressions.Receiver}s */ public FlowExpressionContext( Receiver receiver, List<Receiver> arguments, BaseContext checkerContext) { this(receiver, receiver, arguments, checkerContext); } private FlowExpressionContext( Receiver receiver, Receiver outerReceiver, List<Receiver> arguments, BaseContext checkerContext) { this(receiver, outerReceiver, arguments, checkerContext, false, true); } private FlowExpressionContext( Receiver receiver, Receiver outerReceiver, List<Receiver> arguments, BaseContext checkerContext, boolean parsingMember, boolean useLocalScope) { assert checkerContext != null; this.receiver = receiver; this.arguments = arguments; this.outerReceiver = outerReceiver; this.checkerContext = checkerContext; this.parsingMember = parsingMember; this.useLocalScope = useLocalScope; } /** * Creates a {@link FlowExpressionContext} for the method declared in {@code * methodDeclaration}. * * @param methodDeclaration used translate parameter numbers in a flow expression to formal * parameters of the method * @param enclosingTree used to look up fields and as type of "this" in flow expressions * @param checkerContext use to build FlowExpressions.Receiver * @return context created of {@code methodDeclaration} */ public static FlowExpressionContext buildContextForMethodDeclaration( MethodTree methodDeclaration, Tree enclosingTree, BaseContext checkerContext) { return buildContextForMethodDeclaration( methodDeclaration, InternalUtils.typeOf(enclosingTree), checkerContext); } /** * Creates a {@link FlowExpressionContext} for the method declared in {@code * methodDeclaration}. * * @param methodDeclaration used translate parameter numbers in a flow expression to formal * parameters of the method * @param enclosingType used to look up fields and as type of "this" in flow expressions * @param checkerContext use to build FlowExpressions.Receiver * @return context created of {@code methodDeclaration} */ public static FlowExpressionContext buildContextForMethodDeclaration( MethodTree methodDeclaration, TypeMirror enclosingType, BaseContext checkerContext) { Node receiver; if (methodDeclaration.getModifiers().getFlags().contains(Modifier.STATIC)) { Element classElt = ElementUtils.enclosingClass( TreeUtils.elementFromDeclaration(methodDeclaration)); receiver = new ClassNameNode(enclosingType, classElt); } else { receiver = new ImplicitThisLiteralNode(enclosingType); } Receiver internalReceiver = FlowExpressions.internalReprOf( checkerContext.getAnnotationProvider(), receiver); List<Receiver> internalArguments = new ArrayList<>(); for (VariableTree arg : methodDeclaration.getParameters()) { internalArguments.add( FlowExpressions.internalReprOf( checkerContext.getAnnotationProvider(), new LocalVariableNode(arg, receiver))); } FlowExpressionContext flowExprContext = new FlowExpressionContext(internalReceiver, internalArguments, checkerContext); return flowExprContext; } public static FlowExpressionContext buildContextForLambda( LambdaExpressionTree lambdaTree, TreePath path, BaseContext checkerContext) { TypeMirror enclosingType = InternalUtils.typeOf(TreeUtils.enclosingClass(path)); Node receiver = new ImplicitThisLiteralNode(enclosingType); Receiver internalReceiver = FlowExpressions.internalReprOf( checkerContext.getAnnotationProvider(), receiver); List<Receiver> internalArguments = new ArrayList<>(); for (VariableTree arg : lambdaTree.getParameters()) { internalArguments.add( FlowExpressions.internalReprOf( checkerContext.getAnnotationProvider(), new LocalVariableNode(arg, receiver))); } FlowExpressionContext flowExprContext = new FlowExpressionContext(internalReceiver, internalArguments, checkerContext); return flowExprContext; } /** * Creates a {@link FlowExpressionContext} for the method declared in {@code * methodDeclaration}. * * @param methodDeclaration used translate parameter numbers in a flow expression to formal * parameters of the method * @param currentPath to find the enclosing class, which is used to look up fields and as * type of "this" in flow expressions * @param checkerContext use to build FlowExpressions.Receiver * @return context created of {@code methodDeclaration} */ public static FlowExpressionContext buildContextForMethodDeclaration( MethodTree methodDeclaration, TreePath currentPath, BaseContext checkerContext) { Tree classTree = TreeUtils.enclosingClass(currentPath); return buildContextForMethodDeclaration(methodDeclaration, classTree, checkerContext); } /** * @return a {@link FlowExpressionContext} for the class {@code classTree} as seen at the * class declaration. */ public static FlowExpressionContext buildContextForClassDeclaration( ClassTree classTree, BaseContext checkerContext) { Node receiver = new ImplicitThisLiteralNode(InternalUtils.typeOf(classTree)); Receiver internalReceiver = FlowExpressions.internalReprOf( checkerContext.getAnnotationProvider(), receiver); List<Receiver> internalArguments = new ArrayList<>(); FlowExpressionContext flowExprContext = new FlowExpressionContext(internalReceiver, internalArguments, checkerContext); return flowExprContext; } /** * @return a {@link FlowExpressionContext} for the method {@code methodInvocation} * (represented as a {@link Node} as seen at the method use (i.e., at a method call * site). */ public static FlowExpressionContext buildContextForMethodUse( MethodInvocationNode methodInvocation, BaseContext checkerContext) { Node receiver = methodInvocation.getTarget().getReceiver(); Receiver internalReceiver = FlowExpressions.internalReprOf( checkerContext.getAnnotationProvider(), receiver); List<Receiver> internalArguments = new ArrayList<>(); for (Node arg : methodInvocation.getArguments()) { internalArguments.add( FlowExpressions.internalReprOf( checkerContext.getAnnotationProvider(), arg)); } FlowExpressionContext flowExprContext = new FlowExpressionContext(internalReceiver, internalArguments, checkerContext); return flowExprContext; } /** * @return a {@link FlowExpressionContext} for the constructor {@code n} (represented as a * {@link Node} as seen at the method use (i.e., at a method call site). */ public static FlowExpressionContext buildContextForNewClassUse( ObjectCreationNode n, BaseContext checkerContext) { // This returns an FlowExpressions.Unknown with the type set to the class in which the // constructor is declared Receiver internalReceiver = FlowExpressions.internalReprOf(checkerContext.getAnnotationProvider(), n); List<Receiver> internalArguments = new ArrayList<>(); for (Node arg : n.getArguments()) { internalArguments.add( FlowExpressions.internalReprOf( checkerContext.getAnnotationProvider(), arg)); } FlowExpressionContext flowExprContext = new FlowExpressionContext(internalReceiver, internalArguments, checkerContext); return flowExprContext; } /** * Returns a copy of the context that differs in that it has a different receiver. The outer * receiver remains unchanged. */ public FlowExpressionContext copyChangeToParsingMemberOfReceiver(Receiver receiver) { return new FlowExpressionContext( receiver, outerReceiver, arguments, checkerContext, true, useLocalScope); } /** * Returns a copy of the context that differs in that it uses the outer receiver as main * receiver (and also uses it as the outer receiver). */ public FlowExpressionContext copyAndUseOuterReceiver() { return new FlowExpressionContext( outerReceiver, outerReceiver, arguments, checkerContext, false, useLocalScope); } } /** * Returns the type of the inner most enclosing class.Type.noType is returned if no enclosing * class is found. This is in contrast to {@link DeclaredType#getEnclosingType()} which returns * the type of the inner most instance. If the inner most enclosing class is static this method * will return the type of that class where as {@link DeclaredType#getEnclosingType()} will * return the type of the inner most enclosing class that is not static. * * @param type a DeclaredType * @return the type of the innermost enclosing class or Type.noType */ private static TypeMirror getTypeOfEnclosingClass(DeclaredType type) { if (type instanceof ClassType) { // enclClass() needs to be called on tsym.owner, // otherwise it simply returns tsym. Symbol sym = ((ClassType) type).tsym.owner; if (sym == null) { return Type.noType; } ClassSymbol cs = sym.enclClass(); if (cs == null) { return Type.noType; } return cs.asType(); } else { return type.getEnclosingType(); } } /////////////////////////////////////////////////////////////////////////// /// Exceptions /// /** * An exception that indicates a parse error. It contains a {@link Result} that can be used for * error reporting. */ public static class FlowExpressionParseException extends Exception { private static final long serialVersionUID = 1L; protected final Result result; public FlowExpressionParseException(Result result) { this(result, null); } public FlowExpressionParseException(Result result, Throwable cause) { super(cause); this.result = result; } public Result getResult() { return result; } } /** * Returns a {@link FlowExpressionParseException} for the expression {@code expr} with * explanation {@code explanation}. */ private static FlowExpressionParseException constructParserException( String expr, String explanation) { return constructParserException(expr, explanation, null); } /** * Returns a {@link FlowExpressionParseException} for the expression {@code expr} whose parsing * threw {@code cause}. */ private static FlowExpressionParseException constructParserException( String expr, Throwable cause) { return constructParserException(expr, null, cause); } /** * Returns a {@link FlowExpressionParseException} for the expression {@code expr} with * explanation {@code explanation}, whose parsing threw {@code cause}. */ private static FlowExpressionParseException constructParserException( String expr, String explanation, Throwable cause) { String message = expr + ((explanation == null) ? "" : (": " + explanation)) + ((cause == null) ? "" : (": " + cause.getMessage())); return new FlowExpressionParseException( Result.failure("flowexpr.parse.error", message), cause); } }