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 9, 2024
1 parent 6608030 commit 58bdc8c
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 4 deletions.
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
4 changes: 2 additions & 2 deletions regression/cbmc/pragma_cprover_enable_all/test.desc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ main.c
^\[main\.pointer_primitives\.\d+\] line 77 pointer outside object bounds in R_OK\(q, \(unsigned (long (long )?)?int\)1\): FAILURE$
^\[main\.pointer_arithmetic\.\d+\] line 78 pointer arithmetic: pointer outside object bounds in p \+ (\(signed int\))?2000000000000(l|ll): FAILURE
^\[main\.division-by-zero\.\d+\] line 84 division by zero in x / den: FAILURE
^\[main\.overflow\.\d+\] line 84 arithmetic overflow on signed division in x / den: SUCCESS
^\[main\.overflow\.\d+\] line 84 arithmetic overflow on signed division in x / den: UNKNOWN
^\[main\.enum-range-check\.\d+\] line 85 enum range check in \(ABC\)10: FAILURE
^\[main\.overflow\.\d+\] line 86 arithmetic overflow on signed (to unsigned )?type conversion in \(char\)\(\(signed int\)i \+ 1\): FAILURE
^\[main\.overflow\.\d+\] line 87 arithmetic overflow on signed \+ in j \+ 1: FAILURE
Expand All @@ -17,7 +17,7 @@ main.c
^\[main\.pointer_primitives\.\d+\] line 40 pointer outside object bounds in R_OK\(q, \(unsigned (long (long )?)?int\)1\): FAILURE$
^\[main\.pointer_arithmetic\.\d+\] line 41 pointer arithmetic: pointer outside object bounds in p \+ .*2000000000000(l|ll): FAILURE
^\[main\.division-by-zero\.\d+\] line 47 division by zero in x / den: FAILURE
^\[main\.overflow\.\d+\] line 47 arithmetic overflow on signed division in x / den: SUCCESS
^\[main\.overflow\.\d+\] line 47 arithmetic overflow on signed division in x / den: UNKNOWN
^\[main\.enum-range-check\.\d+\] line 48 enum range check in \(ABC\)10: FAILURE
^\[main\.overflow\.\d+\] line 49 arithmetic overflow on signed type conversion in \(char\)\(signed int\)i \+ 1\): FAILURE
^\[main\.overflow\.\d+\] line 50 arithmetic overflow on signed \+ in j \+ 1: FAILURE
Expand Down
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 @@ -1726,6 +1726,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
130 changes: 130 additions & 0 deletions src/goto-checker/fatal_assertions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*******************************************************************\
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_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)
{
std::stack<function_loc_pairt> working;

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

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

if(loc.target->is_function_call())
{
}
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 58bdc8c

Please sign in to comment.