Skip to content

Commit 0846117

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 7f98cb5 commit 0846117

File tree

4 files changed

+130
-54
lines changed

4 files changed

+130
-54
lines changed

regression/ansi-c/gcc_builtin_constant_p1/main.c

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 47 additions & 0 deletions
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 renamed to regression/cbmc/gcc_builtin_constant_p1/test.desc

Lines changed: 2 additions & 1 deletion
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

Lines changed: 81 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@ class is_compile_time_constantt
6565

6666
/// This function determines what expressions are to be propagated as
6767
/// "constants"
68-
bool is_constant(const exprt &e) const
68+
virtual bool is_constant(const exprt &e) const
6969
{
7070
// non-standard numeric constant
7171
if(e.id() == ID_infinity)
7272
return true;
7373

7474
// numeric, character, or pointer-typed constant
75-
if(e.is_constant())
75+
if(e.is_constant() || e.id() == ID_string_constant)
7676
return true;
7777

7878
// possibly an address constant
@@ -137,6 +137,54 @@ class is_compile_time_constantt
137137
}
138138
};
139139

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

3801-
// do not typecheck the argument - it is never evaluated, and thus side
3802-
// effects must not show up either
3803-
3804-
// try to produce constant
3805-
exprt tmp1=expr.arguments().front();
3806-
simplify(tmp1, *this);
3807-
3808-
bool is_constant=false;
3809-
3810-
// Need to do some special treatment for string literals,
3811-
// which are (void *)&("lit"[0])
3812-
if(
3813-
tmp1.id() == ID_typecast &&
3814-
to_typecast_expr(tmp1).op().id() == ID_address_of &&
3815-
to_address_of_expr(to_typecast_expr(tmp1).op()).object().id() ==
3816-
ID_index &&
3817-
to_index_expr(to_address_of_expr(to_typecast_expr(tmp1).op()).object())
3818-
.array()
3819-
.id() == ID_string_constant)
3852+
bool is_constant = false;
3853+
if(config.ansi_c.mode == configt::ansi_ct::flavourt::CLANG)
38203854
{
3821-
is_constant=true;
3855+
is_constant = clang_is_constant_foldedt(*this)(expr.arguments().front());
38223856
}
38233857
else
3824-
is_constant=tmp1.is_constant();
3858+
{
3859+
// try to produce constant
3860+
exprt tmp1 = expr.arguments().front();
3861+
simplify(tmp1, *this);
3862+
3863+
// Need to do some special treatment for string literals,
3864+
// which are (void *)&("lit"[0])
3865+
if(
3866+
tmp1.id() == ID_typecast &&
3867+
to_typecast_expr(tmp1).op().id() == ID_address_of &&
3868+
to_address_of_expr(to_typecast_expr(tmp1).op()).object().id() ==
3869+
ID_index &&
3870+
to_index_expr(to_address_of_expr(to_typecast_expr(tmp1).op()).object())
3871+
.array()
3872+
.id() == ID_string_constant)
3873+
{
3874+
is_constant = true;
3875+
}
3876+
else if(tmp1.id() == ID_string_constant)
3877+
is_constant = true;
3878+
else
3879+
is_constant = tmp1.is_constant();
3880+
}
38253881

38263882
exprt tmp2=from_integer(is_constant, expr.type());
38273883
tmp2.add_source_location()=source_location;

0 commit comments

Comments
 (0)