001package daikon; 002 003import java.io.IOException; 004import java.io.ObjectInputStream; 005import java.io.Serializable; 006import org.checkerframework.checker.interning.qual.Interned; 007import org.checkerframework.checker.lock.qual.GuardSatisfied; 008import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; 009import org.checkerframework.checker.nullness.qual.Nullable; 010import org.checkerframework.checker.signedness.qual.UnknownSignedness; 011import org.checkerframework.dataflow.qual.Pure; 012import org.checkerframework.dataflow.qual.SideEffectFree; 013import org.plumelib.reflection.ReflectionPlume; 014 015/** 016 * PptName is an immutable ADT that represents naming data associated with a given program point, 017 * such as the class or method. 018 * 019 * <p>Examples below are as if the full value of this PptName were 020 * "DataStructures.StackAr.pop()Ljava/lang/Object;:::EXIT84" 021 * 022 * <p>PptName is deprecated, because declaration file format 2 should not need it. Uses of PptName 023 * should be eliminated. 024 */ 025// No "@Deprecated" annotation yet, but we should add it once support for 026// file format 1 is removed from Daikon. 027public class PptName implements Serializable { 028 // We are Serializable, so we specify a version to allow changes to 029 // method signatures without breaking serialization. If you add or 030 // remove fields, you should change this number to the current date. 031 static final long serialVersionUID = 20020122L; 032 033 // These are never changed but cannot be declared "final", because they 034 // must be re-interned upon deserialization. 035 /** Full program point name. */ 036 private @Interned String fullname; 037 038 // fn_name and point together comprise fullname 039 /** The part of fullname before ":::" */ 040 private @Interned String fn_name; 041 042 /** Post-separator (separator is ":::") */ 043 private @Interned String point; 044 045 // cls and method together comprise fn_name 046 /** Fully-qualified class name. */ 047 private @Nullable @Interned String cls; 048 049 /** Method signature, including types. */ 050 private final @Nullable @Interned String method; 051 052 // Representation invariant: 053 // 054 // fullname must contain ":::". 055 // fn_name is the part of fullname before ::: and point is the part after. 056 // If fn_name does contain a '(' and a '.' that comes before it, then 057 // cls is the portion before the dot and method is the portion after. 058 // If fn_name contains '(' but no dot, cls is null and method 059 // is the same as fn_name. 060 // If fn_name does not contain '(', then class is the same as fn_name and 061 // method is null. 062 063 // ==================== CONSTRUCTORS ==================== 064 065 /** 066 * @param name non-null ppt name as given in the decls file 067 */ 068 public PptName(String name) { 069 // If the name is well-formed, like "mvspc.setupGUI()V:::EXIT75", 070 // then this constructor will extract the class and method names. 071 // If not (eg for lisp code), it's okay because only the GUI uses 072 // this class/method information. 073 074 fullname = name.intern(); 075 int separatorPosition = name.indexOf(FileIO.ppt_tag_separator); 076 if (separatorPosition == -1) { 077 throw new Daikon.UserError("no ppt_tag_separator in '" + name + "'"); 078 // // probably a lisp program, which was instrumented differently 079 // cls = method = point = fn_name = null; 080 // return; 081 } 082 fn_name = name.substring(0, separatorPosition).intern(); 083 point = name.substring(separatorPosition + FileIO.ppt_tag_separator.length()).intern(); 084 085 int lparen = fn_name.indexOf('('); 086 if (lparen == -1) { 087 cls = fn_name; 088 method = null; 089 return; 090 } 091 int dot = fn_name.lastIndexOf('.', lparen); 092 if (dot == -1) { 093 method = fn_name; 094 cls = null; 095 return; 096 } 097 // now 0 <= dot < lparen 098 cls = fn_name.substring(0, dot).intern(); 099 method = fn_name.substring(dot + 1).intern(); 100 } 101 102 /** 103 * Creates a new PptName. {@code className} or {@code methodName} (or both) must be non-null. 104 * 105 * @param className fully-qualified class name 106 * @param methodName method signature, including types 107 * @param pointName post-separator (separator is ":::") 108 */ 109 public PptName(@Nullable String className, @Nullable String methodName, String pointName) { 110 if ((className == null) && (methodName == null)) { 111 throw new UnsupportedOperationException("One of class or method must be non-null"); 112 } 113 // First set class name 114 if (className != null) { 115 cls = className.intern(); 116 fn_name = cls; 117 } 118 // Then add method name 119 if (methodName == null) { 120 method = null; 121 if (fn_name == null) { 122 throw new RuntimeException("fn_name should not be null; probably bad arguments"); 123 } 124 } else { 125 method = methodName.intern(); 126 if (cls != null) { 127 fn_name = (cls + "." + method).intern(); 128 } else { 129 fn_name = method; 130 } 131 } 132 assert fn_name != null; 133 // Finally, add point 134 point = pointName.intern(); 135 fullname = (fn_name + FileIO.ppt_tag_separator + point).intern(); 136 } 137 138 // ==================== OBSERVERS ==================== 139 140 /** 141 * Returns getName() [convenience accessor]. 142 * 143 * @return getName() [convenience accessor] 144 * @see #getName() 145 */ 146 @Pure 147 public String name() { 148 return getName(); 149 } 150 151 /** 152 * Returns the complete program point name, e.g., 153 * "DataStructures.StackAr.pop()Ljava/lang/Object;:::EXIT84". 154 * 155 * @return the complete program point name 156 */ 157 @Pure 158 public String getName() { 159 return fullname; 160 } 161 162 /** 163 * Returns the fully-qualified class name, which uniquely identifies a given class. May be null. 164 * e.g. "DataStructures.StackAr". 165 * 166 * @return the fully-qualified class name 167 */ 168 public @Nullable String getFullClassName() { 169 return cls; 170 } 171 172 /** 173 * Returns the short name of the class, not including any additional context, such as the package 174 * it is in. May be null. e.g. "StackAr". 175 * 176 * @return the short name of the class, or null 177 */ 178 public @Nullable String getShortClassName() { 179 if (cls == null) { 180 return null; 181 } 182 int pt = cls.lastIndexOf('.'); 183 if (pt == -1) { 184 return cls; 185 } else { 186 return cls.substring(pt + 1); 187 } 188 } 189 190 /** 191 * Returns a guess at the package name. May be null. 192 * 193 * @return a guess at the package name. May be null 194 */ 195 public @Nullable String getPackageName() { 196 if (cls == null) { 197 return null; 198 } 199 int pt = cls.lastIndexOf('.'); 200 if (pt == -1) { 201 return null; 202 } else { 203 return cls.substring(0, pt); 204 } 205 } 206 207 /** 208 * Returns the full name which can uniquely identify a method within a class. The name includes 209 * symbols for the argument types and return type. May be null. e.g. "pop()Ljava/lang/Object;". 210 * 211 * @return the full name which can uniquely identify a method within a class 212 */ 213 public @Nullable String getSignature() { 214 return method; 215 } 216 217 /** 218 * Returns the name (identifier) of the method, not taking into account any arguments, return 219 * values, etc. May be null. e.g. "pop". 220 * 221 * @return the name (identifier) of the method, or null 222 */ 223 public @Nullable String getMethodName() { 224 if (method == null) { 225 return null; 226 } 227 int lparen = method.indexOf('('); 228 assert lparen >= 0; 229 return method.substring(0, lparen); 230 } 231 232 /** 233 * Returns the fully-qualified class and method name (and signature). Does not include any point 234 * information (such as ENTER or EXIT). May be null. e.g. 235 * "DataStructures.StackAr.pop()Ljava/lang/Object;" 236 * 237 * @return the fully-qualified class and method name (and signature) 238 */ 239 public @Nullable @Interned String getNameWithoutPoint() { 240 return fn_name; 241 // if (cls == null && method == null) { 242 // return null; 243 // } 244 // if (cls == null) { return method; } 245 // if (method == null) { return cls; } 246 // return (cls + "." + method).intern(); 247 } 248 249 /** 250 * Returns something interesting and descriptive about this point, along the lines of "ENTER" or 251 * "EXIT" or some such. The semantics of this method are not yet decided, so don't try to do 252 * anything useful with this result. May be null. e.g. "EXIT84". 253 * 254 * @return something interesting and descriptive about this 255 */ 256 public @Nullable String getPoint() { 257 return point; 258 } 259 260 /** 261 * Returns a numerical subscript of the given point, or Integer.MIN_VALUE if none exists. e.g. 262 * "84". 263 * 264 * @return a numerical subscript of the given point, or Integer.MIN_VALUE if none exists 265 * @see #exitLine() 266 */ 267 public int getPointSubscript() { 268 int result = Integer.MIN_VALUE; 269 if (point != null) { 270 // returns the largest substring [i..] which parses to an integer 271 for (int i = 0; i < point.length(); i++) { 272 char c = point.charAt(i); 273 if (('0' <= c) && (c <= '9')) { 274 try { 275 result = Integer.parseInt(point.substring(i)); 276 break; 277 } catch (NumberFormatException e) { 278 continue; 279 } 280 } 281 } 282 } 283 return result; 284 } 285 286 /** 287 * Returns true iff this name refers to a synthetic object instance program point. 288 * 289 * @return true iff this name refers to a synthetic object instance program point 290 */ 291 @Pure 292 public boolean isObjectInstanceSynthetic() { 293 return FileIO.object_suffix.equals(point); 294 } 295 296 /** 297 * Returns true iff this name refers to a synthetic class instance program point. 298 * 299 * @return true iff this name refers to a synthetic class instance program point 300 */ 301 @Pure 302 public boolean isClassStaticSynthetic() { 303 return FileIO.class_static_suffix.equals(point); 304 } 305 306 /** 307 * Returns true iff this name refers to program globals. 308 * 309 * @return true iff this name refers to program globals 310 */ 311 @Pure 312 public boolean isGlobalPoint() { 313 return FileIO.global_suffix.equals(point); 314 } 315 316 /** 317 * Returns true iff this name refers to a procedure exit point. 318 * 319 * @return true iff this name refers to a procedure exit point 320 */ 321 @EnsuresNonNullIf(result = true, expression = "point") 322 @Pure 323 public boolean isExitPoint() { 324 return (point != null) && point.startsWith(FileIO.exit_suffix); 325 } 326 327 /** 328 * Returns true iff this name refers to an abrupt completion point. 329 * 330 * @return true iff this name refers to an abrupt completion point 331 */ 332 @EnsuresNonNullIf(result = true, expression = "point") 333 @Pure 334 public boolean isThrowsPoint() { 335 return (point != null) && point.startsWith(FileIO.throws_suffix); 336 } 337 338 /** 339 * Returns true iff this name refers to a combined (synthetic) procedure exit point. 340 * 341 * @return true iff this name refers to a combined (synthetic) procedure exit point 342 */ 343 @EnsuresNonNullIf(result = true, expression = "point") 344 @Pure 345 public boolean isCombinedExitPoint() { 346 return (point != null) && point.equals(FileIO.exit_suffix); 347 } 348 349 /** 350 * Returns true iff this name refers to an actual (not combined) procedure exit point (eg, 351 * EXIT22). 352 * 353 * @return true iff this name refers to an actual (not combined) procedure exit point 354 */ 355 @EnsuresNonNullIf(result = true, expression = "point") 356 @Pure 357 public boolean isNumberedExitPoint() { 358 return (point != null) && (isExitPoint() && !isCombinedExitPoint()); 359 } 360 361 /** 362 * Returns true iff this name refers to a procedure exit point. 363 * 364 * @return true iff this name refers to a procedure exit point 365 */ 366 @EnsuresNonNullIf(result = true, expression = "point") 367 @Pure 368 public boolean isEnterPoint() { 369 return (point != null) && point.startsWith(FileIO.enter_suffix); 370 } 371 372 /** 373 * Returns a string containing the line number, if this is an exit point; otherwise, returns an 374 * empty string. 375 * 376 * @return a string containing the line number, if this is an exit point; otherwise, return an 377 * empty string 378 * @see #getPointSubscript() 379 */ 380 public String exitLine() { 381 if (!isExitPoint()) { 382 return ""; 383 } 384 int non_digit; 385 for (non_digit = FileIO.exit_suffix.length(); non_digit < point.length(); non_digit++) { 386 if (!Character.isDigit(point.charAt(non_digit))) { 387 break; 388 } 389 } 390 return point.substring(FileIO.exit_suffix.length(), non_digit); 391 } 392 393 /** 394 * Returns true iff this program point is a constructor entry or exit. There are two ways in which 395 * this works. With the older declaration format, the method name starts with {@code <init>}. The 396 * newer declaration format does not have {@code <init>} but their method name includes the class 397 * name. For compatibility both mechanisms are checked. 398 * 399 * @return true iff this program point is a constructor entry or exit 400 */ 401 @Pure 402 public boolean isConstructor() { 403 404 if (method != null) { 405 406 if (method.startsWith("<init>")) { 407 return true; 408 } 409 410 if (cls == null) { 411 return false; 412 } 413 414 @SuppressWarnings("signature") // cls is allowed to be arbitrary, especially for non-Java code 415 String class_name = ReflectionPlume.fullyQualifiedNameToSimpleName(cls); 416 assert method != null; // for nullness checker 417 int arg_start = method.indexOf('('); 418 String method_name = method; 419 if (arg_start != -1) { 420 method_name = method.substring(0, arg_start); 421 } 422 423 // System.out.println ("fullname = " + fullname); 424 // System.out.println ("fn_name = " + fn_name); 425 // System.out.println ("method = " + method); 426 // System.out.println ("cls = " + cls); 427 // System.out.println ("class_name = " + class_name); 428 // System.out.println ("method_name = " + method_name); 429 430 if (class_name.equals(method_name)) { 431 return true; 432 } 433 } 434 435 return false; 436 } 437 438 /** 439 * Debugging output. 440 * 441 * @return a string representation of this 442 */ 443 public String repr(@UnknownSignedness PptName this) { 444 return "PptName: fullname=" 445 + fullname 446 + "; fn_name=" 447 + fn_name 448 + "; point=" 449 + point 450 + "; cls=" 451 + cls 452 + "; method=" 453 + method; 454 } 455 456 // ==================== PRODUCERS ==================== 457 458 /** 459 * Requires: this.isExitPoint() 460 * 461 * @return a name for the corresponding enter point 462 */ 463 public PptName makeEnter() { 464 // This associates throw points with the main entry point. 465 // We may wish to have a different exceptional than non-exceptional 466 // entry point; in particular, if there was an exception, then perhaps 467 // the precondition or object invariant was not met. 468 assert isExitPoint() : fullname; 469 470 assert isExitPoint() || isThrowsPoint(); 471 return new PptName(cls, method, FileIO.enter_suffix); 472 } 473 474 /** 475 * Requires: this.isExitPoint() || this.isEnterPoint() 476 * 477 * @return a name for the combined exit point 478 */ 479 public PptName makeExit() { 480 assert isExitPoint() || isEnterPoint() : fullname; 481 return new PptName(cls, method, FileIO.exit_suffix); 482 } 483 484 /** 485 * Requires: this.isExitPoint() || this.isEnterPoint() 486 * 487 * @return a name for the corresponding object invariant 488 */ 489 public PptName makeObject() { 490 assert isExitPoint() || isEnterPoint() : fullname; 491 return new PptName(cls, null, FileIO.object_suffix); 492 } 493 494 /** 495 * Requires: this.isExitPoint() || this.isEnterPoint() || this.isObjectInstanceSynthetic() 496 * 497 * @return a name for the corresponding class-static invariant 498 */ 499 public PptName makeClassStatic() { 500 assert isExitPoint() || isEnterPoint() || isObjectInstanceSynthetic() : fullname; 501 return new PptName(cls, null, FileIO.class_static_suffix); 502 } 503 504 // ==================== OBJECT METHODS ==================== 505 506 /* @return interned string such that this.equals(new PptName(this.toString())) */ 507 @SideEffectFree 508 @Override 509 public String toString(@GuardSatisfied PptName this) { 510 return fullname; 511 } 512 513 @EnsuresNonNullIf(result = true, expression = "#1") 514 @Pure 515 @Override 516 public boolean equals(@GuardSatisfied PptName this, @GuardSatisfied @Nullable Object o) { 517 return (o instanceof PptName) && equalsPptName((PptName) o); 518 } 519 520 @EnsuresNonNullIf(result = true, expression = "#1") 521 @Pure 522 public boolean equalsPptName(@GuardSatisfied PptName this, @GuardSatisfied PptName o) { 523 return (o != null) && (o.fullname == fullname); 524 } 525 526 @Pure 527 @Override 528 public int hashCode(@GuardSatisfied @UnknownSignedness PptName this) { 529 return fullname.hashCode(); 530 } 531 532 // Interning is lost when an object is serialized and deserialized. 533 // Manually re-intern any interned fields upon deserialization. 534 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 535 try { 536 in.defaultReadObject(); 537 if (fullname != null) fullname = fullname.intern(); 538 if (fn_name != null) fn_name = fn_name.intern(); 539 if (cls != null) cls = cls.intern(); 540 if (method != null) { 541 // method = method.intern(); 542 ReflectionPlume.setFinalField(this, "method", method.intern()); 543 } 544 if (point != null) point = point.intern(); 545 } catch (NoSuchFieldException e) { 546 throw new Error(e); 547 } 548 } 549}