2

I'm trying to write a clang-tidy check for certain scenarios involving streaming. Consider this simple function:

#include <iostream>
#include <stdint.h>

void foo(std::ostream& os, uint8_t i) {
    os << i;
    os << 4 << i;
}

If I run clang-query to test some matchers:

$ clang-query foo.cxx --
clang-query> match cxxOperatorCallExpr(hasOverloadedOperatorName("<<"))
// [ ... snip some std library matches ... ]
Match #7:

foo.cxx:5:5: note: "root" binds here
    os << i;
    ^~~~~~~

Match #8:

foo.cxx:6:5: note: "root" binds here
    os << 4 << i;
    ^~~~~~~~~~~~

Match #9:

foo.cxx:6:5: note: "root" binds here
    os << 4 << i;
    ^~~~~~~

This matches all 3 uses of operator<< that I have in this program. Great. However, if I try to add a filter for what the first argument looks like:

clang-query> match cxxOperatorCallExpr(hasOverloadedOperatorName("<<"), hasArgument(0, expr(hasType(asString("std::ostream")))))

Match #1:

foo.cxx:5:5: note: "root" binds here
    os << i;
    ^~~~~~~

Match #2:

foo.cxx:6:5: note: "root" binds here
    os << 4 << i;
    ^~~~~~~

That's it. It doesn't match the whole expression os << 4 << i. Why not? That expression does have type std::ostream. If I dump the AST directly, I see:

|-CXXOperatorCallExpr 0x953f618 <line:6:5, col:16> 'basic_ostream<char, struct std::char_traits<char> >':'class std::basic_ostream<char>' lvalue
| |-ImplicitCastExpr 0x953f600 <col:13> 'basic_ostream<char, struct std::char_traits<char> > &(*)(basic_ostream<char, struct std::char_traits<char> > &, unsigned char)' <FunctionToPointerDecay>
| | `-DeclRefExpr 0x953f5d8 <col:13> 'basic_ostream<char, struct std::char_traits<char> > &(basic_ostream<char, struct std::char_traits<char> > &, unsigned char)' lvalue Function 0x94bcd40 'operator<<' 'basic_ostream<char, struct std::char_traits<char> > &(basic_ostream<char, struct std::char_traits<char> > &, unsigned char)'
| |-CXXOperatorCallExpr 0x953f160 <col:5, col:11> 'std::basic_ostream<char, struct std::char_traits<char> >::__ostream_type':'class std::basic_ostream<char>' lvalue
| | |-ImplicitCastExpr 0x953f148 <col:8> 'std::basic_ostream<char, struct std::char_traits<char> >::__ostream_type &(*)(int)' <FunctionToPointerDecay>
| | | `-DeclRefExpr 0x953f0c0 <col:8> 'std::basic_ostream<char, struct std::char_traits<char> >::__ostream_type &(int)' lvalue CXXMethod 0x94b7740 'operator<<' 'std::basic_ostream<char, struct std::char_traits<char> >::__ostream_type &(int)'
| | |-DeclRefExpr 0x953ec88 <col:5> 'std::ostream':'class std::basic_ostream<char>' lvalue ParmVar 0x953e470 'os' 'std::ostream &'
| | `-IntegerLiteral 0x953ecb0 <col:11> 'int' 4
| `-ImplicitCastExpr 0x953f5c0 <col:16> 'uint8_t':'unsigned char' <LValueToRValue>
|   `-DeclRefExpr 0x953f1a8 <col:16> 'uint8_t':'unsigned char' lvalue ParmVar 0x953e500 'i' 'uint8_t':'unsigned char'

Both the outer and inner CXXOperatorCallExpr say their expression is a class std::basic_ostream<char> lvalue. What's the right way to actually match this argument properly?

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Isn't this evaluated as `(os << 4) << i` therefore the parameters of the second `<<` are like `<<((os << 4), i)`? From the dump of the AST tree it looks like the result of the inner `CXXOperatorCallExpr` is of type `std::basic_ostream` and not `std::ostream`? – Ulrich Thomas Gabor Jan 23 '18 at 09:54
  • @GhostGambler Those are the same type. – Barry Jan 23 '18 at 14:56

1 Answers1

0

It's definitely a problem with the return type of the first sub-call to <<, see the following matcher, which also matches against "class std::__1::basic_ostream<char>" and with this the missing call:

clang-query> match callExpr(hasArgument(0, expr(hasType(anyOf(asString("class std::__1::basic_ostream<char>"), asString("std::ostream"))))))

Match #1:

/Users/ug/clangtest/main.cc:5:5: note: "root" binds here
    os << i;
    ^~~~~~~

Match #2:

/Users/ug/clangtest/main.cc:6:5: note: "root" binds here
    os << 4 << i;
    ^~~~~~~~~~~~

Match #3:

/Users/ug/clangtest/main.cc:6:5: note: "root" binds here
    os << 4 << i;
    ^~~~~~~
3 matches.
Ulrich Thomas Gabor
  • 6,584
  • 4
  • 27
  • 41