Bug 44677 - Warn for variables incremented but not used (+=, ++)
Summary: Warn for variables incremented but not used (+=, ++)
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c (show other bugs)
Version: 4.5.0
: P3 enhancement
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: diagnostic
: 69723 70092 78964 100184 102909 108156 119544 (view as bug list)
Depends on:
Blocks: Wunused
  Show dependency treegraph
 
Reported: 2010-06-26 00:53 UTC by Joseph S. Myers
Modified: 2025-07-15 19:32 UTC (History)
16 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2020-05-19 00:00:00


Attachments
gcc16-pr44677-wip.patch (2.48 KB, patch)
2025-04-22 16:46 UTC, Jakub Jelinek
Details | Diff
gcc16-pr44677-wip.patch (3.18 KB, patch)
2025-04-23 10:05 UTC, Jakub Jelinek
Details | Diff
gcc16-pr44677.patch (6.85 KB, patch)
2025-04-23 17:45 UTC, Jakub Jelinek
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Joseph S. Myers 2010-06-26 00:53:13 UTC
void
f (void)
{
  int i;
  i = 0;
  i++;
}

seems like something a natural generalization of -Wunused-but-set-variable
should warn for; the value of i is only used as part of an increment of i,
so the definition and all uses of this variable could be safely removed.

For a real example of this, see the variable lang_n_infiles in
gcc.c:process_command (I'm testing a patch that removes that variable
along with other changes).
Comment 1 Andrew Pinski 2010-06-28 00:34:58 UTC
Confirmed.
Comment 2 Manuel López-Ibáñez 2016-02-09 01:17:38 UTC
*** Bug 69723 has been marked as a duplicate of this bug. ***
Comment 3 Manuel López-Ibáñez 2016-03-05 22:41:25 UTC
*** Bug 70092 has been marked as a duplicate of this bug. ***
Comment 4 Martin Sebor 2017-01-04 01:31:43 UTC
*** Bug 78964 has been marked as a duplicate of this bug. ***
Comment 5 Jakub Jelinek 2017-01-04 08:04:48 UTC
As soon as consider more complicated testcases than just pre/post increment, this is going to be very hard to define and especially implement.

The way the warning works right now is that all rvalue uses of a variable are marked as "read" and the warning then warns about variables that don't have the "read" bit set, but have the "used" bit set (i.e. they aren't fully unused).
This is especially because the FEs used to fold expressions aggressively very early (and still do, especially the C FE), so not all expressions (even use in sizeof etc. counts) survive long enough where we actually would know what the lhs is corresponding to rhs.  Also, we do not warn if there are any side-effects in between the rvalue use of the var and the store to it.  So, if we do want to warn about m = m + 1; we still shouldn't warn for m = foo (m) + 1; or m = (n = m) + 1; etc.  Handling the pre/post increment by 1 might be easiest, just remember whether the var is not "read" before processing it and reset the "read" bit if so afterwards, but as soon as you run into more complex expressions that is going to be harder and harder.
Comment 6 Martin Sebor 2017-01-04 18:54:18 UTC
I haven't thought through the implementation challenges but defining the extended -Wunused-but-set-variabl rule that's being suggested here seems straightforward: every write to an object must be followed by another access to it (either read or write).  If not, it's diagnosed.

This seems analogous to the -Wuninitialized checker/rule that might be stated as: the first read of an object must be preceded by a write to it.
Comment 7 Jakub Jelinek 2017-01-04 19:00:56 UTC
(In reply to Martin Sebor from comment #6)
> I haven't thought through the implementation challenges but defining the
> extended -Wunused-but-set-variabl rule that's being suggested here seems
> straightforward: every write to an object must be followed by another access
> to it (either read or write).  If not, it's diagnosed.

That is not straightforward at all.  The FE doesn't have IL on which it can analyze write accesses being followed by something (no cfg, no SSA form), and in the middle end it is way too late for such a warning (because as soon as you optimize away something, you could optimize away those reads and warning just because the optimizers managed to optimize away some use are not really helpful).
Comment 8 Martin Sebor 2019-09-29 18:54:35 UTC
I would expect handling -Wunused-but-set-variable during Gimplification to make detecting these sorts of things possible at least in the basic cases.
Comment 9 Jakub Jelinek 2019-09-29 18:58:31 UTC
(In reply to Martin Sebor from comment #8)
> I would expect handling -Wunused-but-set-variable during Gimplification to
> make detecting these sorts of things possible at least in the basic cases.

That is way too late.
Comment 10 Martin Sebor 2020-05-19 17:23:03 UTC
See pr95217 for other cases to handle (-Wunused-but-set-parameter).  For the test case there, Clang's static analyzer diagnoses two out of the four cases:

$ cat pr95217.c && clang -S -Wall -Wextra --analyze pr95217.c
void f0 (int *p)
{
  p = 0;           // -Wunused-but-set-parameter (expected)
}

void f1 (int *p)
{
  p += 1;          // missing warning
}

void f2 (int *p)
{
  p = p + 1;       // missing warning
}

void f3 (int *p)
{
  ++p;             // missing warning
}

pr95217.c:8:3: warning: Value stored to 'p' is never read
  p += 1;          // missing warning
  ^    ~
pr95217.c:13:3: warning: Value stored to 'p' is never read
  p = p + 1;       // missing warning
  ^   ~~~~~
2 warnings generated.
Comment 11 Joseph S. Myers 2021-04-21 17:41:24 UTC
*** Bug 100184 has been marked as a duplicate of this bug. ***
Comment 12 Andrew Pinski 2021-10-24 06:09:57 UTC
*** Bug 102909 has been marked as a duplicate of this bug. ***
Comment 13 Paul Eggert 2022-04-09 00:48:33 UTC
I ran into this issue today on Emacs master, with both += and |=. Clang correctly diagnosed unused local variables, but GCC did not.

It would be nice if GCC could do at least as well as Clang does.

Here are references to patches I installed into Emacs to fix these issues that Clang found but GCC did not:

https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=d9bffa1f3b121085fd8f954eb9446a4a5241c062
https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=68bc1446855c86b96d5bc22f819e63358ab250ac
Comment 14 David C. Manuelda 2022-09-19 10:03:22 UTC
It is worth to notice that this bug propagates to C++ whenever an object uses an int that increments but is never used, like the following example:

class Test{
        public:
                Test() { i = 0; i++; }
        private:
                int i;
};

int main(int, char**) {
        Test unused;

        return 0;
}
Comment 15 Andrew Pinski 2022-12-17 17:58:15 UTC
*** Bug 108156 has been marked as a duplicate of this bug. ***
Comment 16 Andrew Pinski 2022-12-17 18:04:23 UTC
"set but not used" (just for future searches)
Comment 17 Vincent Lefèvre 2023-07-18 22:59:45 UTC
(In reply to Martin Sebor from comment #10)
> $ cat pr95217.c && clang -S -Wall -Wextra --analyze pr95217.c
[...]
> pr95217.c:8:3: warning: Value stored to 'p' is never read
>   p += 1;          // missing warning
>   ^    ~
> pr95217.c:13:3: warning: Value stored to 'p' is never read
>   p = p + 1;       // missing warning
>   ^   ~~~~~
> 2 warnings generated.

Clang (15 and above) with -Wunused-but-set-variable now detects the issue on the "p++" and "p += 1" forms (ditto with other combined assignment operators), but not on "p = p + 1".

Such forms (p++, etc.) are common, so that detecting an unused variable is very useful.

Like Paul did for Emacs (comment 13), I've just fixed two issues in GNU MPFR (one cosmetic about a useless loop variable and one important in the testsuite), found with Clang 16. The references:
https://gitlab.inria.fr/mpfr/mpfr/-/commit/4c110cf4773b3c07de2a33acbee591cedb083c80
https://gitlab.inria.fr/mpfr/mpfr/-/commit/b34d867fa41934d12d0d4dbaaa0242d6d3eb32c7

For the second MPFR issue, there was an "err++" for each error found by the function in the testsuite, but err was not tested at the end, so that potential errors were never reported.
Comment 18 Andrew Pinski 2025-03-30 14:13:34 UTC
*** Bug 119544 has been marked as a duplicate of this bug. ***
Comment 19 Vincent Lefèvre 2025-04-18 23:04:18 UTC
(In reply to Andrew Pinski from comment #18)
> *** Bug 119544 has been marked as a duplicate of this bug. ***

The example in this bug was

extern char* str;

void foo(void)
{
    int i = 0;

    while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r')
    {
        i++;
        str++;
    }
}

However, the i++ is not completely useless, as this is a way to tell the compiler that the number of iterations is bounded by INT_MAX (perhaps not very useful here, but there may be cases where this kind of information could be used for optimization, e.g. loop unrolling -- there does not seem to be a standard way to give such information).
Comment 20 Eyal Rozenberg 2025-04-19 10:24:26 UTC
(In reply to Vincent Lefèvre from comment #19)
> However, the i++ is not completely useless, as this is a way to tell the
> compiler that the number of iterations is bounded by INT_MAX

I wouldn't say that. I mean, it's true (IIANM) taht after the overflow, we hit undefined behavior territory, meaning that the compiler can break from the loop. But that is the compiler's prerogative, it is not the programmer "telling" the compiler something concrete.

If you're worried about programmers being able to offer these hints to GCC, expecting it to behave a certain way, without getting a warning - I would say that does not justify not-warning; and can be accounted for by, say, considering `(void) i;` as a use, or explicit warning suppression etc.
Comment 21 Jakub Jelinek 2025-04-22 16:46:48 UTC
Created attachment 61171 [details]
gcc16-pr44677-wip.patch

So, I tried to implement this partially, so far in the C FE.
As we've been implementing the current behavior for more than 14 years, I think users should be able to specify what exactly they want in this area, so the WIP patch currently has 3 levels, level 1 is what is currently implemented, level 2 doesn't consider pre/post inc/decrements as reads in addition to that and level 3 in addition doesn't consider x += y as read of x unless x is referenced from inside of y.  Both of these were pretty simple (of course need testsuite coverage).  The question is if we could also handle x = x + y or x = y + x similarly (whether in level 3 or new level 4), there the mark_exp_read already happens quite early and not sure how hard it would be to have an exception for the lhs (but only to the outermost operation, perhaps wrapped in ()s, but not more levels.  And perhaps only if there is no cast needed.

Anyway, I think I should first try to implement what the patch does in C in C++ if it isn't too hard and only then think about further changes.
Comment 22 Jakub Jelinek 2025-04-22 16:51:11 UTC
Unfinished testcase, with -Wunused-but-set-variable=1 this warns just about i in foo,
with -Wunused-but-set-variable=2 about a, b, c, d, i and with -Wunused-but-set-variable=3 about a, b, c, d, e, i.  No warnings in bar in any case.
Comment 23 Jakub Jelinek 2025-04-23 10:05:48 UTC
Created attachment 61175 [details]
gcc16-pr44677-wip.patch

Updated untested patch which handles C++ too.
Comment 24 Jakub Jelinek 2025-04-23 17:45:41 UTC
Created attachment 61182 [details]
gcc16-pr44677.patch

More complete patch, still need to write ChangeLog and bootstrap/regtest it.
Comment 25 GCC Commits 2025-07-15 13:01:52 UTC
The master branch has been updated by Jakub Jelinek <jakub@gcc.gnu.org>:

https://gcc.gnu.org/g:0eac9cfee8cb0b21de866a04d5d59685ab35208f

commit r16-2258-g0eac9cfee8cb0b21de866a04d5d59685ab35208f
Author: Jakub Jelinek <jakub@redhat.com>
Date:   Tue Jul 15 15:00:33 2025 +0200

    c, c++: Extend -Wunused-but-set-* warnings [PR44677]
    
    The -Wunused-but-set-* warnings work by using 2 bits on VAR_DECLs &
    PARM_DECLs, TREE_USED and DECL_READ_P.  If neither is set, we typically
    emit -Wunused-variable or -Wunused-parameter warning, that is for variables
    which are just declared (including initializer) and completely unused.
    If TREE_USED is set and DECL_READ_P is unset, -Wunused-but-set-* warnings
    are emitted, i.e. for variables which can appear on the lhs of an assignment
    expression but aren't actually used elsewhere.  The DECL_READ_P marking is
    done through mark_exp_read called from lots of places (e.g. lvalue to rvalue
    conversions etc.).
    
    LLVM has an extension on top of that in that it doesn't count pre/post
    inc/decrements as use (i.e. DECL_READ_P for GCC).
    
    The following patch does that too, though because we had the current
    behavior for 11+ years already and lot of people is -Wunused-but-set-*
    warning free in the current GCC behavior and not in the clang one (including
    GCC sources), it allows users to choose.
    Furthermore, it implements another level, where also var @= expr uses of var
    (except when it is also used in expr) aren't counted as DECL_READ_P.
    
    I think it would be nice to also handle var = var @ expr or var = expr @ var
    but unfortunately mark_exp_read is then done in both FEs during parsing of
    var @ expr or expr @ var and the code doesn't know it is rhs of an
    assignment with var as lhs.
    
    The patch works mostly by checking if DECL_READ_P is clear at some point and
    then clearing it again after some operation which might have set it.
    
    -Wunused or -Wall or -Wunused -Wextra or -Wall -Wextra turn on the 3 level
    of the new warning (i.e. the one which ignores also var++, ++var etc. as
    well as var @= expr), so does -Wunused-but-set-{variable,parameter}, but
    users can use explicit -Wunused-but-set-{variable,parameter}={1,2} to select
    a different level.
    
    2025-07-15  Jakub Jelinek  <jakub@redhat.com>
                Jason Merrill  <jason@redhat.com>
    
            PR c/44677
    gcc/
            * common.opt (Wunused-but-set-parameter=, Wunused-but-set-variable=):
            New options.
            (Wunused-but-set-parameter, Wunused-but-set-variable): Turn into
            aliases.
            * common.opt.urls: Regenerate.
            * diagnostic-spec.cc (nowarn_spec_t::nowarn_spec_t): Use
            OPT_Wunused_but_set_variable_ instead of OPT_Wunused_but_set_variable
            and OPT_Wunused_but_set_parameter_ instead of
            OPT_Wunused_but_set_parameter.
            * gimple-ssa-store-merging.cc (find_bswap_or_nop_1): Remove unused
            but set variable tmp.
            * ipa-strub.cc (pass_ipa_strub::execute): Cast named_args to
            (void) if ATTR_FNSPEC_DECONST_WATERMARK is not defined.
            * doc/invoke.texi (Wunused-but-set-parameter=,
            Wunused-but-set-variable=): Document new options.
            (Wunused-but-set-parameter, Wunused-but-set-variable): Adjust
            documentation now that they are just aliases.
    gcc/c-family/
            * c-opts.cc (c_common_post_options): Change
            warn_unused_but_set_parameter and warn_unused_but_set_variable
            from 1 to 3 if they were set only implicitly.
            * c-attribs.cc (build_attr_access_from_parms): Remove unused
            but set variable nelts.
    gcc/c/
            * c-parser.cc (c_parser_unary_expression): Clear DECL_READ_P
            after default_function_array_read_conversion for
            -Wunused-but-set-{parameter,variable}={2,3} on
            PRE{IN,DE}CREMENT_EXPR argument.
            (c_parser_postfix_expression_after_primary): Similarly for
            POST{IN,DE}CREMENT_EXPR.
            * c-decl.cc (pop_scope): Use OPT_Wunused_but_set_variable_
            instead of OPT_Wunused_but_set_variable.
            (finish_function): Use OPT_Wunused_but_set_parameter_
            instead of OPT_Wunused_but_set_parameter.
            * c-typeck.cc (mark_exp_read): Handle {PRE,POST}{IN,DE}CREMENT_EXPR
            and don't handle it when cast to void.
            (build_modify_expr): Clear DECL_READ_P after build_binary_op
            for -Wunused-but-set-{parameter,variable}=3.
    gcc/cp/
            * cp-gimplify.cc (cp_fold): Clear DECL_READ_P on lhs of MODIFY_EXPR
            after cp_fold_rvalue if it wasn't set before.
            * decl.cc (poplevel): Use OPT_Wunused_but_set_variable_
            instead of OPT_Wunused_but_set_variable.
            (finish_function): Use OPT_Wunused_but_set_parameter_
            instead of OPT_Wunused_but_set_parameter.
            * expr.cc (mark_use): Clear read_p for {PRE,POST}{IN,DE}CREMENT_EXPR
            cast to void on {VAR,PARM}_DECL for
            -Wunused-but-set-{parameter,variable}={2,3}.
            (mark_exp_read): Handle {PRE,POST}{IN,DE}CREMENT_EXPR and don't handle
            it when cast to void.
            * module.cc (trees_in::fn_parms_fini): Remove unused but set variable
            ix.
            * semantics.cc (finish_unary_op_expr): Return early for
            PRE{IN,DE}CREMENT_EXPR.
            * typeck.cc (cp_build_unary_op): Clear DECL_READ_P
            after mark_lvalue_use for -Wunused-but-set-{parameter,variable}={2,3}
            on PRE{IN,DE}CREMENT_EXPR argument.
            (cp_build_modify_expr): Clear DECL_READ_P after cp_build_binary_op
            for -Wunused-but-set-{parameter,variable}=3.
    gcc/go/
            * gofrontend/gogo.cc (Function::export_func_with_type): Remove
            unused but set variable i.
    gcc/cobol/
            * gcobolspec.cc (lang_specific_driver): Remove unused but set variable
            n_cobol_files.
    gcc/testsuite/
            * c-c++-common/Wunused-parm-1.c: New test.
            * c-c++-common/Wunused-parm-2.c: New test.
            * c-c++-common/Wunused-parm-3.c: New test.
            * c-c++-common/Wunused-parm-4.c: New test.
            * c-c++-common/Wunused-parm-5.c: New test.
            * c-c++-common/Wunused-parm-6.c: New test.
            * c-c++-common/Wunused-var-7.c (bar, baz): Expect warning on a.
            * c-c++-common/Wunused-var-19.c: New test.
            * c-c++-common/Wunused-var-20.c: New test.
            * c-c++-common/Wunused-var-21.c: New test.
            * c-c++-common/Wunused-var-22.c: New test.
            * c-c++-common/Wunused-var-23.c: New test.
            * c-c++-common/Wunused-var-24.c: New test.
            * g++.dg/cpp26/name-independent-decl1.C (foo): Expect one
            set but not used warning.
            * g++.dg/warn/Wunused-parm-12.C: New test.
            * g++.dg/warn/Wunused-parm-13.C: New test.
            * g++.dg/warn/Wunused-var-2.C (f2): Expect set but not used warning
            on parameter x and variable a.
            * g++.dg/warn/Wunused-var-40.C: New test.
            * g++.dg/warn/Wunused-var-41.C: New test.
            * gcc.dg/memchr-3.c (test_find): Change return type from void to int,
            and add return n; statement.
            * gcc.dg/unused-9.c (g): Move dg-bogus to the correct line and expect
            a warning on i.
Comment 26 Vincent Lefèvre 2025-07-15 13:26:18 UTC
(In reply to GCC Commits from comment #25)
>     -Wunused or -Wall or -Wunused -Wextra or -Wall -Wextra turn on [...]

This looks wrong. If I understand what has been added to gcc/doc/invoke.texi, unused-but-set-parameter and unused-but-set-variable need to be distinguished here.
Comment 27 Jakub Jelinek 2025-07-15 13:36:02 UTC
Nothing changed in what options imply -Wunused-but-set-variable or -Wunused-but-set-parameter, just that those 2 options now mean the =3 level and users can explicitly use say -Wunused-but-set-variable=1 etc. to restore previous behavior.
Comment 28 Vincent Lefèvre 2025-07-15 13:47:27 UTC
It seems that to get -Wunused-but-set-parameter=3 implicitly, one needs -Wextra (contrary to -Wunused-but-set-variable=3). So the condition

  -Wunused or -Wall or -Wunused -Wextra or -Wall -Wextra

(which includes the -Wunused alone) seems to be valid only for the unused-but-set-variable case.
Comment 29 GCC Commits 2025-07-15 19:06:38 UTC
The trunk branch has been updated by Jason Merrill <jason@gcc.gnu.org>:

https://gcc.gnu.org/g:35b19980046fc57d9d6851b8f4349bd22de3fa03

commit r16-2270-g35b19980046fc57d9d6851b8f4349bd22de3fa03
Author: Jason Merrill <jason@redhat.com>
Date:   Mon Jul 14 18:29:17 2025 -0400

    c++: don't mark void exprs as read [PR44677]
    
    In Jakub's patch for PR44677 he added code to prevent mark_exp_read on
    e.g. (void)++i from marking i as read, but it seems to me that we can
    generalize that to avoid looking any farther into any void expression;
    you can't read a void value, and an explicit cast will have already called
    mark_exp_read on its operand in convert_to_void.
    
    For testing I added an assert to catch places where we were trying to mark
    void expressions as read, and fix a few that it found.  But there were
    several other places (such as check_return_expr) where we could have a void
    expression but always calling mark_exp_read makes sense, so I dropped the
    assert from the final commit.
    
            PR c++/44677
    
    gcc/cp/ChangeLog:
    
            * cp-gimplify.cc (cp_fold) [CLEANUP_POINT_EXPR]: Don't force rvalue.
            [COMPOUND_EXPR]: Likewise.
            * cvt.cc (convert_to_void): Call mark_exp_read later.
            * expr.cc (mark_use): Turn off read_p for any void argument.
            (mark_exp_read): Return early for void argument.
Comment 30 Jakub Jelinek 2025-07-15 19:32:13 UTC
(In reply to Vincent Lefèvre from comment #28)
> It seems that to get -Wunused-but-set-parameter=3 implicitly, one needs
> -Wextra (contrary to -Wunused-but-set-variable=3). So the condition
> 
>   -Wunused or -Wall or -Wunused -Wextra or -Wall -Wextra
> 
> (which includes the -Wunused alone) seems to be valid only for the
> unused-but-set-variable case.

Sure, -Wunused-but-set-parameter* has always been only with -Wunused -Wextra (where -Wunused is enabled by -Wall), while -Wunused-but-set-variable* just by -Wunused.
Nothing changed on that.

The docs document that:
This @option{-Wunused-but-set-parameter=3} warning is also enabled by
@option{-Wunused} together with @option{-Wextra}.
vs.
This @option{-Wunused-but-set-variable=3} warning is also enabled by
@option{-Wunused}, which is enabled by @option{-Wall}.