void GetSufficientConditions(CheckerPropagate *propagate, Bit *safe_bit, Vector<CheckerPropagate*> *propagate_list) { CheckerState *state = propagate->m_frame->State(); Solver *solver = state->GetSolver(); if (!solver->IsSatisfiable()) return; SufficientTester tester(propagate, propagate_list); if (tester.verbose) logout << "SUFFICIENT: " << propagate->m_frame << ": Finding candidates: " << safe_bit << endl; // get any potential sufficient conditions from the frame's impliciations. Vector<Bit*> imply_list; GetImplySufficient(propagate->m_frame, &imply_list); // try to follow possible equalities to get a sufficient condition. GetEqualitySufficient(&tester, safe_bit, imply_list); for (size_t ind = 0; ind < imply_list.Size(); ind++) tester.TestBit(imply_list[ind]); // try to mark the bit itself as a sufficient condition. tester.TestBit(safe_bit); }
// returns whether the error condition is satisfiable within frame. bool TestErrorSatisfiable(CheckerState *state, CheckerFrame *frame, Bit *bit) { BlockMemory *mcfg = frame->Memory(); Solver *solver = state->GetSolver(); if (!solver->IsSatisfiable()) { if (checker_verbose.IsSpecified()) logout << "CHECK: " << frame << ": Guard unsatisfiable: " << bit << " [" << bit->Hash() << "]" << endl; return false; } state->PushContext(); state->AssertBaseBits(); if (!solver->IsSatisfiable()) { if (checker_verbose.IsSpecified()) logout << "CHECK: " << frame << ": Error unsatisfiable: " << bit << " [" << bit->Hash() << "]" << endl; state->PopContext(); return false; } if (!frame->m_checked_assertions) { frame->m_checked_assertions = true; // check to see if the error is contradicted by previous assertions // in this frame. assert the previous assertions, but don't keep // them around past this function to avoid polluting the solver // with worthless extra checks. BlockSummary *sum = GetBlockSummary(mcfg->GetId()); const Vector<AssertInfo> *asserts = sum->GetAsserts(); size_t assert_count = VectorSize<AssertInfo>(asserts); for (size_t ind = 0; ind < assert_count; ind++) { const AssertInfo &info = asserts->At(ind); // only use the same kind of assertion to check for redundancy. if (info.kind != state->GetAssertKind()) continue; if (info.cls != ASC_Check) continue; if (info.point < frame->EndPoint()) { // get the asserted condition relative to block entry. Bit *assert_value; mcfg->TranslateBit(TRK_Point, info.point, info.bit, &assert_value); assert_value->MoveRef(&assert_value, NULL); Bit *point_guard = mcfg->GetGuard(info.point); point_guard->IncRef(); Bit *imply_assert = Bit::MakeImply(point_guard, assert_value); solver->AddConstraint(frame->Id(), imply_assert); } } sum->DecRef(); if (!solver->IsSatisfiable()) { if (checker_verbose.IsSpecified()) logout << "CHECK: " << frame << ": Unsatisfiable from assertions" << endl; state->PopContext(); return false; } } state->PopContext(); return true; }
// check propagation for each point bit in the specified frame. this is called // both for the initial and intermediate checks of the assertion. assert_safe // indicates that this is an initial check or an intermediate check of a heap // invariant, and should be marked as a base bit/frame in the state. bool CheckFrameList(CheckerState *state, CheckerFrame *frame, PPoint point, bool allow_point, bool assert_safe, Bit *base_bit, const GuardBitVector &point_list) { // check if we are ignoring this function outright. BlockId *id = frame->CFG()->GetId(); if (id->Kind() != B_Initializer && IgnoreFunction(id->BaseVar())) { if (checker_verbose.IsSpecified()) logout << "CHECK: " << frame << ": Ignoring function" << endl; return false; } Solver *solver = state->GetSolver(); if (!solver->IsSatisfiable()) { if (checker_verbose.IsSpecified()) logout << "CHECK: " << frame << ": List unsatisfiable" << endl; return false; } for (size_t ind = 0; ind < point_list.Size(); ind++) { const GuardBit &gb = point_list[ind]; state->PushContext(); // the guard for the paths this safe bit takes are an extra assumed bit. frame->PushAssumedBit(gb.guard); // add side conditions and pending information from the bit. solver->AddSideConditions(frame->Id(), gb.bit); if (assert_safe) state->PushBaseBit(gb.bit, frame); if (TestErrorSatisfiable(state, frame, gb.bit)) { // error is feasible along these paths, construct a propagation // for the safe bit and continue exploration. CheckerPropagate propagate(frame, point, allow_point); propagate.m_id = state->GetPropagateId(); propagate.FindTest(base_bit, gb.bit); state->m_stack.PushBack(&propagate); // check the frame against this propagation. if (CheckFrame(state, frame, &propagate)) return true; // check if there was a soft timeout while we were finished // exploring this path. when the timeout occurs all satisfiable // queries become false so we will end up here. if (TimerAlarm::ActiveExpired()) { logout << "Timeout: "; PrintTime(TimerAlarm::ActiveElapsed()); logout << endl; state->SetReport(RK_Timeout); return true; } state->m_stack.PopBack(); } // no error along these paths, unwind the changes we made beforehand. if (assert_safe) state->PopBaseBit(); frame->PopAssumedBit(); state->PopContext(); } return false; }
// mark the trivial/redundant assertions in the specified list. void MarkRedundantAssertions(BlockMemory *mcfg, Vector<AssertInfo> &asserts) { BlockCFG *cfg = mcfg->GetCFG(); // assertions are marked redundant in two passes: // 1. for each path reaching the assertion, the validity of the assertion is // implied by one or more prior or future assertions. // this pass also picks up assertions which trivially hold, where the // assertion is valid due to the conditions along the paths themselves. // 2. there is an isomorphic assertion within an inner loop. it is // sufficient to check just the inner assertion. // implication works differently for invariants vs. other assertions, // since the invariant condition will be asserted at block exit. // for regular assertions, an bit is redundant if (guard ==> bit) // is implied by the (oguard ==> obit) for other assertions: // VALID((oguard ==> obit) ==> (guard ==> bit)) // !SAT(!((oguard ==> obit) ==> (guard ==> bit))) // !SAT(!(!(oguard ==> obit) || (guard ==> bit))) // !SAT((oguard ==> obit) && !(guard ==> bit)) // !SAT((oguard ==> obit) && !(!guard || bit)) // !SAT((oguard ==> obit) && guard && !bit) // for invariants, a bit is redundant if guard implies the oguard // for other invariants with the same asserted bit: // VALID(guard ==> oguard) // !SAT(!(guard ==> oguard)) // !SAT(!(!guard || oguard)) // !SAT(guard && !oguard) Solver *solver = new Solver("redundant"); for (size_t ind = 0; ind < asserts.Size(); ind++) { AssertInfo &info = asserts[ind]; solver->PushContext(); Assert(info.cls == ASC_Check); // assert guard. Bit *guard = mcfg->GetGuard(info.point); solver->AddAssert(0, guard); if (info.kind != ASK_Invariant) { // assert !bit. Bit *not_bit = Bit::MakeNot(info.bit); Bit *result_not_bit; mcfg->TranslateBit(TRK_Point, info.point, not_bit, &result_not_bit); solver->AddAssert(0, result_not_bit); } if (!solver->IsSatisfiable()) { // the assert is tautological or is proved by the guard, thus trivial. info.cls = ASC_Trivial; solver->PopContext(); continue; } // assert the remaining assertions in the summary hold. for (size_t aind = 0; aind < asserts.Size(); aind++) { const AssertInfo &oinfo = asserts[aind]; // skip this assertion itself. if (info.point == oinfo.point && info.bit == oinfo.bit) continue; // skip assertions already marked as trivial or redundant. if (oinfo.cls != ASC_Check) continue; // skip assertions for a different kind than the original. // this avoids interference between the different kinds of assertions, // though it is unlikely to affect whether we actually mark an // assert as redundant. if (oinfo.kind != info.kind) continue; Bit *oguard = mcfg->GetGuard(oinfo.point); if (info.kind == ASK_Invariant) { // only compare with other invariants for the same bit. if (oinfo.bit != info.bit) continue; // assert !oguard Bit *not_oguard = Bit::MakeNot(oguard); solver->AddAssert(0, not_oguard); } else { // assert (oguard ==> obit). Bit *result_obit; mcfg->TranslateBit(TRK_Point, oinfo.point, oinfo.bit, &result_obit); Bit *imply_bit = Bit::MakeImply(oguard, result_obit); solver->AddAssert(0, imply_bit); } } if (!solver->IsSatisfiable()) { // the assert is implied by the remaining assertions, thus redundant. info.cls = ASC_Redundant; } solver->PopContext(); } solver->Clear(); delete solver; for (size_t ind = 0; ind < cfg->GetEdgeCount(); ind++) { PEdgeLoop *loop_edge = cfg->GetEdge(ind)->IfLoop(); if (!loop_edge) continue; for (size_t aind = 0; aind < asserts.Size(); aind++) { AssertInfo &info = asserts[aind]; if (info.cls != ASC_Check) continue; if (cfg->IsLoopIsomorphic(info.point)) { // this assertion's point is isomorphic to a point within the // loop body, so there will be an equivalent loop assertion. info.cls = ASC_Redundant; } } } }
// returns true if bit is a useful condition for testing as sufficient // (no loop-modified terms, preserves reachability of assert, etc.) // regardless of whether it is actually sufficient. bool TestBit(Bit *bit) { Vector<Bit*> &tested_list = propagate->m_sufficient_tested_list; Vector<Bit*> &possible_list = propagate->m_sufficient_possible_list; Vector<Bit*> &sufficient_list = propagate->m_sufficient_list; Solver *solver = state->GetSolver(); if (tested_list.Contains(bit)) { if (possible_list.Contains(bit)) return true; return false; } if (verbose) logout << "SUFFICIENT: " << frame << ": Testing " << bit << " [" << bit->Hash() << "]" << endl; tested_list.PushBack(bit); // don't test for sufficient conditions if a timeout has occurred. if (TimerAlarm::ActiveExpired()) { if (verbose) logout << "SUFFICIENT: " << frame << ": Alarm expired" << endl; return false; } // check that the sufficient condition does not render the point // of the assertion unreachable: the solver is still satisfiable after // asserting the sufficient condition. this also takes care of // unsatisfiable sufficient conditions. state->PushContext(); // ignore bits we can't do any propagation from. CheckerPropagate *test_propagate = new CheckerPropagate(frame, propagate->m_point, propagate->m_allow_point); test_propagate->SetTest(bit); // propagations can trigger new solver side conditions. TODO: should figure // out what's going on here. if (!solver->IsSatisfiable()) { state->PopContext(); return false; } if (test_propagate->m_where->IsNone()) { if (verbose) logout << "SUFFICIENT: " << frame << ": Failed propagate: " << test_propagate->m_where << endl; state->PopContext(); delete test_propagate; return false; } // assert the tested sufficient holds in the frame. frame->AddAssert(bit); if (!solver->IsSatisfiable()) { // the sufficient condition rendered the assertion point unreachable. if (verbose) logout << "SUFFICIENT: " << frame << ": Renders point unreachable" << endl; state->PopContext(); delete test_propagate; return false; } // this is a good potential sufficient condition, remember it. possible_list.PushBack(bit); // check whether the bit is actually a sufficient condition. // just assert the original negated safe bit, and if it is unsatisfiable // it cannot occur under this sufficient condition. state->AssertBaseBits(); bool satisfiable = solver->IsSatisfiable(); if (verbose) { if (satisfiable) { logout << "SUFFICIENT: " << frame << ": Not a sufficient condition:" << endl; solver->PinAssign(); solver->PrintRawAssignment(); solver->UnpinAssign(); } else { logout << "SUFFICIENT: " << frame << ": Success!" << endl; } } if (!satisfiable) { sufficient_list.PushBack(bit); propagate_list->PushBack(test_propagate); } else { delete test_propagate; } state->PopContext(); return true; }