Search code examples
c++g++clang-tidy

Linter check for reference removal in auto type


I wanted to know if there is any compiler option or clang-tidy checks for this situation:

#include<iostream>
#include <ostream>

int glob = 12;

int& test(int d){
    std::cout << &glob << std::endl;
    return glob;
}


int main() {

    auto a = test(10);
    std::cout << &a << std::endl;
    return 0;
}

If I suddenly forget to put the & after the auto, I will see different addresses in the output, and it is expected, but is there any compiler flag or clang-tidy flag that warns me about it?


Solution

  • I'm not aware of an existing warning or check that does this, but one can use clang-query to check for the pattern of declaring a variable using type auto (which is distinct from auto&) and initializing it with a call to a function that returns a reference.

    The following shell script invokes clang-query with such a match expression:

    #!/bin/sh
    
    PATH=$HOME/opt/clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04/bin:$PATH
    
    # In the following, the comments are seen and ignored by clang-query
    # (not the shell).  But beware that clang-query has odd rules for where
    # comments can go, and poor error reporting if the rules are violated.
    query='m
    
      varDecl(                         # Report any variable declaration
        hasType(                       # whose declared type
          autoType()                   # is `auto`,
        ),
        hasInitializer(                # and that has an initializer
          implicitCastExpr(            # that is an implicit conversion
            hasSourceExpression(       # from
              callExpr(                # a function call expression
                callee(                # where the callee
                  functionDecl(        # is a directly named function
                    returns(           # whose return type
                      referenceType()  # is a reference.
                    )
                  )
                )
              )
            )
          )
        )
      ).bind("decl")
    
    '
    
    if [ "x$1" = "x" ]; then
      echo "usage: $0 filename.cc -- <compile options like -I, etc.>"
      exit 2
    fi
    
    # Run the query.  Setting 'bind-root' to false means clang-query will
    # not also print a redundant "root" binding.
    clang-query \
      -c="set bind-root false" \
      -c="$query" \
      "$@"
    
    # EOF
    

    On the following input:

    // test.cc
    // Cases for `auto` declaring non-ref initialized from ref.
    
    int &getref();
    
    int getnonref();
    
    void g(int &refparam)
    {
      // ref -> ref
      auto &r2r = getref();       // not reported: no conversion
    
      // ref -> nonref
      auto r2nr = getref();       // reported
    
      // nonref -> nonref
      auto nr2nr = getnonref();   // not reported: no conversion
    
      // nonref -> ref: Syntax error.
      //auto &nr2r = getnonref();
    
      auto rp2nr = refparam;      // not reported: not a call
    }
    
    // EOF
    

    it produces the output:

    $ ./cmd.sh test.cc --
    
    Match #1:
    
    $PWD/test.cc:14:3: note: "decl" binds here
      auto r2nr = getref();       // reported
      ^~~~~~~~~~~~~~~~~~~~
    1 match.
    

    In the match expression, I restrict reporting to initializers that are function calls. However, depending on what you're after, you might want to delete the entire hasSourceExpression matcher (so implicitCastExpr is just followed by ()), thus reporting any auto declaration initialized using an implicit conversion, since arguably any such case is "surprising" and perhaps unintended.

    I haven't done any large-scale testing or tuning of this matcher, so it might need further adjustment for use in production.

    The procedure for integrating clang-query into a build or test workflow is basically the same as doing so for clang-tidy, assuming the latter is done by direct invocation.