Skip to content

Commit f86884c

Browse files
committed
__builtin_constant_p: properly test, fix behaviour
We previously only compile-time tested what required a run-time check of assertions, so move this to the "cbmc" regression test suite. Also, extend it by behaviour that distinguishes it from actual C-standard specified constant expressions. Finally, fix the behaviour for string literals.
1 parent 8430887 commit f86884c

File tree

4 files changed

+129
-53
lines changed

4 files changed

+129
-53
lines changed

regression/ansi-c/gcc_builtin_constant_p1/main.c

-28
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include <assert.h>
2+
3+
#ifdef __GNUC__
4+
enum
5+
{
6+
E1 = 1
7+
} var;
8+
9+
struct whatnot
10+
{
11+
} whatnot_var;
12+
#endif
13+
14+
int main()
15+
{
16+
// this is gcc only
17+
18+
#ifdef __GNUC__
19+
assert(__builtin_constant_p("some string"));
20+
assert(__builtin_constant_p(1.0f));
21+
assert(__builtin_constant_p(E1));
22+
assert(!__builtin_constant_p(var));
23+
assert(!__builtin_constant_p(main));
24+
assert(!__builtin_constant_p(whatnot_var));
25+
assert(!__builtin_constant_p(&var));
26+
assert(__builtin_constant_p(__builtin_constant_p(var)));
27+
28+
// The following are not constant expressions in the sense of the C standard
29+
// and GCC wouldn't deem them constant expressions either, but they are
30+
// subject to GCC's constant folding. See also regression test ansi-c/sizeof6.
31+
// Clang's behaviour, however, is somewhat different. See
32+
// https://github.com/llvm/llvm-project/issues/55946 for further examples of
33+
// where they differ.
34+
int j;
35+
# ifndef __clang__
36+
assert(__builtin_constant_p(j * 0));
37+
assert(__builtin_constant_p(j - j));
38+
assert(__builtin_constant_p(j ? 0ll : 0ll));
39+
# endif
40+
assert(__builtin_constant_p(0 ? j : 0ll));
41+
42+
// side-effects are _not_ evaluated
43+
int i = 0;
44+
assert(!__builtin_constant_p(i++));
45+
assert(i == 0);
46+
#endif
47+
}

regression/ansi-c/gcc_builtin_constant_p1/test.desc regression/cbmc/gcc_builtin_constant_p1/test.desc

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
CORE gcc-only broken-test-c++-front-end
1+
CORE
22
main.c
33

4+
^VERIFICATION SUCCESSFUL$
45
^EXIT=0$
56
^SIGNAL=0$
67
--

src/ansi-c/c_typecheck_expr.cpp

+80-24
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class is_compile_time_constantt
6666

6767
/// This function determines what expressions are to be propagated as
6868
/// "constants"
69-
bool is_constant(const exprt &e) const
69+
virtual bool is_constant(const exprt &e) const
7070
{
7171
// non-standard numeric constant
7272
if(e.id() == ID_infinity)
@@ -138,6 +138,54 @@ class is_compile_time_constantt
138138
}
139139
};
140140

141+
/// Clang appears to have a somewhat different idea of what is/isn't to be
142+
/// considered a constant at compile time.
143+
class clang_is_constant_foldedt : public is_compile_time_constantt
144+
{
145+
public:
146+
explicit clang_is_constant_foldedt(const namespacet &ns)
147+
: is_compile_time_constantt(ns)
148+
{
149+
}
150+
151+
protected:
152+
/// This function determines what expressions are constant folded by clang
153+
bool is_constant(const exprt &e) const override
154+
{
155+
// we need to adhere to short-circuit semantics for the following
156+
if(e.id() == ID_if)
157+
{
158+
const if_exprt &if_expr = to_if_expr(e);
159+
if(!is_constant(if_expr.cond()))
160+
return false;
161+
exprt const_cond = simplify_expr(if_expr.cond(), ns);
162+
CHECK_RETURN(const_cond.is_constant());
163+
if(const_cond.is_true())
164+
return is_constant(if_expr.true_case());
165+
else
166+
return is_constant(if_expr.false_case());
167+
}
168+
else if(e.id() == ID_and || e.id() == ID_or)
169+
{
170+
for(const auto &op : e.operands())
171+
{
172+
if(!is_constant(op))
173+
return false;
174+
exprt const_cond = simplify_expr(op, ns);
175+
CHECK_RETURN(const_cond.is_constant());
176+
// stop when we hit false (for an and) or true (for an or)
177+
if(const_cond == make_boolean_expr(e.id() == ID_or))
178+
break;
179+
}
180+
return true;
181+
}
182+
else if(e.id() == ID_address_of)
183+
return false;
184+
else
185+
return is_compile_time_constantt::is_constant(e);
186+
}
187+
};
188+
141189
void c_typecheck_baset::typecheck_expr(exprt &expr)
142190
{
143191
if(expr.id()==ID_already_typechecked)
@@ -3684,39 +3732,47 @@ exprt c_typecheck_baset::do_special_functions(
36843732
}
36853733
else if(identifier=="__builtin_constant_p")
36863734
{
3687-
// this is a gcc extension to tell whether the argument
3688-
// is known to be a compile-time constant
3735+
// This is a gcc/clang extension to tell whether the argument
3736+
// is known to be a compile-time constant. The behavior of these two
3737+
// compiler families, however, is quite different, which we need to take
3738+
// care of in the below config-dependent branches.
3739+
36893740
if(expr.arguments().size()!=1)
36903741
{
36913742
error().source_location = f_op.source_location();
36923743
error() << "__builtin_constant_p expects one argument" << eom;
36933744
throw 0;
36943745
}
36953746

3696-
// do not typecheck the argument - it is never evaluated, and thus side
3697-
// effects must not show up either
3698-
3699-
// try to produce constant
3700-
exprt tmp1=expr.arguments().front();
3701-
simplify(tmp1, *this);
3702-
3703-
bool is_constant=false;
3704-
3705-
// Need to do some special treatment for string literals,
3706-
// which are (void *)&("lit"[0])
3707-
if(
3708-
tmp1.id() == ID_typecast &&
3709-
to_typecast_expr(tmp1).op().id() == ID_address_of &&
3710-
to_address_of_expr(to_typecast_expr(tmp1).op()).object().id() ==
3711-
ID_index &&
3712-
to_index_expr(to_address_of_expr(to_typecast_expr(tmp1).op()).object())
3713-
.array()
3714-
.id() == ID_string_constant)
3747+
bool is_constant = false;
3748+
if(config.ansi_c.mode == configt::ansi_ct::flavourt::CLANG)
37153749
{
3716-
is_constant=true;
3750+
is_constant = clang_is_constant_foldedt(*this)(expr.arguments().front());
37173751
}
37183752
else
3719-
is_constant=tmp1.is_constant();
3753+
{
3754+
// try to produce constant
3755+
exprt tmp1 = expr.arguments().front();
3756+
simplify(tmp1, *this);
3757+
3758+
// Need to do some special treatment for string literals,
3759+
// which are (void *)&("lit"[0])
3760+
if(
3761+
tmp1.id() == ID_typecast &&
3762+
to_typecast_expr(tmp1).op().id() == ID_address_of &&
3763+
to_address_of_expr(to_typecast_expr(tmp1).op()).object().id() ==
3764+
ID_index &&
3765+
to_index_expr(to_address_of_expr(to_typecast_expr(tmp1).op()).object())
3766+
.array()
3767+
.id() == ID_string_constant)
3768+
{
3769+
is_constant = true;
3770+
}
3771+
else if(tmp1.id() == ID_string_constant)
3772+
is_constant = true;
3773+
else
3774+
is_constant = tmp1.is_constant();
3775+
}
37203776

37213777
exprt tmp2=from_integer(is_constant, expr.type());
37223778
tmp2.add_source_location()=source_location;

0 commit comments

Comments
 (0)