Skip to content

Commit

Permalink
introduce 'fatal assertions'
Browse files Browse the repository at this point in the history
This introduces a variant of ASSERT instructions that are fatal when they
are refuted.  Execution paths through fatal assertions that are refuted are
undefined.  Assertions that (otherwise) pass and that are reachable from a
refuted fatal assertion are now reported as UNKNOWN.

The motivating use-case for fatal assertions is undefined behavior in
languages such as C/C++ or Rust.
  • Loading branch information
kroening committed Mar 23, 2024
1 parent e8ff03a commit 31d5640
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ CORE
div_by_zero.c
--div-by-zero-check --trace
\[main\.division-by-zero\.1\] line \d division by zero in x / y: FAILURE
\[main\.division-by-zero\.2\] line \d+ division by zero in x / z: SUCCESS
\[main\.division-by-zero\.2\] line \d+ division by zero in x / z: UNKNOWN
y=0
^VERIFICATION FAILED$
^EXIT=10$
Expand Down
11 changes: 11 additions & 0 deletions regression/cbmc/Division3/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
_Bool nondet_bool();

void main()
{
int divisor = nondet_bool() ? 2 : 0;

// possible division by zero
int result = 10 / divisor;

__CPROVER_assert(divisor == 2 || divisor == 0, "divisor value");
}
11 changes: 11 additions & 0 deletions regression/cbmc/Division3/test.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CORE
main.c

^EXIT=10$
^SIGNAL=0$
^\[main\.division-by-zero\.1\] line 8 division by zero in 10 / divisor: FAILURE$
^\[main\.assertion\.1\] line 10 divisor value: UNKNOWN$
^\*\* 1 of 2 failed
^VERIFICATION FAILED$
--
^warning: ignoring
3 changes: 3 additions & 0 deletions src/ansi-c/goto_check_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,9 @@ void goto_check_ct::add_guarded_property(
}
else
{
if(property_class == "division-by-zero")
annotated_location.property_fatal(true);

new_code.add(goto_programt::make_assertion(
std::move(guarded_expr), annotated_location));
}
Expand Down
3 changes: 2 additions & 1 deletion src/goto-checker/Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
SRC = bmc_util.cpp \
counterexample_beautification.cpp \
cover_goals_report_util.cpp \
incremental_goto_checker.cpp \
fatal_assertions.cpp \
goto_symex_fault_localizer.cpp \
goto_symex_property_decider.cpp \
goto_trace_storage.cpp \
goto_verifier.cpp \
incremental_goto_checker.cpp \
multi_path_symex_checker.cpp \
multi_path_symex_only_checker.cpp \
properties.cpp \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ Author: Daniel Kroening, Peter Schrammel
#ifndef CPROVER_GOTO_CHECKER_ALL_PROPERTIES_VERIFIER_WITH_TRACE_STORAGE_H
#define CPROVER_GOTO_CHECKER_ALL_PROPERTIES_VERIFIER_WITH_TRACE_STORAGE_H

#include "goto_verifier.h"
#include <goto-programs/abstract_goto_model.h>

#include "bmc_util.h"
#include "fatal_assertions.h"
#include "goto_trace_storage.h"
#include "goto_verifier.h"
#include "incremental_goto_checker.h"
#include "properties.h"
#include "report_util.h"
Expand Down Expand Up @@ -62,6 +64,8 @@ class all_properties_verifier_with_trace_storaget : public goto_verifiert
++iterations;
}

propagate_fatal_assertions(properties, goto_model.get_goto_functions());

return determine_result(properties);
}

Expand Down
174 changes: 174 additions & 0 deletions src/goto-checker/fatal_assertions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*******************************************************************\
Module: Fatal Assertions
Author: Daniel Kroening, [email protected]
\*******************************************************************/

/// \file
/// Fatal Assertions

#include "fatal_assertions.h"

#include <util/irep_hash.h>

#include <goto-programs/goto_functions.h>

#include <stack>
#include <unordered_set>

struct function_loc_pairt
{
using function_itt = goto_functionst::function_mapt::const_iterator;
function_loc_pairt(
function_itt __function_it,
goto_programt::const_targett __target)
: function_it(__function_it), target(__target)
{
}
function_itt function_it;
goto_programt::const_targett target;
bool operator==(const function_loc_pairt &other) const
{
return function_it->first == other.function_it->first &&
target == other.target;
}
};

struct function_itt_hasht
{
using function_itt = goto_functionst::function_mapt::const_iterator;
std::size_t operator()(const function_itt &function_it) const
{
return function_it->first.hash();
}
};

struct function_loc_pair_hasht
{
std::size_t operator()(const function_loc_pairt &p) const
{
auto h1 = p.function_it->first.hash();
auto h2 = const_target_hash{}(p.target);
return hash_combine(h1, h2);
}
};

using loc_sett =
std::unordered_set<function_loc_pairt, function_loc_pair_hasht>;

static void
reachable_fixpoint(loc_sett &locs, const goto_functionst &goto_functions)
{
// shared return location -- we are not call-site sensitive
using function_itt = goto_functionst::function_mapt::const_iterator;
std::unordered_map<function_itt, loc_sett, function_itt_hasht>
return_locations;

// frontier set
std::stack<function_loc_pairt> working;

for(auto loc : locs)
working.push(loc);

while(!working.empty())
{
auto loc = working.top();
working.pop();

auto insertion_result = locs.insert(loc);
if(!insertion_result.second)
continue; // seen already

if(loc.target->is_end_function())
{
// look up our return locations
auto return_locations_it = return_locations.find(loc.function_it);
if(return_locations_it != return_locations.end())
{
for(auto successor : return_locations_it->second)
working.push(successor);
}
}
else if(loc.target->is_function_call())
{
// get the callee
auto &function = loc.target->call_function();
if(function.id() == ID_symbol)
{
auto &function_identifier = to_symbol_expr(function).get_identifier();
auto function_iterator =
goto_functions.function_map.find(function_identifier);
CHECK_RETURN(function_iterator != goto_functions.function_map.end());
working.emplace(
function_iterator,
function_iterator->second.body.instructions.begin());

// insert the successor location as the return location
loc.target++;
return_locations[function_iterator].insert(loc);
}
}
else
{
auto &body = loc.function_it->second.body;

for(auto successor : body.get_successors(loc.target))
working.emplace(loc.function_it, successor);
}
}
}

void propagate_fatal_assertions(
propertiest &properties,
const goto_functionst &goto_functions)
{
// Iterate to find refuted fatal assertions. Anything reachalble
// from there is a 'fatal loc'.
loc_sett fatal_locs;

for(auto function_it = goto_functions.function_map.begin();
function_it != goto_functions.function_map.end();
function_it++)
{
auto &body = function_it->second.body;
for(auto target = body.instructions.begin();
target != body.instructions.end();
target++)
{
if(target->is_assert() && target->source_location().property_fatal())
{
auto id = target->source_location().get_property_id();
auto property = properties.find(id);
CHECK_RETURN(property != properties.end());

// Status?
if(property->second.status == property_statust::FAIL)
{
fatal_locs.emplace(function_it, target);
}
}
}
}

// Saturate fixpoint.
reachable_fixpoint(fatal_locs, goto_functions);

// Now mark PASS assertions as UNKNOWN.
for(auto &loc : fatal_locs)
{
if(loc.target->is_assert())
{
auto id = loc.target->source_location().get_property_id();
auto property = properties.find(id);
CHECK_RETURN(property != properties.end());

// Status?
if(property->second.status == property_statust::PASS)
{
property->second.status = property_statust::UNKNOWN;
}
}
}
}
29 changes: 29 additions & 0 deletions src/goto-checker/fatal_assertions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*******************************************************************\
Module: Fatal Assertions
Author: Daniel Kroening, [email protected]
\*******************************************************************/

/// \file
/// Fatal Assertions

#ifndef CPROVER_GOTO_CHECKER_FATAL_ASSERTIONS_H
#define CPROVER_GOTO_CHECKER_FATAL_ASSERTIONS_H

#include "properties.h"

class goto_functionst;

/// Assertions after fatal assertions that are
/// refuted do not have meaning, and passing ones
/// are marked as UNKNOWN.
/// Assertions that are independently refuted are left
/// in that state, even though the counterexample
/// might be invalid.
/// This avoids ambiguity if two refuted fatal asserions
/// may reach each other.
void propagate_fatal_assertions(propertiest &, const goto_functionst &);

#endif // CPROVER_GOTO_CHECKER_FATAL_ASSERTIONS_H
1 change: 1 addition & 0 deletions src/goto-checker/multi_path_symex_only_checker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ operator()(propertiest &properties)

resultt result(resultt::progresst::DONE);
update_properties(properties, result.updated_properties);

return result;
}

Expand Down
1 change: 1 addition & 0 deletions src/util/irep_ids.def
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ IREP_ID_ONE(line)
IREP_ID_ONE(column)
IREP_ID_ONE(comment)
IREP_ID_ONE(property_class)
IREP_ID_ONE(property_fatal)
IREP_ID_ONE(property_id)
IREP_ID_ONE(function)
IREP_ID_ONE(mathematical_function)
Expand Down
13 changes: 13 additions & 0 deletions src/util/source_location.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ class source_locationt:public irept
return find(ID_basic_block_source_lines);
}

bool property_fatal() const
{
return get_bool(ID_property_fatal);
}

void set_file(const irep_idt &file)
{
set(ID_file, file);
Expand Down Expand Up @@ -163,6 +168,14 @@ class source_locationt:public irept
add(ID_basic_block_source_lines, std::move(source_lines));
}

void property_fatal(bool _property_fatal)
{
if(_property_fatal)
set(ID_property_fatal, true);
else
remove(ID_property_fatal);
}

void set_hide()
{
set(ID_hide, true);
Expand Down

0 comments on commit 31d5640

Please sign in to comment.