pmeerw's blog

Tue, 05 May 2020

Statically checking C/C++ for unused return values

A seemingly simple problem: check C/C++ code statically for unused return values, but surprisingly here is no easily available tooling. Let's look at some options:

  1. C++-17 has annotation [[nodiscard]], e.g. the following code (unused-return.cpp)
    int foo() {
      return 42;
    }
    
    [[nodiscard]]
    int bar() {
      return 23;
    }
    
    int main() {
      foo();
      bar();
    }
    
    when compiled with g++-8 unused-return.cpp, will result in
    unused-return.cpp: In function ‘int main()’:
    unused-return.cpp:12:6: warning: ignoring return value of ‘int bar()’, declared with attribute nodiscard [-Wunused-result]
       bar();
       ~~~^~
    unused-return.cpp:6:5: note: declared here
     int bar() {
         ^~~
    
    (tested with GCC 8.4 / Ubuntu)

    No warning will printed (foo()), unless [[nodiscard]] is annotated (bar()).

  2. With GCC and clang, an attribute can be added to the function declaration, e.g. unused-return.c:
    __attribute__ ((warn_unused_result))
    int bar() {
      return 23;
    }
    
    resulting in a warning
    unused-return.c: In function ‘main’:
    unused-return.c:12:3: warning: ignoring return value of ‘bar’, declared with attribute warn_unused_result [-Wunused-result]
       bar();
       ^~~~~
    
    when compiled with gcc unused-return.c (GCC 8.4/Ubuntu). It doesn't help to enable warnings to get a similar warning for function foo().
  3. Synopsys Coverity can be used, at least it will report a warning when the return value of a function is checked inconsistently. The tool is costly and probably a bit overkill...
  4. A linter can be used, e.g the free splint tool, splint unused-return.c, but the output is quite verbose and doesn't cover C++:
    Splint 3.1.2 --- 20 Feb 2018
    
    unused-return.c: (in function main)
    unused-return.c:11:3: Return value (type int) ignored: foo()
      Result returned by function call is not used. If this is intended, can cast
      result to (void) to eliminate message. (Use -retvalint to inhibit warning)
    unused-return.c:12:3: Return value (type int) ignored: bar()
    
    Finished checking --- 2 code warnings
    
  5. The clang-query tool can be used to moreless interactively query the AST of the program. This is expored in more detail below...

Stackoverflow provides all the basics: a clang-query script which matches call expressions in the abstract syntax tree (AST) of the program, then restricting to 'intersting cases'.

For a nice intro to clang-query, see this devblog article.

I've added the -w switch to suppress clang warnings when processing the input program, and some bind trickery to make the output a bit nicer.

#!/bin/sh
# unused-return.sh: Run clang-query to report unused return values.

# When --dump, print the AST of matching syntax.
if [ "x$1" = "x--dump" ]; then
  dump="set output dump"
  shift
fi

query='m
  callExpr(
    isExpansionInMainFile(),
    hasParent(anyOf(
      compoundStmt(),
      ifStmt(hasCondition(expr().bind("cond"))),
      whileStmt(hasCondition(expr().bind("cond"))),
      doStmt(hasCondition(expr().bind("cond")))
    )),
    unless(hasType(voidType())),
    unless(isTypeDependent()),
    unless(cxxOperatorCallExpr()),
    unless(callee(namedDecl(anyOf(
      hasName("memset"),
      hasName("setlength"),
      hasName("flags"),
      hasName("width"),
      hasName("__builtin_memcpy")
    )))),
    unless(equalsBoundNode("cond"))).bind("unused-return")'

clang-query-9 -extra-arg="-w" -c="set bind-root false" -c="$dump" -c="$query" "$@" --
The output should look like
Match #1:

unused-return.c:11:3: note: "unused-return" binds here
  foo();
  ^~~~~

Match #2:

unused-return.c:12:3: note: "unused-return" binds here
  bar();
  ^~~~~
2 matches.
A recent clang version is needed, tested with clang 9 / Ubuntu; clang 6 did not work.

All files for download: unused-return.zip.

Update (2020-05-05): MSVC has _Check_return_ and _Must_inspect_result_, for good measure.
Update (2020-05-06): clang-tidy has bugprone-unused-return-value to check for missing return values of certain configured functions, such as std::async(), std::unique_ptr::release(), std::remove()
Update (2020-05-06): see reddit

posted at: 01:30 | path: /programming | permanent link

Made with PyBlosxom