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