001package daikon.tools.jtb; 002 003import daikon.*; 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.List; 007import jtb.*; 008import jtb.syntaxtree.*; 009import jtb.visitor.*; 010import org.checkerframework.checker.nullness.qual.NonNull; 011import org.checkerframework.checker.nullness.qual.Nullable; 012 013/** 014 * Matches program point names with their corresponding MethodDeclaration's (or 015 * ConstructorDeclaration's) in an AST. 016 * 017 * <p>There are a number of issues in matching, for example, ASTs contain generics, and program 018 * point names do not. This implementation handles such issues. 019 */ 020public class PptNameMatcher { 021 022 // Output debugging information when matching a PptName to an AST. 023 private static boolean debug_getMatches = false; 024 025 /** Create an AST matcher that will match program points against AST elements rooted at `root'. */ 026 public PptNameMatcher(Node root) { 027 root.accept(new ClassOrInterfaceTypeDecorateVisitor()); 028 } 029 030 // f0 -> Modifiers() 031 // f1 -> [ "final" | Annotation() ] 032 // f2 -> Type() 033 // f3 -> [ "..." ] 034 // f4 -> VariableDeclaratorId() 035 public String getUngenerifiedType(FormalParameter p) { 036 037 Type type = p.f2; 038 039 // Grammar production for type: 040 // f0 -> ReferenceType() 041 // | PrimitiveType() 042 043 if (type.f0.which == 0) { 044 // It's a reference type. 045 ReferenceType refType = (ReferenceType) type.f0.choice; 046 // Grammar production for ReferenceType: 047 // f0 -> PrimitiveType() ( "[" "]" )+ 048 // | ( ClassOrInterfaceType() ) ( "[" "]" )* 049 050 if (refType.f0.which == 0) { 051 // It's a primitive array; no generics to handle. 052 return Ast.getType(p); 053 054 } else { 055 056 // Make a copy of param (because we may modify it: we may 057 // remove some generics stuff). 058 // p.accept(new TreeFormatter()); 059 FormalParameter param = (FormalParameter) Ast.create("FormalParameter", Ast.format(p)); 060 061 Type type2 = param.f2; 062 ReferenceType refType2 = (ReferenceType) type2.f0.choice; 063 064 // Note the wrapping parentheses in 065 // ( ClassOrInterfaceType() ) ( "[" "]" )* 066 NodeSequence intermediateSequence = (NodeSequence) refType2.f0.choice; 067 NodeSequence intermediateSequenceOrig = (NodeSequence) refType.f0.choice; 068 NodeSequence seq = (NodeSequence) intermediateSequence.elementAt(0); 069 NodeSequence seqOrig = (NodeSequence) intermediateSequenceOrig.elementAt(0); 070 071 List<Node> singleElementList = seq.nodes; 072 List<Node> singleElementListOrig = seqOrig.nodes; 073 // Replace the ClassOrInterfaceType with its ungenerified version. 074 075 // System.out.println("@0"); 076 // param.accept(new TreeDumper()); 077 078 // ClassOrInterfaceType t = (ClassOrInterfaceType)singleElementList.get(0); 079 ClassOrInterfaceType tOrig = (ClassOrInterfaceType) singleElementListOrig.get(0); 080 assert tOrig.unGenerifiedVersionOfThis != null; 081 singleElementList.set(0, tOrig.unGenerifiedVersionOfThis); 082 // Return getType of the ungenerified version of p. 083 084 // tOrig.unGenerifiedVersionOfThis may have line/col numbering 085 // that's inconsistent with param, so we call a formatter 086 // here. param is only used for matching, and afterwards it's 087 // discarded. So it's ok to reformat it. 088 param.accept(new TreeFormatter()); 089 090 // System.out.println("@1"); 091 // param.accept(new TreeDumper()); 092 // System.out.println("@2"); 093 094 return Ast.getType(param); 095 } 096 097 } else { 098 // It's a primitive; no generics to handle. 099 return Ast.getType(p); 100 } 101 } 102 103 /** Iterates through program points and returns those that match the given method declaration. */ 104 public List<PptTopLevel> getMatches(PptMap ppts, MethodDeclaration methdecl) { 105 return getMatchesInternal(ppts, methdecl); 106 } 107 108 /** 109 * Iterates through program points and returns those that match the given constructor declaration. 110 */ 111 public List<PptTopLevel> getMatches(PptMap ppts, ConstructorDeclaration constrdecl) { 112 return getMatchesInternal(ppts, constrdecl); 113 } 114 115 // Iterates through program points and returns those that match the 116 // given method or constructor declaration. 117 private List<PptTopLevel> getMatchesInternal(PptMap ppts, Node methodOrConstructorDeclaration) { 118 119 List<PptTopLevel> result = new ArrayList<>(); 120 121 for (PptTopLevel ppt : ppts.pptIterable()) { 122 PptName ppt_name = ppt.ppt_name; 123 124 if (matches(ppt_name, methodOrConstructorDeclaration)) { 125 result.add(ppt); 126 } 127 } 128 129 if (debug_getMatches) { 130 System.out.println("getMatchesInternal => " + result); 131 } 132 return result; 133 } 134 135 public boolean matches(PptName pptName, Node methodOrConstructorDeclaration) { 136 137 // This method figures out three things and then calls another 138 // method to do the match. The three things are: 139 140 // 1. method name 141 // 2. class name 142 // 3. method parameters 143 144 String classname; 145 String methodname; 146 List<FormalParameter> params; 147 148 if (methodOrConstructorDeclaration instanceof MethodDeclaration) { 149 classname = Ast.getClassName((MethodDeclaration) methodOrConstructorDeclaration); 150 methodname = Ast.getName((MethodDeclaration) methodOrConstructorDeclaration); 151 params = Ast.getParameters((MethodDeclaration) methodOrConstructorDeclaration); 152 } else if (methodOrConstructorDeclaration instanceof ConstructorDeclaration) { 153 classname = Ast.getClassName((ConstructorDeclaration) methodOrConstructorDeclaration); 154 methodname = "<init>"; 155 params = Ast.getParameters((ConstructorDeclaration) methodOrConstructorDeclaration); 156 } else { 157 throw new Error( 158 "Bad type in Ast.getMatches: must be a MethodDeclaration or a ConstructorDeclaration:" 159 + methodOrConstructorDeclaration); 160 } 161 162 if (debug_getMatches) { 163 System.out.printf("getMatches(%s, %s, ...)%n", classname, methodname); 164 } 165 if (methodname.equals("<init>")) { 166 methodname = simpleName(classname); 167 if (debug_getMatches) { 168 System.out.printf("new methodname: getMatches(%s, %s, ...)%n", classname, methodname); 169 } 170 } 171 172 if (debug_getMatches) { 173 System.out.println("getMatch goal = " + classname + " " + methodname); 174 } 175 176 return matches(pptName, classname, methodname, params); 177 } 178 179 // True if pptName's name matches the method represented by the rest 180 // of the parameters. 181 private boolean matches( 182 PptName pptName, String classname, String methodname, List<FormalParameter> method_params) { 183 184 // The goal is a fully qualified classname such as 185 // samples.calculator.Calculator.AbstractOperandState, but 186 // pptName.getFullClassName() can be a binary name such as 187 // samples.calculator.Calculator$AbstractOperandState, at least for the 188 // :::OBJECT program point. Is that a bug? 189 190 // Furthermore, pptName.getMethodName may be null for a constructor. 191 192 String pptClassName = pptName.getFullClassName(); 193 boolean classname_matches = 194 (classname.equals(pptClassName) 195 || ((pptClassName != null) && classname.equals(pptClassName.replace('$', '.')))); 196 String pptMethodName = pptName.getMethodName(); 197 boolean methodname_matches = 198 (methodname.equals(pptMethodName) 199 || ((pptMethodName != null) 200 && (pptMethodName.indexOf('$') >= 0) 201 && methodname.equals(pptMethodName.substring(pptMethodName.lastIndexOf('$') + 1)))); 202 203 if (!(classname_matches && methodname_matches)) { 204 if (debug_getMatches) { 205 System.out.printf( 206 "getMatch: class name %s and method name %s DO NOT match candidate.%n", 207 pptClassName, pptMethodName); 208 } 209 return false; 210 } 211 if (debug_getMatches) { 212 System.out.printf( 213 "getMatch: class name %s and method name %s DO match candidate.%n", 214 classname, methodname); 215 } 216 217 List<String> pptTypeStrings = extractPptArgs(pptName); 218 219 if (pptTypeStrings.size() != method_params.size()) { 220 221 // An inner class constructor has an extra first parameter 222 // that is an implicit outer this parameter. If so, remove it 223 // before checking for a match. 224 boolean OK_outer_this = false; 225 if (pptTypeStrings.size() == method_params.size() + 1) { 226 String icName = innerConstructorName(pptName); 227 if (icName != null) { 228 String param0Full = pptTypeStrings.get(0); 229 // String param0Simple = simpleName(pptTypeStrings.get(0)); 230 // Need to check whether param0Simple is the superclass of icName. How to do that? 231 if (classname.startsWith(param0Full + ".")) { 232 OK_outer_this = true; 233 pptTypeStrings = new ArrayList<String>(pptTypeStrings); 234 pptTypeStrings.remove(0); 235 } 236 } 237 } 238 if (!OK_outer_this) { 239 if (debug_getMatches) { 240 System.out.println( 241 "arg lengths mismatch: " + pptTypeStrings.size() + ", " + method_params.size()); 242 } 243 return false; 244 } 245 } 246 247 for (int i = 0; i < pptTypeStrings.size(); i++) { 248 String pptTypeString = pptTypeStrings.get(i); 249 FormalParameter astType = method_params.get(i); 250 251 if (debug_getMatches) { 252 System.out.println( 253 "getMatch considering " 254 + pptTypeString 255 + " (" 256 + pptName.getFullClassName() 257 + "," 258 + pptName.getMethodName() 259 + ")"); 260 } 261 262 if (debug_getMatches) { 263 System.out.println("Trying to match at arg position " + Integer.toString(i)); 264 } 265 266 if (!typeMatch(pptTypeString, astType)) { 267 return false; 268 } else { 269 continue; 270 } 271 } 272 273 return true; 274 } 275 276 public boolean typeMatch(String pptTypeString, FormalParameter astFormalParameter) { 277 278 if (debug_getMatches) { 279 System.out.println(Ast.formatEntireTree(astFormalParameter)); 280 } 281 282 String astTypeString = getUngenerifiedType(astFormalParameter); 283 284 if (debug_getMatches) { 285 System.out.println("Comparing " + pptTypeString + " to " + astTypeString + ":"); 286 } 287 288 if (Ast.typeMatch(pptTypeString, astTypeString)) { 289 if (debug_getMatches) { 290 System.out.println("Match arg: " + pptTypeString + " " + astTypeString); 291 } 292 return true; 293 } 294 295 if ((pptTypeString != null) && Ast.typeMatch(pptTypeString, astTypeString)) { 296 if (debug_getMatches) { 297 System.out.println("Match arg: " + pptTypeString + " " + astTypeString); 298 } 299 return true; 300 } 301 302 if (debug_getMatches) { 303 System.out.println("Mismatch arg: " + pptTypeString + " " + astTypeString); 304 } 305 306 return false; 307 } 308 309 public List<String> extractPptArgs(PptName ppt_name) { 310 311 @SuppressWarnings("nullness") // application invariant 312 @NonNull String pptFullMethodName = ppt_name.getSignature(); 313 314 if (debug_getMatches) { 315 System.out.println("in extractPptArgs: pptFullMethodName = " + pptFullMethodName); 316 } 317 int lparen = pptFullMethodName.indexOf('('); 318 int rparen = pptFullMethodName.indexOf(')'); 319 assert lparen > 0; 320 assert rparen > lparen; 321 String ppt_args_string = pptFullMethodName.substring(lparen + 1, rparen); 322 String[] ppt_args = ppt_args_string.split(", "); 323 if ((ppt_args.length == 1) && ppt_args[0].equals("")) { 324 ppt_args = new String[0]; 325 } 326 327 return Arrays.<String>asList(ppt_args); 328 } 329 330 /** Returns simple name of inner class, or null if ppt_name is not an inner constructor. */ 331 private static @Nullable String innerConstructorName(PptName pptName) { 332 String mname = pptName.getMethodName(); 333 if (mname == null) { 334 return null; 335 } 336 int dollarpos = mname.lastIndexOf('$'); 337 if (dollarpos >= 0) { 338 return mname.substring(dollarpos + 1); 339 } 340 return null; 341 } 342 343 /** 344 * Returns the simple name of a possibly-fully-qualified class name. The argument can be a 345 * fully-qualified name or a binary name. 346 */ 347 private static String simpleName(String classname) { 348 int dotpos = classname.lastIndexOf('.'); 349 int dollarpos = classname.lastIndexOf('$'); 350 int pos = Math.max(dotpos, dollarpos); 351 if (pos == -1) { 352 return classname; 353 } else { 354 return classname.substring(pos + 1); 355 } 356 } 357}