001package daikon.suppress; 002 003import static daikon.inv.Invariant.asInvClass; 004 005import daikon.Debug; 006import daikon.PptSlice; 007import daikon.PptTopLevel; 008import daikon.ValueTuple; 009import daikon.VarInfo; 010import daikon.inv.Invariant; 011import daikon.inv.InvariantStatus; 012import daikon.inv.binary.BinaryInvariant; 013import daikon.inv.unary.UnaryInvariant; 014import java.lang.reflect.Method; 015import java.util.Arrays; 016import java.util.logging.Level; 017import java.util.logging.Logger; 018import org.checkerframework.checker.lock.qual.GuardSatisfied; 019import org.checkerframework.checker.nullness.qual.NonNull; 020import org.checkerframework.checker.nullness.qual.Nullable; 021import org.checkerframework.dataflow.qual.Pure; 022import org.checkerframework.dataflow.qual.SideEffectFree; 023import typequals.prototype.qual.Prototype; 024 025/** 026 * Class that defines a suppressor invariant for use in non-instantiating suppressions. In 027 * non-instantiating suppressions, suppressor invariants are defined independent of specific 028 * variables. Instead, arguments are identified by their variable index in the suppressee. 029 */ 030public class NISuppressor { 031 032 /** Debug tracer. */ 033 public static final Logger debug = Logger.getLogger("daikon.inv.NISuppressor"); 034 035 /** Argument indices used by the invariant. */ 036 int v1_index = -1; 037 038 int v2_index = -1; 039 int v3_index = -1; 040 041 /** Invariant class. */ 042 Class<? extends Invariant> inv_class; 043 044 /** True if the order of the variables was swapped. */ 045 boolean swap = false; 046 047 /** True if invariant permutes by changing its class. */ 048 boolean swap_class = false; 049 050 /** 051 * State of the suppressor for the current check. The state must be one of the defined above. They 052 * can always be compared with ==. 053 */ 054 NIS.SuppressState state = NIS.SuppressState.NONE; 055 056 /** 057 * information about the suppressor for the current check. This is just used for debugging 058 * purposes. 059 */ 060 @Nullable String current_state_str = null; 061 062 /** 063 * Sample invariant - used to check the suppressor over constants. this is a prototype invariant; 064 * that is, sample_inv.ppt == null. 065 */ 066 @Prototype Invariant sample_inv; 067 068 /** Defines a unary suppressor. */ 069 public NISuppressor(int v1_index, Class<? extends Invariant> cls) { 070 071 debug.fine(String.format("creating %s over arg %d", cls.getName(), v1_index)); 072 073 this.v1_index = v1_index; 074 this.inv_class = cls; 075 076 // Create a sample invariant 077 try { 078 Method get_proto = inv_class.getMethod("get_proto", new Class<?>[] {}); 079 @SuppressWarnings({"nullness", "prototype"}) // reflective invocation is nullness-correct 080 @NonNull @Prototype 081 Invariant sample_inv_local = (@Prototype Invariant) get_proto.invoke(null, new Object[] {}); 082 sample_inv = sample_inv_local; 083 assert sample_inv != null; 084 } catch (Exception e) { 085 throw new RuntimeException("error instantiating invariant " + inv_class.getName() + ": " + e); 086 } 087 088 debug.fine("Created " + this); 089 } 090 091 /** Defines a binary suppressor. */ 092 public NISuppressor(int v1_index, int v2_index, Class<? extends Invariant> cls) { 093 094 debug.fine(String.format("creating %s over args %d and %d", cls.getName(), v1_index, v2_index)); 095 096 // put the variables in their standard order 097 if (v1_index > v2_index) { 098 this.v1_index = v2_index; 099 this.v2_index = v1_index; 100 swap = true; 101 } else { 102 this.v1_index = v1_index; 103 this.v2_index = v2_index; 104 swap = false; 105 } 106 107 // If the specified class handles swapping with a different class, 108 // get the class 109 swap_class = true; 110 try { 111 Method swap_method = cls.getMethod("swap_class", (Class<?>[]) null); 112 if (swap) { 113 @SuppressWarnings("nullness") // static method, so null first arg is OK: swap_class() 114 Class<? extends Invariant> tmp_cls = 115 asInvClass(swap_method.invoke(null, (Object @Nullable []) null)); 116 cls = tmp_cls; 117 } 118 119 } catch (Exception e) { 120 swap_class = false; 121 } 122 123 this.inv_class = cls; 124 125 // Create a sample invariant, by reflectively calling either 126 // get_proto(boolean) or get_proto(). 127 try { 128 try { 129 Method get_proto = inv_class.getMethod("get_proto", new Class<?>[] {boolean.class}); 130 @SuppressWarnings({"nullness", "prototype"}) // reflective invocation is nullness-correct 131 @NonNull @Prototype 132 Invariant sample_inv_local = 133 (@Prototype Invariant) get_proto.invoke(null, new Object[] {Boolean.valueOf(swap)}); 134 sample_inv = sample_inv_local; 135 } catch (NoSuchMethodException e) { 136 Method get_proto = inv_class.getMethod("get_proto", new Class<?>[] {}); 137 @SuppressWarnings({"nullness", "prototype"}) // reflective invocation is nullness-correct 138 @NonNull @Prototype 139 Invariant sample_inv_local = (@Prototype Invariant) get_proto.invoke(null, new Object[] {}); 140 sample_inv = sample_inv_local; 141 } 142 } catch (Exception e) { 143 throw new RuntimeException("error getting proto invariant " + inv_class.getName() + ": " + e); 144 } 145 146 assert sample_inv != null; 147 debug.fine("Created " + this); 148 } 149 150 /** 151 * Returns a new suppressor that is the same as this one except with its variables swapped. Unary 152 * suppressors have their variable index swapped from 0 to 1 or 1 to 0. 153 */ 154 public NISuppressor swap() { 155 156 if (v2_index == -1) { 157 int new_index = 0; 158 if (v1_index == 0) { 159 new_index = 1; 160 } 161 return new NISuppressor(new_index, inv_class); 162 } 163 assert v3_index == -1; 164 165 if (swap) { 166 return new NISuppressor(v1_index, v2_index, inv_class); 167 } else { 168 return new NISuppressor(v2_index, v1_index, inv_class); 169 } 170 } 171 172 /** 173 * Returns whether or not this suppressor is enabled. A suppressor is enabled if the invariant on 174 * which it depends is enabled. 175 */ 176 @Pure 177 public boolean is_enabled() { 178 return sample_inv.enabled(); 179 } 180 181 /** 182 * Returns whether or not this suppressor invariant could be instantiated over the specified 183 * variables. A suppressor that canot be instantiated over the variables cannot possibly suppress. 184 * Consider the NonZero invariant. It is suppressed by EqualsOne. But while NonZero is valid over 185 * all variables, EqualsOne is only valid over non-pointer variables. Thus the suppression is only 186 * valid over non-pointer variables. 187 */ 188 public boolean instantiate_ok(VarInfo[] vis) { 189 return sample_inv.instantiate_ok(vis); 190 } 191 192 /** 193 * Sets the status of this suppressor with regards to the specified vis and falsified invariant. 194 * The status consists of whether or not the suppressor is valid (true) and whether or not it 195 * matches the falsified invariant. 196 * 197 * <p>Matching a suppressor is more complex than is apparent at first glance. The invariant AND 198 * its variables must match. Since suppressors are specified without variables, the variables are 199 * taken from the specified vis. The variable indices specify which variables to consider. 200 * 201 * <p>For example consider the suppressor {1, 2, IntLessEqual} and a vis of {x, y, z}. The 202 * suppressor is true if the IntLessEqual invariant exists in the slice {y, z}. This allows 203 * ternary invariants to specify exactly the suppressor required for their particular permutation 204 * ofarguments. Invariants that have an internal permute variable must match that as well. 205 * 206 * @param ppt the top level program point 207 * @param vis the slice of the suppressee. Thus, if the suppressee is ternary, vis, should specify 208 * three variables. 209 * @param inv the falsified invariant. inv_match indicates whether or not inv matches this 210 * suppressor. 211 * @return the state of this suppressor which is one of (NIS.SuppressState.MATCH, 212 * NIS.SuppressState.VALID, NIS.SuppressState.INVALID, NIS.SuppressState.NONSENSICAL) 213 */ 214 public NIS.SuppressState check(PptTopLevel ppt, VarInfo[] vis, @Nullable Invariant inv) { 215 216 // Currently we only support unary and binary suppressors 217 assert v3_index == -1; 218 assert v1_index != -1; 219 220 // If the underlying invariant is not enabled, we can't possibly be true 221 if (!is_enabled()) { 222 return (state = NIS.SuppressState.INVALID); 223 } 224 225 if (Debug.logDetail() && NIS.debug.isLoggable(Level.FINE)) { 226 NIS.debug.fine( 227 "checking suppressor " 228 + this 229 + " against inv " 230 + ((inv != null) ? inv.format() : "null") 231 + " over vars " 232 + Arrays.toString(vis) 233 + " in ppt " 234 + ppt.name); 235 } 236 237 // If unary 238 if (v2_index == -1) { 239 240 VarInfo v1 = vis[v1_index]; 241 242 // If the underlying inariant can't be instantiated over these variables, 243 // this can't possibly be true 244 if (!instantiate_ok(new VarInfo[] {v1})) { 245 // System.out.printf("suppressor %s invalid over variable %s%n", 246 // this, v1); 247 return (state = NIS.SuppressState.INVALID); 248 } 249 250 // Check to see if inv matches this suppressor. The invariant class 251 // and variables must match for this to be true. This check is only 252 // needed for the falsified method. 253 if (!NIS.antecedent_method) { 254 if ((inv != null) && (inv.getClass() == inv_class) && (v1 == inv.ppt.var_infos[0])) { 255 return (state = NIS.SuppressState.MATCH); 256 } 257 } 258 259 // Check to see if the suppressor is true over all constants. 260 if (ppt.is_prev_constant(v1)) { 261 assert ppt.constants != null : "@AssumeAssertion(nullness)"; 262 boolean valid = false; 263 VarInfo[] sup_vis = new VarInfo[] {v1}; 264 assert sample_inv.valid_types(sup_vis); 265 if (sample_inv.instantiate_ok(sup_vis)) { 266 UnaryInvariant uinv = (UnaryInvariant) sample_inv; 267 InvariantStatus status = 268 uinv.check(ppt.constants.constant_value(v1), ValueTuple.MODIFIED, 1); 269 valid = (status == InvariantStatus.NO_CHANGE); 270 } 271 if (NIS.debug.isLoggable(Level.FINE)) { 272 NIS.debug.fine("constant args - " + valid); 273 } 274 if (valid) { 275 current_state_str = "true over constant " + ppt.constants.constant_value(v1); 276 } else { 277 current_state_str = "invalid over constant " + ppt.constants.constant_value(v1); 278 } 279 return (state = (valid ? NIS.SuppressState.VALID : NIS.SuppressState.INVALID)); 280 } 281 282 // Check to see the variable is missing 283 if (ppt.is_prev_missing(v1)) { 284 current_state_str = "nonsensical"; 285 return (state = NIS.SuppressState.NONSENSICAL); 286 } 287 288 // Check to see if this suppressor is true. Note that we don't check 289 // to see if the invariant has been falsified. That is because we 290 // do this processing as falsified invariants are removed from the lists. 291 // An invariant that is still in the list, but marked falsified, is true 292 // for our purposes (we will process it later, when it is removed) 293 PptSlice slice = ppt.findSlice(v1); 294 if (slice != null) { 295 for (Invariant slice_inv : slice.invs) { 296 if (match_true(slice_inv)) { 297 current_state_str = "invariant " + slice_inv.format(); 298 return (state = NIS.SuppressState.VALID); 299 } 300 } 301 } 302 current_state_str = "invariant not found"; 303 return (state = NIS.SuppressState.INVALID); 304 305 } else /* must be binary */ { 306 if (v1_index >= vis.length || v2_index >= vis.length) { 307 // Stringifying "this" is expensive, so only do it if one of the 308 // assertions will fail 309 assert (v1_index < vis.length) 310 : "v1/len= " + v1_index + "/" + vis.length + " suppressor " + this; 311 assert (v2_index < vis.length) 312 : "v2/len= " + v2_index + "/" + vis.length + " suppressor " + this; 313 } 314 VarInfo v1 = vis[v1_index]; 315 VarInfo v2 = vis[v2_index]; 316 317 // If the underlying inariant can't be instantiated over these variables, 318 // this can't possibly be true 319 if (!instantiate_ok(new VarInfo[] {v1, v2})) { 320 // System.out.printf("suppressor %s invalid over variables %s & %s%n", 321 // this, v1, v2); 322 return (state = NIS.SuppressState.INVALID); 323 } 324 325 // Check to see if inv matches this suppressor. The invariant class, 326 // variables, and swap must match for this to be true. This check is 327 // only needed in the falsified method. 328 if (!NIS.antecedent_method) { 329 if ((inv != null) 330 && match(inv) 331 && (v1 == inv.ppt.var_infos[0]) 332 && (v2 == inv.ppt.var_infos[1])) { 333 if (NIS.debug.isLoggable(Level.FINE)) { 334 NIS.debug.fine("Matches falsified inv " + inv.format()); 335 } 336 return (state = NIS.SuppressState.MATCH); 337 } 338 } 339 340 // Check to see if the suppressor is true over all constants. This 341 // code only works for stateless invariants! 342 if (ppt.is_prev_constant(v1) && ppt.is_prev_constant(v2)) { 343 assert ppt.constants != null : "@AssumeAssertion(nullness)"; 344 boolean valid = false; 345 VarInfo[] sup_vis = new VarInfo[] {v1, v2}; 346 assert sample_inv.valid_types(sup_vis); 347 if (sample_inv.instantiate_ok(sup_vis)) { 348 BinaryInvariant binv = (BinaryInvariant) sample_inv; 349 InvariantStatus status = 350 binv.check_unordered( 351 ppt.constants.constant_value(v1), 352 ppt.constants.constant_value(v2), 353 ValueTuple.MODIFIED, 354 1); 355 valid = (status == InvariantStatus.NO_CHANGE); 356 } 357 if (NIS.debug.isLoggable(Level.FINE)) { 358 NIS.debug.fine( 359 String.format( 360 "constant args (%s, %s) = %b ", 361 Debug.toString(ppt.constants.constant_value(v1)), 362 Debug.toString(ppt.constants.constant_value(v2)), 363 valid)); 364 } 365 Object const1 = ppt.constants.constant_value(v1); 366 Object const2 = ppt.constants.constant_value(v2); 367 current_state_str = 368 "true over constants " + Debug.toString(const1) + " and " + Debug.toString(const2); 369 if (!valid) { 370 current_state_str = "not " + current_state_str; 371 } 372 return (state = (valid ? NIS.SuppressState.VALID : NIS.SuppressState.INVALID)); 373 } 374 375 // Check to see if either variable is missing 376 if (ppt.is_prev_missing(v1) || ppt.is_prev_missing(v2)) { 377 current_state_str = "nonsensical"; 378 return (state = NIS.SuppressState.NONSENSICAL); 379 } 380 381 // Check to see if this suppressor is true. Note that we don't check 382 // to see if the invariant has been falsified. That is because we 383 // do this processing as falsified invariants are removed from the lists. 384 // An invariant that is still in the list, but marked falsified, is true 385 // for our purposes (we will process it later, when it is removed) 386 PptSlice slice = ppt.findSlice(v1, v2); 387 if (slice != null) { 388 for (Invariant slice_inv : slice.invs) { 389 // NIS.debug.fine (": processing inv " + slice_inv.format()); 390 if (match_true(slice_inv)) { 391 if (NIS.debug.isLoggable(Level.FINE)) { 392 NIS.debug.fine( 393 "suppressor matches inv " + slice_inv.format() + " " + !slice_inv.is_false()); 394 } 395 current_state_str = "invariant " + slice_inv.format(); 396 return (state = NIS.SuppressState.VALID); 397 } 398 } 399 } 400 NIS.debug.fine("suppressor not found"); 401 return (state = NIS.SuppressState.INVALID); 402 } 403 } 404 405 /** 406 * Returns true if inv matches this suppressor and the invariant is not falsified. 407 * 408 * @see #match(Invariant) 409 */ 410 public boolean match_true(Invariant inv) { 411 if (NIS.antecedent_method) { 412 return match(inv) && !inv.is_false(); 413 } else { 414 return match(inv); 415 } 416 } 417 418 /** 419 * Returns true if inv matches this suppressor. It is assumed that inv's variables already match 420 * (i.e., that it was looked up in compatible slice). 421 */ 422 public boolean match(Invariant inv) { 423 424 if (v2_index == -1) { 425 return (inv.getClass() == inv_class); 426 } else { 427 if (inv.getClass() != inv_class) { 428 return false; 429 } 430 if (!swap_class) { 431 BinaryInvariant binv = (BinaryInvariant) inv; 432 return binv.is_symmetric() || (swap == binv.get_swap()); 433 } 434 return true; 435 } 436 } 437 438 /** 439 * Returns true if the suppressee matches this suppressor. Currently only checks that the class 440 * matches but this will need to be expanded to check for a permutation match as well. 441 */ 442 public boolean match(NISuppressee sse) { 443 444 if (v2_index == -1) { 445 return (sse.sup_class == inv_class); 446 } else { 447 if (sse.sup_class != inv_class) { 448 return false; 449 } 450 if (!swap_class) { 451 BinaryInvariant binv = (BinaryInvariant) sse.sample_inv; 452 boolean match = (binv.is_symmetric() || (swap == binv.get_swap())); 453 return match; 454 } 455 return true; 456 } 457 } 458 459 /** Returns a copy of this suppressor translated to match the variable order in sor. */ 460 public NISuppressor translate(NISuppressor sor) { 461 462 int new_v1 = sor.translate_index(v1_index); 463 int new_v2 = sor.translate_index(v2_index); 464 int new_v3 = sor.translate_index(v3_index); 465 466 if (new_v2 == -1) { 467 return new NISuppressor(new_v1, inv_class); 468 } else if (new_v3 == -1) { 469 return new NISuppressor(new_v1, new_v2, inv_class); 470 } else { 471 throw new Error("Unexpected ternary suppressor"); 472 } 473 } 474 475 /** Returns the variable index that corresponds to index. */ 476 private int translate_index(int index) { 477 478 if (index == 0) { 479 return v1_index; 480 } else if (index == 1) { 481 return v2_index; 482 } else if (index == 2) { 483 return v3_index; 484 } else { 485 return index; 486 } 487 } 488 489 /** Returns the invariant class of this suppressor. */ 490 public Class<? extends Invariant> get_inv_class() { 491 return inv_class; 492 } 493 494 /** clears the state of this suppressor to NIS.none */ 495 public void clear_state() { 496 state = NIS.SuppressState.NONE; 497 current_state_str = null; 498 } 499 500 static String[] varname = new String[] {"x", "y", "z"}; 501 502 /** 503 * Returns a string representation of the suppressor. Rather than show var indices as numbers, the 504 * variables x, y, and z are shown instead with indices 0, 1, and 2 respectively. 505 */ 506 @SideEffectFree 507 @Override 508 public String toString(@GuardSatisfied NISuppressor this) { 509 510 String cname = inv_class.getCanonicalName(); 511 512 String status; 513 if (state == NIS.SuppressState.NONE) { 514 status = ""; 515 } else { 516 status = state.toString().toLowerCase(); 517 } 518 519 if (current_state_str != null) { 520 status = status + " [" + current_state_str + "]"; 521 } 522 523 if (v2_index == -1) { 524 return String.format("%s(%s) [%s]", cname, varname[v1_index], status); 525 } else if (v3_index == -1) { 526 if (swap && !swap_class) { 527 return String.format("%s(%s,%s) [%s]", cname, varname[v2_index], varname[v1_index], status); 528 } else { 529 return String.format("%s(%s,%s) [%s]", cname, varname[v1_index], varname[v2_index], status); 530 } 531 } else { 532 return String.format( 533 "%s(%s,%s,%s) [%s]", 534 cname, varname[v1_index], varname[v2_index], varname[v3_index], status); 535 } 536 } 537}