001package daikon; 002 003import static java.util.logging.Level.FINE; 004import static java.util.logging.Level.INFO; 005 006import daikon.split.PptSplitter; 007import daikon.suppress.NIS; 008import gnu.getopt.Getopt; 009import gnu.getopt.LongOpt; 010import java.io.File; 011import java.io.FileNotFoundException; 012import java.io.IOException; 013import java.io.OptionalDataException; 014import java.io.StreamCorruptedException; 015import java.util.ArrayList; 016import java.util.List; 017import java.util.Set; 018import java.util.TreeSet; 019import java.util.concurrent.TimeUnit; 020import java.util.logging.Logger; 021import org.checkerframework.checker.nullness.qual.Nullable; 022import org.plumelib.util.FilesPlume; 023import org.plumelib.util.StringsPlume; 024 025/** 026 * Merges invariants from multiple invariant files into a single invariant file. It does this by 027 * forming a hierarchy over the ppts from each invariant file and using the normal hierarchy merging 028 * code to merge the invariants. 029 * 030 * <p>The ppts from each invariant file are merged to create a single ppt map that contains the ppts 031 * from all of the files. At each leaf of the merged map, a hierarchy is formed to the ppts from 032 * each of the input files. 033 */ 034public final class MergeInvariants { 035 private MergeInvariants() { 036 throw new Error("do not instantiate"); 037 } 038 039 /** Debug logger. */ 040 public static final Logger debug = Logger.getLogger("daikon.MergeInvariants"); 041 042 /** Progress logger. */ 043 public static final Logger debugProgress = Logger.getLogger("daikon.MergeInvariants.progress"); 044 045 /** The file in which to produce output; if null, the results are printed. */ 046 public static @Nullable File output_inv_file; 047 048 /** The usage message for this program. */ 049 private static String usage = 050 StringsPlume.joinLines( 051 "Usage: java daikon.MergeInvariants [OPTION]... FILE", 052 " -h, --" + Daikon.help_SWITCH, 053 " Display this usage message", 054 " --" + Daikon.config_option_SWITCH, 055 " Specify a configuration option ", 056 " --" + Daikon.debug_SWITCH, 057 " Specify a logger to enable", 058 " --" + Daikon.track_SWITCH, 059 " Specify a class, varinfos, and ppt to debug track. Format" 060 + "is class<var1,var2,var3>@ppt", 061 " -o ", 062 " Specify an output inv file. If not specified, the results are printed"); 063 064 public static void main(final String[] args) 065 throws FileNotFoundException, 066 StreamCorruptedException, 067 OptionalDataException, 068 IOException, 069 ClassNotFoundException { 070 try { 071 mainHelper(args); 072 } catch (Daikon.DaikonTerminationException e) { 073 Daikon.handleDaikonTerminationException(e); 074 } 075 } 076 077 /** 078 * This does the work of {@link #main(String[])}, but it never calls System.exit, so it is 079 * appropriate to be called progrmmatically. 080 * 081 * @param args the command-line arguments 082 * @throws FileNotFoundException if a file cannot be found 083 * @throws StreamCorruptedException if a stream is corrupted 084 * @throws OptionalDataException if there is a serialization problem 085 * @throws IOException if there is trouble with I/O 086 * @throws ClassNotFoundException if a class cannot be found 087 */ 088 @SuppressWarnings("nullness:contracts.precondition") // private field 089 public static void mainHelper(String[] args) 090 throws FileNotFoundException, 091 StreamCorruptedException, 092 OptionalDataException, 093 IOException, 094 ClassNotFoundException { 095 096 daikon.LogHelper.setupLogs(INFO); 097 098 LongOpt[] longopts = 099 new LongOpt[] { 100 new LongOpt(Daikon.help_SWITCH, LongOpt.NO_ARGUMENT, null, 0), 101 new LongOpt(Daikon.config_option_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0), 102 new LongOpt(Daikon.debugAll_SWITCH, LongOpt.NO_ARGUMENT, null, 0), 103 new LongOpt(Daikon.debug_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0), 104 new LongOpt(Daikon.track_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0), 105 }; 106 107 Getopt g = new Getopt("daikon.MergeInvariants", args, "ho:", longopts); 108 int c; 109 while ((c = g.getopt()) != -1) { 110 switch (c) { 111 112 // long option 113 case 0: 114 String option_name = longopts[g.getLongind()].getName(); 115 if (Daikon.help_SWITCH.equals(option_name)) { 116 System.out.println(usage); 117 throw new Daikon.NormalTermination(); 118 } else if (Daikon.config_option_SWITCH.equals(option_name)) { 119 String item = Daikon.getOptarg(g); 120 daikon.config.Configuration.getInstance().apply(item); 121 break; 122 123 } else if (Daikon.debugAll_SWITCH.equals(option_name)) { 124 Global.debugAll = true; 125 126 } else if (Daikon.debug_SWITCH.equals(option_name)) { 127 LogHelper.setLevel(Daikon.getOptarg(g), FINE); 128 } else if (Daikon.track_SWITCH.equals(option_name)) { 129 LogHelper.setLevel("daikon.Debug", FINE); 130 String error = Debug.add_track(Daikon.getOptarg(g)); 131 if (error != null) { 132 throw new Daikon.UserError( 133 "Error parsing track argument '" + Daikon.getOptarg(g) + "' - " + error); 134 } 135 } else { 136 throw new Daikon.UserError("Unknown long option received: " + option_name); 137 } 138 break; 139 140 case 'h': 141 System.out.println(usage); 142 throw new Daikon.NormalTermination(); 143 144 case 'o': 145 String output_inv_filename = Daikon.getOptarg(g); 146 147 if (output_inv_file != null) { 148 throw new Daikon.UserError( 149 "multiple serialization output files supplied on command line: " 150 + output_inv_file 151 + " " 152 + output_inv_filename); 153 } 154 155 output_inv_file = new File(output_inv_filename); 156 157 if (!FilesPlume.canCreateAndWrite(output_inv_file)) { 158 throw new Daikon.UserError( 159 "Cannot write to serialization output file " + output_inv_file); 160 } 161 break; 162 163 case '?': 164 break; // getopt() already printed an error 165 166 default: 167 System.out.println("getopt() returned " + c); 168 break; 169 } 170 } 171 172 daikon.LogHelper.setupLogs(Global.debugAll ? FINE : INFO); 173 174 List<File> inv_files = new ArrayList<>(); 175 File decl_file = null; 176 Set<File> splitter_files = new TreeSet<>(); 177 178 // Get each file specified 179 for (int i = g.getOptind(); i < args.length; i++) { 180 File file = new File(args[i]); 181 if (!file.exists()) { 182 throw new Daikon.UserError("File " + file + " not found."); 183 } 184 if (file.toString().indexOf(".inv") != -1) { 185 inv_files.add(file); 186 } else if (file.toString().indexOf(".decls") != -1) { 187 if (decl_file != null) { 188 throw new Daikon.UserError("Only one decl file may be specified"); 189 } 190 decl_file = file; 191 } else if (file.toString().indexOf(".spinfo") != -1) { 192 splitter_files.add(file); 193 } else { 194 throw new Daikon.UserError("Unrecognized file type: " + file); 195 } 196 } 197 198 // Make sure at least two files were specified 199 if (inv_files.size() < 2) { 200 throw new Daikon.UserError( 201 "Provided " 202 + StringsPlume.nplural(inv_files.size(), "inv file") 203 + " but needs at least two"); 204 } 205 206 // Setup the default for guarding 207 PrintInvariants.validateGuardNulls(); 208 209 // Initialize the prototype invariants and NI suppressions 210 Daikon.setup_proto_invs(); 211 NIS.init_ni_suppression(); 212 213 // Read in each of the specified maps 214 List<PptMap> pptmaps = new ArrayList<>(); 215 for (File file : inv_files) { 216 debugProgress.fine("Processing " + file); 217 PptMap ppts = FileIO.read_serialized_pptmap(file, true); 218 ppts.repCheck(); 219 pptmaps.add(ppts); 220 Debug.check(ppts, "After initial reading of " + file); 221 } 222 223 // Merged ppt map (result of merging each specified inv file) 224 PptMap merge_ppts = null; 225 226 // if no decls file was specified 227 if (decl_file == null) { 228 if (splitter_files.size() > 0) { 229 throw new Daikon.UserError(".spinfo files may only be specified along with a .decls file"); 230 } 231 232 // Read in each of the maps again to build a template that contains all 233 // of the program points from each map. 234 for (File file : inv_files) { 235 debugProgress.fine("Reading " + file + " as merge template"); 236 if (merge_ppts == null) { 237 merge_ppts = FileIO.read_serialized_pptmap(file, true); 238 } else { 239 PptMap pmap = FileIO.read_serialized_pptmap(file, true); 240 for (PptTopLevel ppt : pmap.pptIterable()) { 241 if (merge_ppts.containsName(ppt.name())) { 242 // System.out.printf("Not adding ppt %s from %s%n", ppt, file); 243 continue; 244 } 245 merge_ppts.add(ppt); 246 // System.out.printf("Adding ppt %s from %s%n", ppt, file); 247 248 // Make sure that the parents of this ppt are already in 249 // the map. This will be true if all possible children of 250 // any ppt are always included in the same invariant file. 251 // For example, all possible enter/exit points should be 252 // included with each object point. This is true for Chicory 253 // as long as ppt filtering didn't remove some ppts. 254 for (PptRelation rel : ppt.parents) { 255 assert merge_ppts.get(rel.parent.name()) == rel.parent : ppt + " - " + rel; 256 } 257 } 258 } 259 } 260 assert merge_ppts != null 261 : "@AssumeAssertion(nullness): inv_files is non-empty, so for-loop body executed"; 262 263 // Remove all of the slices, equality sets, to start 264 debugProgress.fine("Cleaning ppt map in preparation for merge"); 265 for (PptTopLevel ppt : merge_ppts.ppt_all_iterable()) { 266 ppt.clean_for_merge(); 267 } 268 269 } else { 270 271 // Build the result pptmap from the specific decls file 272 debugProgress.fine("Building result ppt map from decls file"); 273 Daikon.create_splitters(splitter_files); 274 List<File> decl_files = new ArrayList<>(); 275 decl_files.add(decl_file); 276 merge_ppts = FileIO.read_declaration_files(decl_files); 277 merge_ppts.trimToSize(); 278 PptRelation.init_hierarchy(merge_ppts); 279 } 280 281 // Create a hierarchy between the merge exitNN points and the 282 // corresponding points in each of the specified maps. This 283 // should only be created at the exitNN points (i.e., the leaves) 284 // so that the normal processing will create the invariants at 285 // upper points. 286 debugProgress.fine("Building hierarchy between leaves of the maps"); 287 for (PptTopLevel ppt : merge_ppts.pptIterable()) { 288 289 // Skip everything that is not a final exit point 290 if (!ppt.ppt_name.isExitPoint()) { 291 assert ppt.children.size() > 0 : ppt; 292 continue; 293 } 294 if (ppt.ppt_name.isCombinedExitPoint()) { 295 assert ppt.children.size() > 0 : ppt; 296 continue; 297 } 298 299 // System.out.printf("Including ppt %s, %d children%n", ppt, 300 // ppt.children.size()); 301 302 // Splitters should not have any children to begin with 303 if (ppt.has_splitters()) { 304 assert ppt.splitters != null; // because ppt.has_splitters() = true 305 for (PptSplitter ppt_split : ppt.splitters) { 306 for (PptTopLevel p : ppt_split.ppts) { 307 assert p.children.size() == 0 : p; 308 } 309 } 310 } 311 312 // Loop over each of the input ppt maps, looking for the same ppt 313 for (int j = 0; j < pptmaps.size(); j++) { 314 PptMap pmap = pptmaps.get(j); 315 PptTopLevel child = pmap.get(ppt.name()); 316 // System.out.printf("found child %s from pmap %d%n", child, j); 317 if (child == null) { 318 continue; 319 } 320 if (child.equality_view == null) { 321 System.out.println( 322 "equality_view == null in child ppt: " 323 + child.name() 324 + " (" 325 + inv_files.get(j) 326 + ")"); 327 } else if (child.equality_view.invs == null) { 328 System.out.println( 329 "equality_view.invs == null in child ppt: " 330 + child.name() 331 + " (" 332 + inv_files.get(j) 333 + ")" 334 + " samples = " 335 + child.num_samples()); 336 } 337 338 // Remove the equality invariants added during equality post 339 // processing. These are not over leaders and will cause problems 340 // in the merge 341 child.remove_equality_invariants(); 342 child.in_merge = false; 343 344 // Remove implications, they don't merge correctly 345 child.remove_implications(); 346 347 // If the ppt has splitters, attach the child's splitters to the 348 // splitters. Don't attach the ppt itself, as its invariants can 349 // be built from the invariants in the splitters. 350 if (ppt.has_splitters()) { 351 assert ppt.splitters != null; // because ppt.has_splitters() = true 352 setup_conditional_merge(ppt, child); 353 } else { 354 PptRelation.newMergeChildRel(ppt, child); 355 } 356 } 357 358 // Make sure at least one child was found 359 assert ppt.children.size() > 0 : ppt; 360 if (ppt.has_splitters()) { 361 assert ppt.splitters != null; // because ppt.has_splitters() = true 362 for (PptSplitter ppt_split : ppt.splitters) { 363 for (PptTopLevel p : ppt_split.ppts) { 364 assert p.children.size() > 0 : p; 365 } 366 } 367 } 368 } 369 370 // Check the resulting PptMap for consistency 371 merge_ppts.repCheck(); 372 373 // Debug print the hierarchy is a more readable manner 374 if (debug.isLoggable(FINE)) { 375 debug.fine("PPT Hierarchy"); 376 for (PptTopLevel ppt : merge_ppts.pptIterable()) { 377 if (ppt.parents.size() == 0) { 378 ppt.debug_print_tree(debug, 0, null); 379 } 380 } 381 } 382 383 // Merge the invariants 384 debugProgress.fine("Merging invariants"); 385 Daikon.createUpperPpts(merge_ppts); 386 387 // Equality post processing 388 debugProgress.fine("Equality Post Processing"); 389 for (PptTopLevel ppt : merge_ppts.ppt_all_iterable()) { 390 ppt.postProcessEquality(); 391 } 392 393 // Implications 394 long startTime = System.nanoTime(); 395 // System.out.println("Creating implications "); 396 debugProgress.fine("Adding Implications ... "); 397 for (PptTopLevel ppt : merge_ppts.pptIterable()) { 398 if (ppt.num_samples() > 0) { 399 ppt.addImplications(); 400 } 401 } 402 long duration = System.nanoTime() - startTime; 403 debugProgress.fine("Time spent in implications: " + TimeUnit.NANOSECONDS.toSeconds(duration)); 404 405 // Remove the PptRelation links so that when the file is written 406 // out it only includes the new information 407 for (PptTopLevel ppt : merge_ppts.pptIterable()) { 408 if (!ppt.ppt_name.isExitPoint()) { 409 continue; 410 } 411 if (ppt.ppt_name.isCombinedExitPoint()) { 412 continue; 413 } 414 ppt.children.clear(); 415 for (PptConditional cond : ppt.cond_iterable()) { 416 cond.children.clear(); 417 } 418 } 419 420 // Write serialized output 421 debugProgress.fine("Writing Output"); 422 if (output_inv_file != null) { 423 try { 424 FileIO.write_serialized_pptmap(merge_ppts, output_inv_file); 425 } catch (IOException e) { 426 throw new RuntimeException( 427 "Error while writing .inv file '" + output_inv_file + "': " + e.toString()); 428 } 429 } else { 430 // Print the invariants 431 PrintInvariants.print_invariants(merge_ppts); 432 } 433 } 434 435 /** 436 * Ses up the specified relation beteween each of the conditionals in ppt and the matching 437 * conditionals in child. Each must have the same number of splitters setup in the same order. The 438 * splitter match can't be checked because splitters can't be read back in. 439 */ 440 private static void setup_conditional_merge(PptTopLevel ppt, PptTopLevel child) { 441 442 // Both ppt and child should have splitters 443 if (ppt.has_splitters() != child.has_splitters()) { 444 throw new Error( 445 "Merge ppt " 446 + ppt.name 447 + (ppt.has_splitters() ? " has " : "doesn't have ") 448 + "splitters, but child ppt " 449 + child.name 450 + (child.has_splitters() ? " does" : " doesn't")); 451 } 452 453 // Nothing to do if there are no splitters here 454 if (!ppt.has_splitters()) { 455 return; 456 } 457 458 assert child.splitters != null 459 : "@AssumeAssertion(nullness): correlated: ppt.has_splitters() == child.has_splitters()," 460 + " and ppt.has_splitters() == true"; 461 462 // Both ppt and child should have the same number of splitters 463 if (ppt.splitters.size() != child.splitters.size()) { 464 throw new Error( 465 "Merge ppt " 466 + ppt.name 467 + " has " 468 + ((ppt.splitters.size() > child.splitters.size()) ? "more" : "fewer") 469 + " splitters (" 470 + ppt.splitters.size() 471 + ") than child ppt " 472 + child.name 473 + " (" 474 + child.splitters.size() 475 + ")"); 476 } 477 478 // Create a relation from each conditional ppt to its corresponding 479 // conditional ppt in the child. 480 for (int ii = 0; ii < ppt.splitters.size(); ii++) { 481 PptSplitter ppt_split = ppt.splitters.get(ii); 482 PptSplitter child_split = child.splitters.get(ii); 483 for (int jj = 0; jj < ppt_split.ppts.length; jj++) { 484 child_split.ppts[jj].remove_equality_invariants(); 485 child_split.ppts[jj].in_merge = false; 486 PptRelation.newMergeChildRel(ppt_split.ppts[jj], child_split.ppts[jj]); 487 } 488 } 489 } 490}