001// ***** This file is automatically generated from PptSlice.java.jpp 002 003package daikon; 004 005import static daikon.tools.nullness.NullnessUtil.castNonNullDeep; 006 007import daikon.inv.*; 008 009import daikon.inv.binary.*; 010import daikon.inv.binary.twoScalar.*; 011import daikon.inv.binary.twoSequence.*; 012import daikon.inv.binary.twoString.*; 013import daikon.inv.binary.sequenceScalar.*; 014 015import daikon.suppress.*; 016import java.util.*; 017import java.util.logging.Level; 018import java.util.logging.Logger; 019import org.checkerframework.checker.initialization.qual.UnknownInitialization; 020import org.checkerframework.checker.interning.qual.Interned; 021import org.checkerframework.checker.lock.qual.GuardSatisfied; 022import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 023import org.checkerframework.checker.nullness.qual.NonNull; 024import org.plumelib.util.ArraysPlume; 025import org.plumelib.util.Intern; 026import typequals.prototype.qual.Prototype; 027import typequals.prototype.qual.NonPrototype; 028 029/** Contains all of the invariants over a particular set of 2 variables. */ 030public final class PptSlice2 extends PptSlice { 031 // We are Serializable, so we specify a version to allow changes to 032 // method signatures without breaking serialization. If you add or 033 // remove fields, you should change this number to the current date. 034 static final long serialVersionUID = 20040921L; 035 036 /** Debug tracer. */ 037 public static final Logger debugSpecific = Logger.getLogger("daikon.PptSlice2"); 038 039 public static final Logger debugMerge = Logger.getLogger("daikon.PptSlice.merge"); 040 041 /** Create a new PptSlice2. The var_infos must be in varinfo_index order. */ 042 public PptSlice2(PptTopLevel parent, VarInfo[] var_infos) { 043 044 super(parent, var_infos); 045 assert var_infos.length == 2; 046 047 if (debug.isLoggable(Level.FINE) || debugSpecific.isLoggable(Level.FINE)) { 048 debug.info("Created PptSlice2 " + this.name()); 049 } 050 if (Debug.logOn()) { 051 Debug.log(getClass(), this, "Created"); 052 } 053 } 054 055 PptSlice2(PptTopLevel parent, VarInfo var_info1, VarInfo var_info2) { 056 this(parent, new VarInfo[] {var_info1, var_info2}); 057 } 058 059 @Override 060 public final int arity( 061 @UnknownInitialization(PptSlice.class) PptSlice2 this) { 062 return 2; 063 } 064 /** 065 * Creates all of the invariants that are appropriate for this slice. No invariants are 066 * created unless the variables in the slice are compatible. If the variables are compatible, 067 * invariants that match the type of the slices variables are created. 068 */ 069 @Override 070 public void instantiate_invariants() { 071 instantiate_invariants(Daikon.proto_invs); 072 } 073 074 /** 075 * Creates all of the invariants that are appropriate for this slice based on the list of 076 * invariants passed in. No invariants are created unless the variables in the slice are 077 * compatible. If the variables are compatible, invariants that match the type of the slices 078 * variables are created. 079 */ 080 public void instantiate_invariants(List<@Prototype Invariant> proto_invs) { 081 082 if (Debug.logOn()) { 083 log("instantiate invariants"); 084 } 085 086 // Do nothing if the variables aren't compatible 087 088 boolean compatible = true; 089 VarInfo v1 = var_infos[0]; 090 VarInfo v2 = var_infos[1]; 091 if (v1.rep_type.isArray() == v2.rep_type.isArray()) { 092 compatible = v1.compatible(v2); 093 } else if (v1.rep_type.isArray()) { 094 compatible = v1.eltsCompatible(v2); 095 } else { 096 compatible = v2.eltsCompatible(v1); 097 } 098 if (!compatible) { 099 Debug.log(this.getClass(), this, "not created - incompatible"); 100 return; 101 } 102 103 // Instantiate each invariant that is valid over those types 104 for (@Prototype Invariant proto : proto_invs) { 105 106 // Skip invariant if the types are not appropriate 107 if (!proto.valid_types(var_infos) || !proto.instantiate_ok(var_infos)) { 108 // Debug.log (proto.getClass(), this, "not created-types not valid"); 109 continue; 110 } 111 112 // Skip invariant if it is suppressed. Note that this will work 113 // even though we haven't instantiated all of the invariants for this 114 // slice yet because it will check constant values. 115 NISuppressionSet ss = proto.get_ni_suppressions(); 116 if (NIS.dkconfig_enabled && (ss != null) && !ss.is_instantiate_ok(this)) { 117 if (Debug.logOn()) // avoid stringify if not logging 118 Debug.log(proto.getClass(), this, "not created - suppressed " + ss); 119 continue; 120 } 121 122 // Instantiate the invariant and add it to the list. Null should 123 // never be returned because we check instantiate_ok() above and 124 // only enabled invariants are in this list. 125 @SuppressWarnings("nullness") // application invariant, see comment above 126 @NonNull Invariant inv = proto.instantiate(this); 127 addInvariant(inv); 128 if (Debug.logOn()) { 129 inv.log("Created invariant %s ss = %s", inv.format(), ss); 130 } 131 } 132 } 133 134 // These accessors are for abstract methods declared in Ppt 135 /** Returns the number of (non-missing) samples observed at this slice. */ 136 @Override 137 public int num_samples(@UnknownInitialization @GuardSatisfied PptSlice2 this) { 138 if (parent == null || var_infos == null) { // handle not-yet-initialized slices 139 return 0; 140 } 141 // return an approximation 142 143 int num_slice_samples = parent.num_samples(var_infos[0], var_infos[1]); 144 145 return num_slice_samples; 146 } 147 148 /** 149 * Returns an upper bound on the number of distinct values observed at this slice. This is not the 150 * number of samples observed. 151 */ 152 @Override 153 public int num_values() { 154 // return an approximation 155 156 int num_values = parent.num_values(var_infos[0], var_infos[1]); 157 158 return num_values; 159 } 160 161 /** 162 * This procedure accepts a sample (a ValueTuple), extracts the values from it, casts them to the 163 * proper types, and passes them along to the invariants proper. (The invariants accept typed 164 * values rather than a ValueTuple that encapsulates objects of any type whatever.) 165 */ 166 @Override 167 public List<Invariant> add(ValueTuple full_vt, int count) { 168 169 assert invs.size() > 0; 170 for (Invariant inv : invs) { 171 assert inv != null; 172 } 173 174 VarInfo vi1 = var_infos[0]; 175 VarInfo vi2 = var_infos[1]; 176 177 // If any var has encountered out of array bounds values, 178 // stop all invariants in this slice. The presumption here is that 179 // an index out of bounds implies that the derived variable (eg a[i]) 180 // doesn't really make any sense (essentially that i is not a valid 181 // index for a). Invariants on the derived variable are thus not 182 // relevant 183 for (int i = 0; i < var_infos.length; i++) { 184 if (var_infos[i].missingOutOfBounds()) { 185 List<Invariant> result = new ArrayList<>(); 186 for (Invariant inv : invs) { 187 if (PrintInvariants.print_discarded_invariants) { 188 DiscReasonMap.put( 189 inv, 190 DiscardCode.bad_sample, 191 var_infos[i].name() + " array index was out of bounds"); 192 } 193 inv.falsify(); 194 result.add(inv); 195 if (Invariant.logOn()) { 196 inv.log("destroyed because %s array index out of bounds", var_infos[i].name()); 197 } 198 } 199 if (VarInfo.debugMissing.isLoggable(Level.FINE)) { 200 VarInfo.debugMissing.fine( 201 "Removing slice " 202 + this 203 + " because var " 204 + var_infos[i].name() 205 + " array index out of bounds"); 206 } 207 return result; 208 } 209 } 210 211 int mod1 = full_vt.getModified(vi1); 212 if (mod1 == ValueTuple.MISSING_FLOW || mod1 == ValueTuple.MISSING_NONSENSICAL) { 213 return emptyInvList; 214 } 215 216 if (mod1 == ValueTuple.STATIC_CONSTANT) { 217 assert vi1.is_static_constant; 218 mod1 = ((num_samples() == 0) ? ValueTuple.MODIFIED : ValueTuple.UNMODIFIED); 219 } 220 221 int mod2 = full_vt.getModified(vi2); 222 if (mod2 == ValueTuple.MISSING_FLOW || mod2 == ValueTuple.MISSING_NONSENSICAL) { 223 return emptyInvList; 224 } 225 if (mod2 == ValueTuple.STATIC_CONSTANT) { 226 assert vi2.is_static_constant; 227 mod2 = ((num_samples() == 0) 228 ? ValueTuple.MODIFIED : ValueTuple.UNMODIFIED); 229 } 230 231 Object val1 = full_vt.getValue(vi1); 232 assert Intern.isInterned(val1) : "obj " + val1 + " class " + val1.getClass(); 233 234 Object val2 = full_vt.getValue(vi2); 235 assert Intern.isInterned(val2); 236 237 // Supply the new values to all the invariant objects. 238 assert (mod1 == vi1.getModified(full_vt)) 239 || ((vi1.getModified(full_vt) == ValueTuple.STATIC_CONSTANT) 240 && ((mod1 == ValueTuple.UNMODIFIED) || (mod1 == ValueTuple.MODIFIED))); 241 242 List<Invariant> weakened_invs = add_val_bu(val1, val2, mod1, mod2, count); 243 244 return weakened_invs; 245 } 246 247 public List<Invariant> add_val_bu(@Interned Object val1, @Interned Object val2, 248 int mod1, int mod2, int count) { 249 250 assert (mod1 != ValueTuple.MISSING_FLOW 251 && mod1 != ValueTuple.MISSING_NONSENSICAL) 252 && (mod2 != ValueTuple.MISSING_FLOW 253 && mod2 != ValueTuple.MISSING_NONSENSICAL); 254 255 List<Invariant> result = new ArrayList<>(); 256 boolean array1 = var_infos[0].rep_type.isArray(); 257 boolean array2 = var_infos[1].rep_type.isArray(); 258 if (array2 && ! array1) { 259 for (Invariant invariant : invs) { 260 BinaryInvariant inv = (BinaryInvariant) invariant; 261 if (inv.is_false()) { 262 continue; 263 } 264 InvariantStatus status = inv.add(val2, val1, mod1, count); 265 if (status == InvariantStatus.FALSIFIED) { 266 inv.falsify(); 267 result.add(inv); 268 } else if (status == InvariantStatus.WEAKENED) { 269 result.add(inv); 270 } 271 if (PrintInvariants.print_discarded_invariants && inv.is_false()) { 272 DiscReasonMap.put(inv, DiscardCode.bad_sample, 273 "Falsified from sample: " 274 + var_infos[0].name() 275 + " = " + val1 + "," + var_infos[1].name() 276 + " = " + Debug.toString(val2)); 277 } 278 } 279 } else { 280 for (Invariant invariant : invs) { 281 BinaryInvariant inv = (BinaryInvariant) invariant; 282 if (inv.is_false()) { 283 continue; 284 } 285 InvariantStatus status = inv.add(val1, val2, mod1, count); 286 if (status == InvariantStatus.FALSIFIED) { 287 inv.falsify(); 288 result.add(inv); 289 } else if (status == InvariantStatus.WEAKENED) { 290 result.add(inv); 291 } 292 if (PrintInvariants.print_discarded_invariants && inv.is_false()) { 293 if (array1 294 && array2 295 && !( (inv instanceof SeqSeqIntEqual) 296 || (inv instanceof SeqSeqFloatEqual) 297 || (inv instanceof SeqSeqStringEqual) ) 298 && ArraysPlume.length(val1) != ArraysPlume.length(val2)) { 299 // No need to print out two potentially huge arrays if the 300 // reason for discard was because of different array lengths 301 DiscReasonMap.put(inv, DiscardCode.bad_sample, 302 "Samples seen with different array lengths"); 303 } else { 304 DiscReasonMap.put(inv, DiscardCode.bad_sample, 305 "Falsified from sample: " + var_infos[0].name() 306 + " = " + Debug.toString(val1) 307 + "," + var_infos[1].name() 308 + " = " + Debug.toString(val2)); 309 } 310 } 311 } 312 } 313 return result; 314 } 315 316 @Override 317 public void addInvariant(Invariant invariant) { 318 assert invariant != null; 319 // assert invariant.ppt == this; 320 321 if (Debug.dkconfig_internal_check) { 322 // Don't add a check here to ensure that the invairant is not suppressed 323 // in some cases (see NIS.apply_samples), a suppressed invariant is 324 // added and them later removed when the suppression is found 325 // if (invariant.is_ni_suppressed()) { 326 // System.out.printf("suppressed invariant %s is being added to ppt %s " 327 // + "with %d samples%n", invariant.format(), this, 328 // this.num_samples()); 329 // NISuppressionSet ss = invariant.get_ni_suppressions(); 330 // ss.suppressed (invariant.ppt); 331 // System.out.printf("suppression = %s%n", ss); 332 // throw new Error(); 333 // } 334 } 335 336 invs.add(invariant); 337 Global.instantiated_invariants++; 338 if (Invariant.logOn()) { 339 invariant.log("Instantiated %s", invariant.format()); 340 } 341 } 342 343 /** 344 * Copy invariants from this slice to a new slice over the variables argNewVarInfos. The new slice 345 * should not already exist. 346 */ 347 @Override 348 protected PptSlice cloneAndPivot(VarInfo[] argNewVarInfos) { 349 350 // Sort the VarInfos by var_index and build a matching permutation 351 // from the current order to the new order 352 VarInfo[] vis_sorted = argNewVarInfos.clone(); 353 Arrays.sort(vis_sorted, VarInfo.IndexComparator.getInstance()); 354 int[] permutation = PptTopLevel.build_permute(argNewVarInfos, vis_sorted); 355 356 // Assert sorted 357 for (int i = 0; i < 2 - 1; i++) { 358 assert vis_sorted[i].varinfo_index <= vis_sorted[i + 1].varinfo_index; 359 } 360 361 assert ArraysPlume.fnIsPermutation(permutation); 362 // Assert that the permutation represents the rearrangement 363 for (int i = 0; i < 2; i++) { 364 // the variable that used to be at position "i" is now found at 365 // position permutation[i]. 366 VarInfo oldvi = argNewVarInfos[i]; 367 VarInfo newvi = vis_sorted[permutation[i]]; 368 assert oldvi == newvi; 369 } 370 371 // The new slice should not already exist. 372 assert parent.findSlice(vis_sorted) == null; 373 374 // Why not just clone? Because then index order wouldn't be 375 // preserved 376 PptSlice2 result = new PptSlice2(this.parent, vis_sorted); 377 378 // re-parent the invariants and copy them out. 379 List<Invariant> newInvs = new ArrayList<>(); 380 for (Invariant inv : invs) { 381 assert inv.ppt == this; 382 Invariant newInv = inv.transfer(result, permutation); 383 newInvs.add(newInv); 384 assert newInv != inv; 385 assert newInv.ppt == result; 386 assert inv.ppt == this; 387 } 388 389 if (Debug.logOn()) { 390 result.log( 391 "Copied " 392 + newInvs.size() 393 + " invariants from " 394 + this.name() 395 + " with " 396 + invs.size() 397 + " invariants"); 398 } 399 result.invs.addAll(newInvs); 400 if (PptSliceEquality.debug.isLoggable(Level.FINE)) { 401 PptSliceEquality.debug.fine("cloneAndPivot: newInvs " + invs); 402 } 403 return result; 404 } 405 406 /** 407 * Creates invariants at this ppt by merging invariants from each of its children. An invariant 408 * must exist at each of the children in order for it to be created here (at the parent). 409 * Additionally, some invariants have state information that must be merged. This is done by the 410 * invariant itself. 411 * 412 * <p>The basic steps are: 413 * 414 * <ol> 415 * <li>Find all of the child invariants. These are the invariants in the matching slice of each 416 * child. 417 * <li>For each invariant class, build a list of all of the invariants of that class. Note that 418 * some invariant classes (eg, functionBinary) contain distinct invariants, each of which 419 * must be merged separately. See Invariant.Match for more information concerning what makes 420 * an invariant the 'same' 421 * <li>Each invariant that is found at each of the children is then merged to possibly create a 422 * parent invariant. 423 * </ol> 424 */ 425 public void merge_invariants() { 426 427 if (debugMerge.isLoggable(Level.FINE)) { 428 debugMerge.fine("merging invs for " + name()); 429 } 430 431 // List of all invariants found over all of the children 432 List<Invariant> all_invs = new ArrayList<>(); 433 434 // Keep count of the number of valid children processed. An invariant 435 // must be found at each valid child in order to exist at the parent. 436 // A valid child is one that has received samples and has a corresponding 437 // variable for each parent variable 438 int valid_child_count = 0; 439 440 // Loop through all of the children of our top level parent 441 child_loop: 442 for (PptRelation rel : parent.children) { 443 PptTopLevel ppt = rel.child; 444 445 // Skip any children that have not seen any samples 446 if (ppt.num_samples() == 0) { 447 if (debugMerge.isLoggable(Level.FINE)) { 448 debugMerge.fine("-- slice ignored (no samples) " + ppt.name()); 449 } 450 continue; 451 } 452 453 // Child variable info 454 /*NNC:@MonotonicNonNull*/ VarInfo[] cvis = new VarInfo[var_infos.length]; 455 /*NNC:@MonotonicNonNull*/ VarInfo[] cvis_sorted = new VarInfo[var_infos.length]; 456 457 // Build the corresponding array of VarInfos for the child. If any 458 // of the vars don't exist in this child, skip the child (since we 459 // won't have data for each variable). 460 for (int j = 0; j < var_infos.length; j++) { 461 VarInfo pv = var_infos[j]; 462 VarInfo cv = rel.childVar(pv); 463 if (cv == null) { 464 continue child_loop; 465 } 466 cvis[j] = cv.canonicalRep(); 467 cvis_sorted[j] = cv.canonicalRep(); 468 } 469 470 cvis = castNonNullDeep(cvis); // https://tinyurl.com/cfissue/986 471 cvis_sorted = castNonNullDeep(cvis_sorted); // https://tinyurl.com/cfissue/986 472 473 // If any of the child variables have always been missing, this 474 // particular slice in the child received no samples. If dynamic 475 // constants are enabled, the slice will have never been created. 476 // These slices can be skipped unless they contain a missing out of 477 // bound var. Out of bounds variables destroy all invariants in 478 // the slice (since the variable is deemed to be nonsensical) 479 if (slice_missing(ppt, cvis)) { 480 if (debugMerge.isLoggable(Level.FINE)) { 481 debugMerge.fine( 482 "-- slice ignored (missing) " + ppt.name() + " vars " + Arrays.toString(cvis_sorted)); 483 } 484 continue; 485 } 486 487 // The child variables must be sorted by their index (in the child) 488 Arrays.sort(cvis_sorted, VarInfo.IndexComparator.getInstance()); 489 490 // Keep track of the number of valid children 491 valid_child_count++; 492 493 // Find the corresponding slice. If the slice does not exist or 494 // has no invariants, there can be no invariants to merge (since 495 // invariants must exist at each child to exist at the parent) 496 PptSlice2 cslice = (PptSlice2) ppt.findSlice(cvis_sorted); 497 if ((cslice == null) || (cslice.invs.size() == 0)) { 498 if (Debug.logOn()) { 499 this.log( 500 "slice not found " 501 + ppt.name() 502 + " " 503 + Arrays.toString(cvis_sorted) 504 + " num_samples= " 505 + ppt.num_samples() 506 + " ppt.constants = " 507 + ppt.constants); 508 } 509 if (debugMerge.isLoggable(Level.FINE)) { 510 debugMerge.fine( 511 "-- slice not found " + ppt.name() + " vars " + Arrays.toString(cvis_sorted)); 512 } 513 return; 514 } 515 516 // // Update sample count info 517 // mod_samples += cslice.mod_samples; 518 // unmod_samples += cslice.unmod_samples; 519 520 // Build the permutation array from child to parent slice 521 int[] permute = PptTopLevel.build_permute(cvis_sorted, cvis); 522 523 // Debug print child vars and permute to parent 524 if (debugMerge.isLoggable(Level.FINE)) { 525 debugMerge.fine("-- Processing child " + ppt.name() + " (" + rel.getRelationType() + ")"); 526 debugMerge.fine("-- -- child vars = " + Arrays.toString(cvis_sorted)); 527 debugMerge.fine("-- -- parent vars = " + Arrays.toString(var_infos)); 528 debugMerge.fine("-- -- permute = " + Arrays.toString(permute)); 529 } 530 531 // Add each invariant (permuted to match the parent varinfos) 532 // to our list of invariants. 533 for (Invariant orig_inv : cslice.invs) { 534 Invariant inv = orig_inv.clone_and_permute(permute); 535 all_invs.add(inv); 536 if (Invariant.logOn()) { 537 /*NNC:@MonotonicNonNull*/ VarInfo[] child_vars = new VarInfo[var_infos.length]; 538 for (int k = 0; k < var_infos.length; k++) { 539 VarInfo pv = var_infos[k]; 540 @SuppressWarnings("nullness") 541 @NonNull VarInfo cv = rel.childVar(pv); 542 assert cv != null; 543 child_vars[k] = cv.canonicalRep(); 544 } 545 child_vars = castNonNullDeep(child_vars); // https://tinyurl.com/cfissue/986 546 orig_inv.log("org inv"); 547 inv.log( 548 "Created %s from %s using permute %s cvis_sorted = %s cvis = %s for ppt %s", 549 inv, 550 orig_inv, 551 Arrays.toString(permute), 552 Arrays.toString(cvis_sorted), 553 Arrays.toString(child_vars), 554 parent.name()); 555 } 556 } 557 } 558 559 log("Found " + all_invs.size() + " invariants to merge"); 560 if (debugMerge.isLoggable(Level.FINE) && (valid_child_count == 0)) { 561 debugMerge.fine("-- No valid children found"); 562 } 563 564 // For each invariant found, find the list of invariants of the 565 // same type (type corresponds basically but not exactly to the 566 // invariants class) and add the invariant to that list. 567 // Invariant.Match.equals() defines if two invariants are of the 568 // same 'type' for the purpose of merging invariants. 569 Map<Invariant.Match, List<Invariant>> inv_map = 570 new LinkedHashMap<>(); 571 for (Invariant inv : all_invs) { 572 Invariant.Match imatch = new Invariant.Match(inv); 573 List<Invariant> invs = inv_map.get(imatch); 574 if (Invariant.logOn()) { 575 inv.log("Adding %s to %s invs list %s", inv.format(), name(), invs); 576 } 577 if (invs == null) { 578 invs = new ArrayList<Invariant>(); 579 inv_map.put(imatch, invs); 580 } 581 invs.add(inv); 582 } 583 584 // Attempt to create a parent invariant for each invariant that 585 // appeared at each valid child. Note that some invariants will 586 // not exist at the parent even if they exist at each child (eg, 587 // LinearBinary) 588 for (List<Invariant> child_invs : inv_map.values()) { 589 if (child_invs.size() > valid_child_count) { 590 // this shouldn't happen 591 System.out.println( 592 "Found " 593 + child_invs.size() 594 + " invariants at " 595 + name() 596 + " (" 597 + valid_child_count 598 + " children)"); 599 for (Invariant child_inv : child_invs) { 600 System.out.printf( 601 "-- Invariant = '%s' [%s] @%s%n", 602 child_inv.repr(), child_inv.getClass(), child_inv.ppt); 603 } 604 assert child_invs.size() <= valid_child_count; 605 } 606 if (child_invs.size() == valid_child_count) { 607 Invariant first = child_invs.get(0); 608 if (Debug.logOn()) { 609 first.log("Attempting merge of %s invariants into ppt %s", child_invs.size(), name()); 610 } 611 Invariant parent_inv = first.merge(child_invs, this); 612 if (parent_inv != null) { 613 invs.add(parent_inv); 614 if (Debug.logOn()) { 615 parent_inv.log("Merge successful of %s into %s", parent_inv.format(), name()); 616 } 617 } 618 } else { 619 if (Debug.logOn()) { 620 Invariant inv = child_invs.get(0); 621 inv.log( 622 "Not merging invariant into %s, Found %s child invariants in %s children", 623 name(), child_invs.size(), valid_child_count); 624 } 625 } 626 } 627 } 628 629 /** 630 * Returns whether or not the slice is missing due to having one or more of its variables always 631 * missing. This returns true only for missing flow and/or missing nonsensical. Out of Bounds is 632 * treated differently since it destroys all of its invariants. 633 */ 634 private boolean slice_missing(PptTopLevel ppt, VarInfo[] vis) { 635 636 if (ppt.constants != null) { 637 638 if ((ppt.constants.is_missing(vis[0]) || ppt.constants.is_missing(vis[1])) 639 && !vis[0].missingOutOfBounds() 640 && !vis[1].missingOutOfBounds()) { 641 return true; 642 } 643 644 } 645 return false; 646 } 647}