Bug 82134 - warn_unused_result triggers on empty structs even when they are used
Summary: warn_unused_result triggers on empty structs even when they are used
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: c (show other bugs)
Version: 7.2.1
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: diagnostic
Depends on:
Blocks:
 
Reported: 2017-09-07 13:20 UTC by Zack Weinberg
Modified: 2021-05-16 05:02 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Zack Weinberg 2017-09-07 13:20:30 UTC
If a function that returns an empty struct is tagged with attribute warn_unused_result, any call will trigger the warning, even if the return value is (nominally) used.  For instance

struct Empty {};

extern void use_empty(struct Empty);

__attribute__((warn_unused_result))
extern struct Empty make_empty(void);

void should_warn(void)
{
    make_empty();
}

void shouldnt_warn_1(void)
{
    use_empty(make_empty());
}

void shouldnt_warn_2(void)
{
    struct Empty e = make_empty();
    use_empty(e);
}

-->

test.c: In function ‘should_warn’:
test.c:10:5: warning: ignoring return value of ‘make_empty’, declared with attribute warn_unused_result [-Wunused-result]
     make_empty();
     ^~~~~~~~~~~~
test.c: In function ‘shouldnt_warn_1’:
test.c:15:5: warning: ignoring return value of ‘make_empty’, declared with attribute warn_unused_result [-Wunused-result]
     use_empty(make_empty());
     ^~~~~~~~~~~~~~~~~~~~~~~
test.c: In function ‘shouldnt_warn_2’:
test.c:20:22: warning: ignoring return value of ‘make_empty’, declared with attribute warn_unused_result [-Wunused-result]
     struct Empty e = make_empty();
                      ^~~~~~~~~~~~

with both GCC 6 and GCC 7.

(From here: https://stackoverflow.com/questions/46096628/gcc-empty-structs-and-wunused-result)
Comment 1 Jakub Jelinek 2017-09-07 15:06:20 UTC
I fail to see the usefulness of warn_unused_result attribute on functions that return such types, what kind of problem is calling those without assigning that to a var?  There are no data copied...
From implementation POV, the problem is that the empty structure copies are discarded during gimplification (in different spots, for C++ they are discarded through cp-gimplify.c (cp_gimplify_expr) doing:
        else if (simple_empty_class_p (TREE_TYPE (op0), op1))
          {
            /* Remove any copies of empty classes.  Also drop volatile
               variables on the RHS to avoid infinite recursion from
               gimplify_expr trying to load the value.  */
            if (TREE_SIDE_EFFECTS (op1))
              {
                if (TREE_THIS_VOLATILE (op1)
                    && (REFERENCE_CLASS_P (op1) || DECL_P (op1)))
                  op1 = build_fold_addr_expr (op1);
  
                gimplify_and_add (op1, pre_p);
              }
            gimplify_expr (&TREE_OPERAND (*expr_p, 0), pre_p, post_p,
                           is_gimple_lvalue, fb_lvalue);
            *expr_p = TREE_OPERAND (*expr_p, 0);
          }
while for C, it is done through gimplify.c (gimplify_modify_expr) doing:
  /* For zero sized types only gimplify the left hand side and right hand
     side as statements and throw away the assignment.  Do this after
     gimplify_modify_expr_rhs so we handle TARGET_EXPRs of addressable
     types properly.  */
  if (zero_sized_type (TREE_TYPE (*from_p)) && !want_value)
    {
      gimplify_stmt (from_p, pre_p);
      gimplify_stmt (to_p, pre_p);
      *expr_p = NULL_TREE;
      return GS_ALL_DONE;
    }

and after that there is no way to differentiate if the call had a lhs or not.

Perhaps we could add a langhook and for zero_sized_type or if the langhook is true (would use simple_empty_class_p for C++, otherwise false) just ignore the warn_unused_result attribute or something similar; that would mean we wouldn't warn in should_warn at least for C though (for C++ we would, because we turn it into a TARGET_EXPR and warn as we voidify the call).
Comment 2 Zack Weinberg 2017-09-07 15:45:31 UTC
The claim in the Stack Overflow post was that this was useful in a scenario involving machine-generated code that couldn't return void for some external reason, but they didn't go into any kind of detail.

It has always been my opinion that warnings in general should be made optimization-independent, including early dead code optimizations like this, with _possible_ carefully-defined-and-documented exceptions for `if (0) { /* don't warn about stuff in here */ }` and similar.  But I realize that's a long way from where GCC is or has ever been.
Comment 3 Marek Polacek 2017-09-07 15:53:24 UTC
The problem with `if (0) { /* don't warn about stuff in here */ }` is that you can jump into that block from elsewhere:

if (0)
  {
L:
    // ...
  }

so it's not as easy as it might seem.
Comment 4 Arne Vogel 2017-11-13 13:41:53 UTC
@Jakub Jelinek: Returning empty structs (this affects empty tuples as well) can be useful in templates. E.g.

struct empty_t {};

template<typename C>
void executeContext()
{
    auto savedState = C::prepare();
    C::execute();
    C::cleanup(std::move(savedState));
}

/*
 * Context which does not require saving state but should be compatible
 * with executeContext().
 */
struct StatelessContext
{
    static empty_t prepare();
    static void execute();
    static void cleanup(empty_t);
};

// Usage: executeContext<StatelessContext>();

Obviously, void does not work here *precisely* because executeContext saves (i.e. uses) the return value. I have an example that makes more sense than the above, but takes longer to explain. Anyway, I hope you get the idea.

A possible workaround is e.g. to use a dummy char instead. The documentation says (slightly misleadingly, see below) empty structs in G++ are treated as though they contained a single char. But this in turn may cause unwanted interference e.g. with empty base optimization.
Comment 5 Jakub Jelinek 2017-11-13 13:47:23 UTC
I don't argue that returning empty structures can be sometimes useful.
But I fail to understand why would you want to use warn_unused_result attribute on such functions, that just makes no sense, because there is no harm if the empty struct is not copied to a temporary.
Comment 6 David Blaikie 2021-05-16 05:02:11 UTC
For what it's worth, this is being actively worked around in gmock here: https://github.com/google/googletest/blob/662fe38e44900c007eccb65a5d2ea19df7bd520e/googlemock/include/gmock/gmock-more-actions.h#L295