https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77606

--- Comment #3 from Martin Sebor <msebor at gcc dot gnu.org> ---
(In reply to Jakub Jelinek from comment #2)
> The only bug is in the testcase, using __builtin_object_size (, 2) for this
> is just wrong

Unless the manual does a better job explaining things it's hard to guess how to
use the feature, even for someone who has been working with this feature for
years.  Here's what I see there now:

  The intended use can be e.g.

     #undef memcpy
     #define bos0(dest) __builtin_object_size (dest, 0)
     #define memcpy(dest, src, n) \
       __builtin___memcpy_chk (dest, src, n, bos0 (dest))

Given this example of intended use it seems that another such example is one
where the bos macro is defined like so:

     #define bos2(dest) __builtin_object_size (dest, 2)
     #define memcpy(dest, src, n) \
       __builtin___memcpy_chk (dest, src, n, bos2 (dest))

If this isn't meant to be a valid example then the manual needs to be clarified
and explain what some of the other examples might be (and also what would not
be valid examples).  I can appreciate that passing 0 as the object size to
__builtin___memcpy_chk and interpreting it as "unknown size" in the absence of
any other information is tricky.  But that's a problem with (defect in) the
design of the builti-ins that needs to highlighted in the manual so users know
to avoid it.  This was also one point of the test case in comment #0.  My bad
for not articulating it more clearly.

> 0 is completely valid return value for that builtin, it is
> the "don't know" value that is returned whenever the code doesn't figure it
> out.

Exactly.  Overloading the return value this way is the problem that either
needs to be solved or that needs to be documented in the manual (i.e., the
manual needs to explain that the use of the bos2() macro may lead to false
positives in some cases and give examples of some of them).

> __builtin_object_size (, 0) in this case also returns -1, "don't know" value.
> 
> So, if anything, this can be treated as an enhancement request.  Right now
> for malloc-like calls, including calls with alloc_size argument, we do:
>   if (arg1 < 0 || arg1 >= (int)gimple_call_num_args (call)
>       || TREE_CODE (gimple_call_arg (call, arg1)) != INTEGER_CST
>       || (arg2 >= 0
>           && (arg2 >= (int)gimple_call_num_args (call)
>               || TREE_CODE (gimple_call_arg (call, arg2)) != INTEGER_CST)))
>     return unknown[object_size_type];
> So, the enhancement request would be for gimple_call_arg (call, argN) here
> being an SSA_NAME instead of INTEGER_CST to try to prove some range instead
> (and for the multiplication case do it with extra care because of possible
> overflows).

I haven't studied the implementation in enough detail to comment on the
suggestion above but I can say what my expectation is from a user's point of
view: since there is no practical difference between an object allocated by
'__builtin_malloc (1 < argc ? 40 : 80)' and one allocated by '1 < argc ?
__builtin_malloc (40) : __builtin_malloc (80)', then since writing past the end
of one such object is detected so should be writing past the end of the other. 
When it isn't, users don't care if it's due to some inherent limitation in the
GCC representation of the two expressions, or what changes in GCC might be
required to make it so.  To them, the feature simply doesn't work the way they
expect and the way it's documented to.  In most users' minds that's also not an
enhancement but a deficiency.

> I have reservations about using get_range_info for that though, the VRP
> stuff is computed from many sources, including assumptions that undefined
> behavior will not happen etc.  But, these builtins are primarily used for
> security purposes, and the stuff it is guarding is also undefined behavior,
> so if anything, it should do some cheap short walk of the operands and
> compute range more conservatively (perhaps only allow SSA_NAME copies,
> perhaps casts, and PHIs with all constant arguments or something similar?).

I don't necessarily insist on using VRP here.  If there's a different/better
way to do it I'm all for it.  But if there isn't, I don't think that just
because VRP assumes the absence of undefined behavior it can't or shouldn't be
used to help detect its absence in calls to memcpy et al., unless it causes
false positives.

Reply via email to