ArgumentCommentCheck.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. //===--- ArgumentCommentCheck.cpp - clang-tidy ----------------------------===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. #include "ArgumentCommentCheck.h"
  9. #include "clang/AST/ASTContext.h"
  10. #include "clang/ASTMatchers/ASTMatchFinder.h"
  11. #include "clang/Lex/Lexer.h"
  12. #include "clang/Lex/Token.h"
  13. #include "../utils/LexerUtils.h"
  14. using namespace clang::ast_matchers;
  15. namespace clang::tidy::bugprone {
  16. namespace {
  17. AST_MATCHER(Decl, isFromStdNamespaceOrSystemHeader) {
  18. if (const auto *D = Node.getDeclContext()->getEnclosingNamespaceContext())
  19. if (D->isStdNamespace())
  20. return true;
  21. if (Node.getLocation().isInvalid())
  22. return false;
  23. return Node.getASTContext().getSourceManager().isInSystemHeader(
  24. Node.getLocation());
  25. }
  26. } // namespace
  27. ArgumentCommentCheck::ArgumentCommentCheck(StringRef Name,
  28. ClangTidyContext *Context)
  29. : ClangTidyCheck(Name, Context),
  30. StrictMode(Options.getLocalOrGlobal("StrictMode", false)),
  31. IgnoreSingleArgument(Options.get("IgnoreSingleArgument", false)),
  32. CommentBoolLiterals(Options.get("CommentBoolLiterals", false)),
  33. CommentIntegerLiterals(Options.get("CommentIntegerLiterals", false)),
  34. CommentFloatLiterals(Options.get("CommentFloatLiterals", false)),
  35. CommentStringLiterals(Options.get("CommentStringLiterals", false)),
  36. CommentUserDefinedLiterals(
  37. Options.get("CommentUserDefinedLiterals", false)),
  38. CommentCharacterLiterals(Options.get("CommentCharacterLiterals", false)),
  39. CommentNullPtrs(Options.get("CommentNullPtrs", false)),
  40. IdentRE("^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {}
  41. void ArgumentCommentCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  42. Options.store(Opts, "StrictMode", StrictMode);
  43. Options.store(Opts, "IgnoreSingleArgument", IgnoreSingleArgument);
  44. Options.store(Opts, "CommentBoolLiterals", CommentBoolLiterals);
  45. Options.store(Opts, "CommentIntegerLiterals", CommentIntegerLiterals);
  46. Options.store(Opts, "CommentFloatLiterals", CommentFloatLiterals);
  47. Options.store(Opts, "CommentStringLiterals", CommentStringLiterals);
  48. Options.store(Opts, "CommentUserDefinedLiterals", CommentUserDefinedLiterals);
  49. Options.store(Opts, "CommentCharacterLiterals", CommentCharacterLiterals);
  50. Options.store(Opts, "CommentNullPtrs", CommentNullPtrs);
  51. }
  52. void ArgumentCommentCheck::registerMatchers(MatchFinder *Finder) {
  53. Finder->addMatcher(
  54. callExpr(unless(cxxOperatorCallExpr()), unless(userDefinedLiteral()),
  55. // NewCallback's arguments relate to the pointed function,
  56. // don't check them against NewCallback's parameter names.
  57. // FIXME: Make this configurable.
  58. unless(hasDeclaration(functionDecl(
  59. hasAnyName("NewCallback", "NewPermanentCallback")))),
  60. // Ignore APIs from the standard library, since their names are
  61. // not specified by the standard, and standard library
  62. // implementations in practice have to use reserved names to
  63. // avoid conflicts with same-named macros.
  64. unless(hasDeclaration(isFromStdNamespaceOrSystemHeader())))
  65. .bind("expr"),
  66. this);
  67. Finder->addMatcher(cxxConstructExpr(unless(hasDeclaration(
  68. isFromStdNamespaceOrSystemHeader())))
  69. .bind("expr"),
  70. this);
  71. }
  72. static std::vector<std::pair<SourceLocation, StringRef>>
  73. getCommentsInRange(ASTContext *Ctx, CharSourceRange Range) {
  74. std::vector<std::pair<SourceLocation, StringRef>> Comments;
  75. auto &SM = Ctx->getSourceManager();
  76. std::pair<FileID, unsigned> BeginLoc = SM.getDecomposedLoc(Range.getBegin()),
  77. EndLoc = SM.getDecomposedLoc(Range.getEnd());
  78. if (BeginLoc.first != EndLoc.first)
  79. return Comments;
  80. bool Invalid = false;
  81. StringRef Buffer = SM.getBufferData(BeginLoc.first, &Invalid);
  82. if (Invalid)
  83. return Comments;
  84. const char *StrData = Buffer.data() + BeginLoc.second;
  85. Lexer TheLexer(SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(),
  86. Buffer.begin(), StrData, Buffer.end());
  87. TheLexer.SetCommentRetentionState(true);
  88. while (true) {
  89. Token Tok;
  90. if (TheLexer.LexFromRawLexer(Tok))
  91. break;
  92. if (Tok.getLocation() == Range.getEnd() || Tok.is(tok::eof))
  93. break;
  94. if (Tok.is(tok::comment)) {
  95. std::pair<FileID, unsigned> CommentLoc =
  96. SM.getDecomposedLoc(Tok.getLocation());
  97. assert(CommentLoc.first == BeginLoc.first);
  98. Comments.emplace_back(
  99. Tok.getLocation(),
  100. StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength()));
  101. } else {
  102. // Clear comments found before the different token, e.g. comma.
  103. Comments.clear();
  104. }
  105. }
  106. return Comments;
  107. }
  108. static std::vector<std::pair<SourceLocation, StringRef>>
  109. getCommentsBeforeLoc(ASTContext *Ctx, SourceLocation Loc) {
  110. std::vector<std::pair<SourceLocation, StringRef>> Comments;
  111. while (Loc.isValid()) {
  112. clang::Token Tok = utils::lexer::getPreviousToken(
  113. Loc, Ctx->getSourceManager(), Ctx->getLangOpts(),
  114. /*SkipComments=*/false);
  115. if (Tok.isNot(tok::comment))
  116. break;
  117. Loc = Tok.getLocation();
  118. Comments.emplace_back(
  119. Loc,
  120. Lexer::getSourceText(CharSourceRange::getCharRange(
  121. Loc, Loc.getLocWithOffset(Tok.getLength())),
  122. Ctx->getSourceManager(), Ctx->getLangOpts()));
  123. }
  124. return Comments;
  125. }
  126. static bool isLikelyTypo(llvm::ArrayRef<ParmVarDecl *> Params,
  127. StringRef ArgName, unsigned ArgIndex) {
  128. std::string ArgNameLowerStr = ArgName.lower();
  129. StringRef ArgNameLower = ArgNameLowerStr;
  130. // The threshold is arbitrary.
  131. unsigned UpperBound = (ArgName.size() + 2) / 3 + 1;
  132. unsigned ThisED = ArgNameLower.edit_distance(
  133. Params[ArgIndex]->getIdentifier()->getName().lower(),
  134. /*AllowReplacements=*/true, UpperBound);
  135. if (ThisED >= UpperBound)
  136. return false;
  137. for (unsigned I = 0, E = Params.size(); I != E; ++I) {
  138. if (I == ArgIndex)
  139. continue;
  140. IdentifierInfo *II = Params[I]->getIdentifier();
  141. if (!II)
  142. continue;
  143. const unsigned Threshold = 2;
  144. // Other parameters must be an edit distance at least Threshold more away
  145. // from this parameter. This gives us greater confidence that this is a
  146. // typo of this parameter and not one with a similar name.
  147. unsigned OtherED = ArgNameLower.edit_distance(II->getName().lower(),
  148. /*AllowReplacements=*/true,
  149. ThisED + Threshold);
  150. if (OtherED < ThisED + Threshold)
  151. return false;
  152. }
  153. return true;
  154. }
  155. static bool sameName(StringRef InComment, StringRef InDecl, bool StrictMode) {
  156. if (StrictMode)
  157. return InComment == InDecl;
  158. InComment = InComment.trim('_');
  159. InDecl = InDecl.trim('_');
  160. // FIXME: compare_insensitive only works for ASCII.
  161. return InComment.compare_insensitive(InDecl) == 0;
  162. }
  163. static bool looksLikeExpectMethod(const CXXMethodDecl *Expect) {
  164. return Expect != nullptr && Expect->getLocation().isMacroID() &&
  165. Expect->getNameInfo().getName().isIdentifier() &&
  166. Expect->getName().startswith("gmock_");
  167. }
  168. static bool areMockAndExpectMethods(const CXXMethodDecl *Mock,
  169. const CXXMethodDecl *Expect) {
  170. assert(looksLikeExpectMethod(Expect));
  171. return Mock != nullptr && Mock->getNextDeclInContext() == Expect &&
  172. Mock->getNumParams() == Expect->getNumParams() &&
  173. Mock->getLocation().isMacroID() &&
  174. Mock->getNameInfo().getName().isIdentifier() &&
  175. Mock->getName() == Expect->getName().substr(strlen("gmock_"));
  176. }
  177. // This uses implementation details of MOCK_METHODx_ macros: for each mocked
  178. // method M it defines M() with appropriate signature and a method used to set
  179. // up expectations - gmock_M() - with each argument's type changed the
  180. // corresponding matcher. This function returns M when given either M or
  181. // gmock_M.
  182. static const CXXMethodDecl *findMockedMethod(const CXXMethodDecl *Method) {
  183. if (looksLikeExpectMethod(Method)) {
  184. const DeclContext *Ctx = Method->getDeclContext();
  185. if (Ctx == nullptr || !Ctx->isRecord())
  186. return nullptr;
  187. for (const auto *D : Ctx->decls()) {
  188. if (D->getNextDeclInContext() == Method) {
  189. const auto *Previous = dyn_cast<CXXMethodDecl>(D);
  190. return areMockAndExpectMethods(Previous, Method) ? Previous : nullptr;
  191. }
  192. }
  193. return nullptr;
  194. }
  195. if (const auto *Next =
  196. dyn_cast_or_null<CXXMethodDecl>(Method->getNextDeclInContext())) {
  197. if (looksLikeExpectMethod(Next) && areMockAndExpectMethods(Method, Next))
  198. return Method;
  199. }
  200. return nullptr;
  201. }
  202. // For gmock expectation builder method (the target of the call generated by
  203. // `EXPECT_CALL(obj, Method(...))`) tries to find the real method being mocked
  204. // (returns nullptr, if the mock method doesn't override anything). For other
  205. // functions returns the function itself.
  206. static const FunctionDecl *resolveMocks(const FunctionDecl *Func) {
  207. if (const auto *Method = dyn_cast<CXXMethodDecl>(Func)) {
  208. if (const auto *MockedMethod = findMockedMethod(Method)) {
  209. // If mocked method overrides the real one, we can use its parameter
  210. // names, otherwise we're out of luck.
  211. if (MockedMethod->size_overridden_methods() > 0) {
  212. return *MockedMethod->begin_overridden_methods();
  213. }
  214. return nullptr;
  215. }
  216. }
  217. return Func;
  218. }
  219. // Given the argument type and the options determine if we should
  220. // be adding an argument comment.
  221. bool ArgumentCommentCheck::shouldAddComment(const Expr *Arg) const {
  222. Arg = Arg->IgnoreImpCasts();
  223. if (isa<UnaryOperator>(Arg))
  224. Arg = cast<UnaryOperator>(Arg)->getSubExpr();
  225. if (Arg->getExprLoc().isMacroID())
  226. return false;
  227. return (CommentBoolLiterals && isa<CXXBoolLiteralExpr>(Arg)) ||
  228. (CommentIntegerLiterals && isa<IntegerLiteral>(Arg)) ||
  229. (CommentFloatLiterals && isa<FloatingLiteral>(Arg)) ||
  230. (CommentUserDefinedLiterals && isa<UserDefinedLiteral>(Arg)) ||
  231. (CommentCharacterLiterals && isa<CharacterLiteral>(Arg)) ||
  232. (CommentStringLiterals && isa<StringLiteral>(Arg)) ||
  233. (CommentNullPtrs && isa<CXXNullPtrLiteralExpr>(Arg));
  234. }
  235. void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
  236. const FunctionDecl *OriginalCallee,
  237. SourceLocation ArgBeginLoc,
  238. llvm::ArrayRef<const Expr *> Args) {
  239. const FunctionDecl *Callee = resolveMocks(OriginalCallee);
  240. if (!Callee)
  241. return;
  242. Callee = Callee->getFirstDecl();
  243. unsigned NumArgs = std::min<unsigned>(Args.size(), Callee->getNumParams());
  244. if ((NumArgs == 0) || (IgnoreSingleArgument && NumArgs == 1))
  245. return;
  246. auto MakeFileCharRange = [Ctx](SourceLocation Begin, SourceLocation End) {
  247. return Lexer::makeFileCharRange(CharSourceRange::getCharRange(Begin, End),
  248. Ctx->getSourceManager(),
  249. Ctx->getLangOpts());
  250. };
  251. for (unsigned I = 0; I < NumArgs; ++I) {
  252. const ParmVarDecl *PVD = Callee->getParamDecl(I);
  253. IdentifierInfo *II = PVD->getIdentifier();
  254. if (!II)
  255. continue;
  256. if (FunctionDecl *Template = Callee->getTemplateInstantiationPattern()) {
  257. // Don't warn on arguments for parameters instantiated from template
  258. // parameter packs. If we find more arguments than the template
  259. // definition has, it also means that they correspond to a parameter
  260. // pack.
  261. if (Template->getNumParams() <= I ||
  262. Template->getParamDecl(I)->isParameterPack()) {
  263. continue;
  264. }
  265. }
  266. CharSourceRange BeforeArgument =
  267. MakeFileCharRange(ArgBeginLoc, Args[I]->getBeginLoc());
  268. ArgBeginLoc = Args[I]->getEndLoc();
  269. std::vector<std::pair<SourceLocation, StringRef>> Comments;
  270. if (BeforeArgument.isValid()) {
  271. Comments = getCommentsInRange(Ctx, BeforeArgument);
  272. } else {
  273. // Fall back to parsing back from the start of the argument.
  274. CharSourceRange ArgsRange =
  275. MakeFileCharRange(Args[I]->getBeginLoc(), Args[I]->getEndLoc());
  276. Comments = getCommentsBeforeLoc(Ctx, ArgsRange.getBegin());
  277. }
  278. for (auto Comment : Comments) {
  279. llvm::SmallVector<StringRef, 2> Matches;
  280. if (IdentRE.match(Comment.second, &Matches) &&
  281. !sameName(Matches[2], II->getName(), StrictMode)) {
  282. {
  283. DiagnosticBuilder Diag =
  284. diag(Comment.first, "argument name '%0' in comment does not "
  285. "match parameter name %1")
  286. << Matches[2] << II;
  287. if (isLikelyTypo(Callee->parameters(), Matches[2], I)) {
  288. Diag << FixItHint::CreateReplacement(
  289. Comment.first, (Matches[1] + II->getName() + Matches[3]).str());
  290. }
  291. }
  292. diag(PVD->getLocation(), "%0 declared here", DiagnosticIDs::Note) << II;
  293. if (OriginalCallee != Callee) {
  294. diag(OriginalCallee->getLocation(),
  295. "actual callee (%0) is declared here", DiagnosticIDs::Note)
  296. << OriginalCallee;
  297. }
  298. }
  299. }
  300. // If the argument comments are missing for literals add them.
  301. if (Comments.empty() && shouldAddComment(Args[I])) {
  302. std::string ArgComment =
  303. (llvm::Twine("/*") + II->getName() + "=*/").str();
  304. DiagnosticBuilder Diag =
  305. diag(Args[I]->getBeginLoc(),
  306. "argument comment missing for literal argument %0")
  307. << II
  308. << FixItHint::CreateInsertion(Args[I]->getBeginLoc(), ArgComment);
  309. }
  310. }
  311. }
  312. void ArgumentCommentCheck::check(const MatchFinder::MatchResult &Result) {
  313. const auto *E = Result.Nodes.getNodeAs<Expr>("expr");
  314. if (const auto *Call = dyn_cast<CallExpr>(E)) {
  315. const FunctionDecl *Callee = Call->getDirectCallee();
  316. if (!Callee)
  317. return;
  318. checkCallArgs(Result.Context, Callee, Call->getCallee()->getEndLoc(),
  319. llvm::ArrayRef(Call->getArgs(), Call->getNumArgs()));
  320. } else {
  321. const auto *Construct = cast<CXXConstructExpr>(E);
  322. if (Construct->getNumArgs() > 0 &&
  323. Construct->getArg(0)->getSourceRange() == Construct->getSourceRange()) {
  324. // Ignore implicit construction.
  325. return;
  326. }
  327. checkCallArgs(
  328. Result.Context, Construct->getConstructor(),
  329. Construct->getParenOrBraceRange().getBegin(),
  330. llvm::ArrayRef(Construct->getArgs(), Construct->getNumArgs()));
  331. }
  332. }
  333. } // namespace clang::tidy::bugprone