Skip to content

Commit d397f58

Browse files
committed
Memory allocations produced via alloca have function scope
We must not mark the allocation as dead when leaving the block, but instead need to defer that until the end of the function.
1 parent e04d0eb commit d397f58

File tree

5 files changed

+150
-38
lines changed

5 files changed

+150
-38
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#include <stdlib.h>
2+
3+
#ifdef _WIN32
4+
void *alloca(size_t alloca_size);
5+
#endif
6+
7+
typedef int *int_ptr;
8+
9+
int_ptr global;
10+
11+
void foo()
12+
{
13+
int_ptr ptr[2];
14+
for(int i = 0; i < 2; ++i)
15+
{
16+
ptr[i] = alloca(sizeof(int));
17+
}
18+
*(ptr[0]) = 42;
19+
*(ptr[1]) = 42;
20+
21+
_Bool nondet;
22+
if(nondet)
23+
global = ptr[0];
24+
else
25+
global = ptr[1];
26+
}
27+
28+
int main()
29+
{
30+
foo();
31+
*global = 42;
32+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--pointer-check
4+
^\[main.pointer_dereference.\d+\] line 31 dereference failure: dead object in \*global: FAILURE$
5+
^\*\* 1 of \d+ failed
6+
^VERIFICATION FAILED$
7+
^EXIT=10$
8+
^SIGNAL=0$
9+
--
10+
^warning: ignoring

src/goto-programs/builtin_functions.cpp

Lines changed: 94 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Author: Daniel Kroening, [email protected]
1616
#include <util/cprover_prefix.h>
1717
#include <util/expr_initializer.h>
1818
#include <util/expr_util.h>
19+
#include <util/fresh_symbol.h>
1920
#include <util/mathematical_expr.h>
2021
#include <util/mathematical_types.h>
2122
#include <util/pointer_expr.h>
@@ -721,6 +722,97 @@ void goto_convertt::do_havoc_slice(
721722
dest.add(goto_programt::make_other(array_replace, source_location));
722723
}
723724

725+
/// alloca allocates memory that is freed when leaving the function (and not the
726+
/// block, as regular destructors would do).
727+
void goto_convertt::do_alloca(
728+
const exprt &lhs,
729+
const symbol_exprt &function,
730+
const exprt::operandst &arguments,
731+
goto_programt &dest,
732+
const irep_idt &mode)
733+
{
734+
const source_locationt &source_location = function.source_location();
735+
exprt new_lhs = lhs;
736+
737+
// make sure we have a left-hand side to track the allocation even when the
738+
// original program did not
739+
if(lhs.is_nil())
740+
{
741+
new_lhs = new_tmp_symbol(
742+
to_code_type(function.type()).return_type(),
743+
"alloca",
744+
dest,
745+
source_location,
746+
mode)
747+
.symbol_expr();
748+
}
749+
750+
// do the actual function call
751+
code_function_callt function_call(new_lhs, function, arguments);
752+
function_call.add_source_location() = source_location;
753+
copy(function_call, FUNCTION_CALL, dest);
754+
755+
// Don't add instrumentation when we're in alloca (which might in turn call
756+
// __builtin_alloca) -- the instrumentation will be done for the call of
757+
// alloca. Also, we can only add instrumentation when we're in a function
758+
// context.
759+
if(
760+
function.source_location().get_function() == "alloca" || !targets.prefix ||
761+
!targets.suffix)
762+
{
763+
return;
764+
}
765+
766+
// create a symbol to eventually (and non-deterministically) mark the
767+
// allocation as dead; this symbol has function scope and is initialised to
768+
// NULL
769+
symbol_exprt this_alloca_ptr =
770+
get_fresh_aux_symbol(
771+
to_code_type(function.type()).return_type(),
772+
id2string(function.source_location().get_function()),
773+
"tmp_alloca",
774+
source_location,
775+
mode,
776+
symbol_table)
777+
.symbol_expr();
778+
goto_programt decl_prg;
779+
decl_prg.add(goto_programt::make_decl(this_alloca_ptr, source_location));
780+
decl_prg.add(goto_programt::make_assignment(
781+
this_alloca_ptr,
782+
null_pointer_exprt{to_pointer_type(this_alloca_ptr.type())},
783+
source_location));
784+
targets.prefix->destructive_insert(
785+
targets.prefix->instructions.begin(), decl_prg);
786+
787+
// non-deterministically update this_alloca_ptr
788+
if_exprt rhs{
789+
side_effect_expr_nondett{bool_typet(), source_location},
790+
new_lhs,
791+
this_alloca_ptr};
792+
dest.add(goto_programt::make_assignment(
793+
this_alloca_ptr, std::move(rhs), source_location));
794+
795+
// mark pointer to alloca result as dead, unless the alloca result (in
796+
// this_alloca_ptr) is still NULL
797+
symbol_exprt dead_object_sym =
798+
ns.lookup(CPROVER_PREFIX "dead_object").symbol_expr();
799+
exprt alloca_result =
800+
typecast_exprt::conditional_cast(this_alloca_ptr, dead_object_sym.type());
801+
if_exprt not_null{
802+
equal_exprt{
803+
this_alloca_ptr,
804+
null_pointer_exprt{to_pointer_type(this_alloca_ptr.type())}},
805+
dead_object_sym,
806+
std::move(alloca_result)};
807+
auto assign = goto_programt::make_assignment(
808+
std::move(dead_object_sym), std::move(not_null), source_location);
809+
targets.suffix->insert_before_swap(
810+
targets.suffix->instructions.begin(), assign);
811+
targets.suffix->insert_after(
812+
targets.suffix->instructions.begin(),
813+
goto_programt::make_dead(this_alloca_ptr, source_location));
814+
}
815+
724816
/// add function calls to function queue for later processing
725817
void goto_convertt::do_function_call_symbol(
726818
const exprt &lhs,
@@ -1398,45 +1490,9 @@ void goto_convertt::do_function_call_symbol(
13981490

13991491
copy(function_call, FUNCTION_CALL, dest);
14001492
}
1401-
else if(
1402-
(identifier == "alloca" || identifier == "__builtin_alloca") &&
1403-
function.source_location().get_function() != "alloca")
1493+
else if(identifier == "alloca" || identifier == "__builtin_alloca")
14041494
{
1405-
const source_locationt &source_location = function.source_location();
1406-
exprt new_lhs = lhs;
1407-
1408-
if(lhs.is_nil())
1409-
{
1410-
new_lhs = new_tmp_symbol(
1411-
to_code_type(function.type()).return_type(),
1412-
"alloca",
1413-
dest,
1414-
source_location,
1415-
mode)
1416-
.symbol_expr();
1417-
}
1418-
1419-
code_function_callt function_call(new_lhs, function, arguments);
1420-
function_call.add_source_location() = source_location;
1421-
copy(function_call, FUNCTION_CALL, dest);
1422-
1423-
// create a backup copy to ensure that no assignments to the pointer affect
1424-
// the destructor code that will execute eventually
1425-
if(!lhs.is_nil())
1426-
make_temp_symbol(new_lhs, "alloca", dest, mode);
1427-
1428-
// mark pointer to alloca result as dead
1429-
symbol_exprt dead_object_sym =
1430-
ns.lookup(CPROVER_PREFIX "dead_object").symbol_expr();
1431-
exprt alloca_result =
1432-
typecast_exprt::conditional_cast(new_lhs, dead_object_sym.type());
1433-
if_exprt rhs{
1434-
side_effect_expr_nondett{bool_typet(), source_location},
1435-
std::move(alloca_result),
1436-
dead_object_sym};
1437-
code_assignt assign{
1438-
std::move(dead_object_sym), std::move(rhs), source_location};
1439-
targets.destructor_stack.add(assign);
1495+
do_alloca(lhs, function, arguments, dest, mode);
14401496
}
14411497
else
14421498
{

src/goto-programs/goto_convert_class.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,9 @@ class goto_convertt:public messaget
382382

383383
struct targetst
384384
{
385+
goto_programt *prefix = nullptr;
386+
goto_programt *suffix = nullptr;
387+
385388
bool return_set, has_return_value, break_set, continue_set,
386389
default_set, throw_set, leave_set;
387390

@@ -688,6 +691,12 @@ class goto_convertt:public messaget
688691
const exprt::operandst &arguments,
689692
goto_programt &dest,
690693
const irep_idt &mode);
694+
void do_alloca(
695+
const exprt &lhs,
696+
const symbol_exprt &function,
697+
const exprt::operandst &arguments,
698+
goto_programt &dest,
699+
const irep_idt &mode);
691700

692701
exprt get_array_argument(const exprt &src);
693702
};

src/goto-programs/goto_convert_functions.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ void goto_convert_functionst::convert_function(
196196
tmp_end_function.add(goto_programt::make_end_function(end_location));
197197

198198
targets = targetst();
199+
targets.prefix = &f.body;
200+
targets.suffix = &tmp_end_function;
199201
targets.set_return(end_function);
200202
targets.has_return_value = code_type.return_type().id() != ID_empty &&
201203
code_type.return_type().id() != ID_constructor &&
@@ -235,6 +237,9 @@ void goto_convert_functionst::convert_function(
235237
f.make_hidden();
236238

237239
lifetime = parent_lifetime;
240+
241+
targets.prefix = nullptr;
242+
targets.suffix = nullptr;
238243
}
239244

240245
void goto_convert(goto_modelt &goto_model, message_handlert &message_handler)

0 commit comments

Comments
 (0)