001package daikon; 002 003import daikon.PptTopLevel.PptType; 004import java.io.File; 005import java.io.IOException; 006import java.util.ArrayList; 007import java.util.LinkedHashMap; 008import java.util.List; 009import java.util.Map; 010import org.checkerframework.checker.interning.qual.Interned; 011import org.checkerframework.checker.nullness.qual.KeyFor; 012import org.checkerframework.checker.nullness.qual.NonNull; 013import org.checkerframework.checker.nullness.qual.Nullable; 014import org.checkerframework.dataflow.qual.Pure; 015import org.plumelib.options.Option; 016import org.plumelib.options.Options; 017import org.plumelib.reflection.Signatures; 018 019/** 020 * AnnotateNullable reads a Daikon invariant file and determines which reference variables have seen 021 * any null values. It writes to standard out an <a 022 * href="https://checkerframework.org/annotation-file-utilities/annotation-file-format.html">annotation 023 * file</a> with those variables. It determines which variables have seen null values by looking at 024 * the NonZero invariant. If that invariant is NOT present, then the variable must have been null at 025 * least once. 026 * 027 * <p>Since only the NonZero invariant is used, Daikon processing time can be significantly reduced 028 * by turning off derived variables and all invariants other than daikon.inv.unary.scalar.NonZero. 029 * This is not necessary, however, for correct operation. File {@code annotate_nullable.config} in 030 * the distribution does this. 031 */ 032public class AnnotateNullable { 033 034 // Why is this variable static? 035 static PptMap ppts = new PptMap(); // dummy value, to satisfy Nullness Checker 036 037 // static SimpleLog verbose = new SimpleLog(/*enabled=*/ false); 038 // static SimpleLog debug = new SimpleLog(/*enabled=*/ false); 039 040 /** Map from a class name to the list of static functions for that class. */ 041 static Map<String, List<PptTopLevel>> class_map = new LinkedHashMap<>(); 042 043 // The package for the previous class. Used to reduce duplication in 044 // output file. 045 static @Interned String last_package = ""; 046 047 /** 048 * Write an output file in the stub class format (see the Checker Framework Manual), instead of in 049 * annotation file format. 050 */ 051 @Option("Use the stub class file format") 052 public static boolean stub_format = false; 053 054 /** 055 * Adds NonNull annotations as well as Nullable annotations. Unlike Nullable annotations, NonNull 056 * annotations are not necessarily correct. 057 */ 058 @Option("-n Insert NonNull as well as Nullable annotations") 059 public static boolean nonnull_annotations = false; 060 061 public static void main(String[] args) throws IOException { 062 063 Options options = 064 new Options("plume.AnnotateNullable [options] <inv_file>", AnnotateNullable.class); 065 String[] inv_files = options.parse(true, args); 066 assert inv_files.length == 1; 067 068 // Read the serialized invariant file 069 File inv_file = new File(inv_files[0]); 070 ppts = FileIO.read_serialized_pptmap(inv_file, true); 071 Daikon.all_ppts = ppts; 072 // verbose.log("Finished reading %d program points", ppts.size()); 073 074 // Setup the list of proto invariants and initialize NIS suppressions 075 Daikon.setup_proto_invs(); 076 Daikon.setup_NISuppression(); 077 078 // Write out the definitions of our annotations 079 if (stub_format) { 080 System.out.println("import org.checkerframework.checker.nullness.qual.Nullable;"); 081 System.out.println("import org.checkerframework.checker.nullness.qual.NonNull;"); 082 System.out.println(); 083 } else { 084 System.out.println("package org.checkerframework.checker.nullness.qual:"); 085 System.out.println("annotation @Nullable:"); 086 System.out.println("annotation @NonNull:"); 087 System.out.println(); 088 } 089 090 // Find all exit ppts that do not have a parent and determine what 091 // class they are associated with. These are static methods for classes 092 // without any static variables (no class ppt is created if there are no 093 // static variables) 094 095 // First find all of the classes 096 for (PptTopLevel ppt : ppts.pptIterable()) { 097 if (ppt.is_object()) { 098 String classname = ppt.name().replace(":::OBJECT", ""); 099 assert !class_map.containsKey(classname) : classname; 100 List<PptTopLevel> static_methods = new ArrayList<>(); 101 class_map.put(classname, static_methods); 102 } 103 } 104 105 // Then, add combined exit points for static methods to their class. A 106 // static method can be identified because it will not have the OBJECT 107 // point as a parent. 108 for (PptTopLevel ppt : ppts.pptIterable()) { 109 if (!ppt.is_combined_exit() || !is_static_method(ppt)) { 110 continue; 111 } 112 113 String name = ppt.name().replaceFirst("[(].*$", ""); 114 int lastdot = name.lastIndexOf('.'); 115 @SuppressWarnings("keyfor") // application invariant: KeyFor and substring 116 // @KeyFor because class_map has entry per class, and this method is in some class 117 @KeyFor("class_map") String classname = name.substring(0, lastdot); 118 // System.out.printf("classname for ppt %s is '%s'%n", name, classname); 119 @NonNull List<PptTopLevel> static_methods = class_map.get(classname); 120 assert static_methods != null : classname; 121 static_methods.add(ppt); 122 } 123 124 // Debug print all of the static methods 125 if (false) { 126 for (String classname : class_map.keySet()) { 127 System.out.printf("class %s static methods: %s%n", classname, class_map.get(classname)); 128 } 129 } 130 131 // Make sure that the static methods found by inference, match those 132 // found for any class ppts 133 for (PptTopLevel ppt : ppts.pptIterable()) { 134 if (ppt.is_class()) { 135 @SuppressWarnings( 136 "nullness") // map: retrieve class name from class Ppt name, with string manipulation 137 @NonNull List<PptTopLevel> static_methods = 138 class_map.get(ppt.name().replace(":::CLASS", "")); 139 int child_cnt = 0; 140 // TODO: Once Checker Framework issue 565 has been fixed (https://tinyurl.com/cfissue/565), 141 // change the following two lines back to 142 // for (PptRelation child_rel : ppt.children) { 143 for (int i = 0; i < ppt.children.size(); i++) { 144 PptRelation child_rel = ppt.children.get(i); 145 PptTopLevel child = child_rel.child; 146 // Skip enter ppts, all of the info is at the exit. 147 if ((child.type == PptType.ENTER) || (child.type == PptType.OBJECT)) { 148 continue; 149 } 150 child_cnt++; 151 assert static_methods.contains(child) : child; 152 } 153 assert child_cnt == static_methods.size() : static_methods; 154 } 155 } 156 157 // Process each class. 158 for (PptTopLevel ppt : ppts.pptIterable()) { 159 160 // Skip synthetic program points 161 if (ppt.name().startsWith("$")) { 162 continue; 163 } 164 165 // Skip program points that are not OBJECT ppts 166 if (ppt.is_object()) { 167 process_class(ppt); 168 } 169 } 170 } 171 172 // Returns null if no corresponding class ppt exists 173 private static @Nullable PptTopLevel class_for_object(PptTopLevel object_ppt) { 174 if (object_ppt.parents.size() == 0) { 175 return null; 176 } 177 assert object_ppt.parents.size() == 1 : object_ppt; 178 return object_ppt.parents.get(0).parent; 179 } 180 181 // Process a class, including all its methods. 182 // Takes the object program point as its argument. 183 public static void process_class(PptTopLevel object_ppt) { 184 185 // Get the class program point (if any) 186 PptTopLevel class_ppt = class_for_object(object_ppt); 187 188 String class_samples = "-"; 189 if (class_ppt != null) { 190 class_samples = String.format("%d", class_ppt.num_samples()); 191 } 192 String ppt_package = object_ppt.ppt_name.getPackageName(); 193 if (ppt_package == null) { 194 ppt_package = ""; 195 } else { 196 ppt_package = ppt_package.intern(); 197 } 198 if (stub_format) { 199 if (ppt_package != last_package) { 200 // This will print the empty string if we switch from a package to the 201 // unnamed package. That is intentional. 202 System.out.printf("package %s;%n%n", ppt_package); 203 last_package = ppt_package; 204 } 205 System.out.printf( 206 "class %s { // %d/%s obj/class samples%n", 207 object_ppt.ppt_name.getFullClassName(), object_ppt.num_samples(), class_samples); 208 } else { 209 System.out.printf("package %s:%n", ppt_package); 210 System.out.printf( 211 "class %s : // %d/%s obj/class samples%n", 212 object_ppt.ppt_name.getShortClassName(), object_ppt.num_samples(), class_samples); 213 } 214 215 // Process static fields 216 if (class_ppt != null) { 217 process_obj_fields(class_ppt); 218 } 219 220 // Process member (non-static) fields 221 process_obj_fields(object_ppt); 222 223 // Process static methods 224 if (class_ppt != null) { 225 for (PptRelation child_rel : class_ppt.children) { 226 PptTopLevel child = child_rel.child; 227 // Skip enter ppts, all of the info is at the exit. 228 if ((child.type == PptType.ENTER) || (child.type == PptType.OBJECT)) { 229 continue; 230 } 231 // debug.log("processing static method %s, type %s", child, child.type); 232 process_method(child); 233 } 234 } else { 235 String classname = object_ppt.ppt_name.getFullClassName(); 236 assert classname != null; 237 @SuppressWarnings("nullness") // map: class_map has entry per classname 238 @NonNull List<PptTopLevel> static_methods = class_map.get(classname); 239 assert static_methods != null : classname; 240 for (PptTopLevel child : static_methods) { 241 process_method(child); 242 } 243 } 244 245 // Process member (non-static) methods 246 for (PptRelation child_rel : object_ppt.children) { 247 PptTopLevel child = child_rel.child; 248 // Skip enter ppts, all of the info is at the exit. 249 if (child.type == PptType.ENTER) { 250 continue; 251 } 252 // debug.log("processing method %s, type %s", child, child.type); 253 process_method(child); 254 } 255 256 if (stub_format) { 257 System.out.printf("}"); 258 } 259 System.out.println(); 260 System.out.println(); 261 } 262 263 /** 264 * Get the annotation for the specified variable. Returns @Nullable if samples were found for this 265 * variable and at least one sample contained a null value. Returns an (interned) empty string if 266 * no annotation is applicable. Otherwise, the return value contains a trailing space. 267 */ 268 public static String get_annotation(PptTopLevel ppt, VarInfo vi) { 269 270 if (vi.type.isPrimitive()) { 271 return ""; 272 } 273 274 String annotation = (nonnull_annotations ? "NonNull" : ""); 275 if ((ppt.num_samples(vi) > 0) && !ppt.is_nonzero(vi)) { 276 annotation = "Nullable"; 277 } 278 if (annotation != "") { // interned 279 // if (! stub_format) { 280 // annotation = "org.checkerframework.checker.nullness.qual." + annotation; 281 // } 282 annotation = "@" + annotation; 283 } 284 return annotation; 285 } 286 287 /** Print out the annotations for the specified method. */ 288 public static void process_method(PptTopLevel ppt) { 289 290 assert ppt.type == PptType.EXIT : ppt; 291 292 // Get all of the parameters to the method and the return value 293 List<VarInfo> params = new ArrayList<>(); 294 VarInfo retvar = null; 295 for (VarInfo vi : ppt.var_infos) { 296 if (vi.var_kind == VarInfo.VarKind.RETURN) { 297 retvar = vi; 298 } else { 299 if (vi.isParam() 300 && (vi.name() != "this") // interned 301 && !vi.isPrestate()) { 302 params.add(vi); 303 } 304 } 305 } 306 307 // The formatted annotation for the return value with a leading space, or empty string 308 String return_annotation = (retvar == null ? "" : " " + get_annotation(ppt, retvar)); 309 310 // Look up the annotation for each parameter. 311 List<String> names = new ArrayList<>(); 312 List<String> annos = new ArrayList<>(); 313 for (VarInfo param : params) { 314 String annotation = ""; 315 names.add(param.name()); 316 if (param.file_rep_type.isHashcode()) { 317 annotation = get_annotation(ppt, param); 318 } 319 annos.add(annotation); 320 } 321 322 // Print out the method declaration 323 if (stub_format) { 324 System.out.printf(" %s %s(", return_annotation, ppt.ppt_name.getMethodName()); 325 for (int i = 0; i < params.size(); i++) { 326 if (i != 0) { 327 System.out.printf(" ,"); 328 } 329 System.out.printf("%s %s %s", annos.get(i), "type-goes-here", names.get(i)); 330 } 331 System.out.printf("); // %d samples%n", ppt.num_samples()); 332 } else { 333 System.out.printf(" method %s : // %d samples%n", jvm_signature(ppt), ppt.num_samples()); 334 System.out.printf(" return:%s%n", return_annotation); 335 for (int i = 0; i < params.size(); i++) { 336 // Print the annotation for this parameter 337 System.out.printf(" parameter #%d : %s // %s%n", i, annos.get(i), names.get(i)); 338 } 339 } 340 } 341 342 /** Print out the annotations for each field in the object or class. */ 343 public static void process_obj_fields(PptTopLevel ppt) { 344 345 for (VarInfo vi : ppt.var_infos) { 346 347 assert !vi.isPrestate() : vi; 348 349 // Skip anyone with a parent in the hierarchy. We are only 350 // interested in them at the top (e.g., we don't want to see 351 // object fields in each method). 352 if (!vi.parents.isEmpty()) { 353 continue; 354 } 355 356 // Skip variables that are always non-null. 357 if (vi.aux.isNonNull()) { 358 continue; 359 } 360 361 // Skip any variable that is enclosed by a variable other than 'this'. 362 // These are fields and can only be annotated where they are declared. 363 VarInfo evar = vi.get_enclosing_var(); 364 if ((evar != null) && !evar.name().equals("this")) { 365 // System.out.printf(" enclosed %s %s%n", vi.type, vi.name()); 366 continue; 367 } 368 369 // print out the entry for this field 370 String annotation = ""; 371 if (vi.file_rep_type.isHashcode()) { 372 annotation = get_annotation(ppt, vi); 373 } 374 if (stub_format) { 375 System.out.printf(" field %s %s {} // %s%n", field_name(vi), annotation, vi.type); 376 } else { 377 System.out.printf(" field %s : %s // %s%n", field_name(vi), annotation, vi.type); 378 } 379 } 380 } 381 382 /** Returns a JVM signature for the method. */ 383 public static String jvm_signature(PptTopLevel ppt) { 384 385 @SuppressWarnings("nullness") // Java method, so getMethodName() != null 386 @NonNull String method = ppt.ppt_name.getMethodName(); 387 @SuppressWarnings("nullness") // Java method, so getSignature() != null 388 @NonNull String java_sig = ppt.ppt_name.getSignature(); 389 String java_args = java_sig.replace(method, ""); 390 // System.out.printf("m/s/a = %s %s %s%n", method, java_sig, java_args); 391 if (method.equals(ppt.ppt_name.getShortClassName())) { 392 method = "<init>"; 393 } 394 395 // Problem: I need the return type, but Chicory does not output it. 396 // So, I could try to retrieve it from the "return" variable in the 397 // program point (which is, fortunately, always an exit point), or 398 // change Chicory to output it. 399 VarInfo returnVar = ppt.find_var_by_name("return"); 400 @SuppressWarnings( 401 "signature" // application invariant: returnVar.type.toString() is a binary name (if 402 // returnVar is non-null), because we are processing a Java program 403 ) 404 String returnType = 405 returnVar == null ? "V" : Signatures.binaryNameToFieldDescriptor(returnVar.type.toString()); 406 407 return method + Signatures.arglistToJvm(java_args) + returnType; 408 } 409 410 /** 411 * Returns the field name of the specified variable. This is the relative name for instance 412 * fields, but the relative name is not specified for static fields (because there is no enclosing 413 * variable with the full name). The field name is obtained in that case, by removing the 414 * package/class specifier. 415 */ 416 public static String field_name(VarInfo vi) { 417 418 if (vi.relative_name != null) { 419 return vi.relative_name; 420 } 421 422 String field_name = vi.name(); 423 int pt = field_name.lastIndexOf('.'); 424 if (pt == -1) { 425 return field_name; 426 } else { 427 return field_name.substring(pt + 1); 428 } 429 } 430 431 /** 432 * Returns whether or not the method of the specified ppt is static or not. The ppt must be an 433 * exit ppt. Exit ppts that do not have an object as a parent are inferred to be static. This does 434 * not work for enter ppts, because constructors do not have the object as a parent on entry. 435 */ 436 @Pure 437 public static boolean is_static_method(PptTopLevel ppt) { 438 439 assert ppt.is_exit() : ppt; 440 for (PptRelation rel : ppt.parents) { 441 if (rel.parent.is_object()) { 442 return false; 443 } 444 } 445 446 return true; 447 } 448}