xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
1 //===--- StringLiteralWithEmbeddedNulCheck.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 
9 #include "StringLiteralWithEmbeddedNulCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 
13 using namespace clang::ast_matchers;
14 
15 namespace clang::tidy::bugprone {
16 
17 namespace {
AST_MATCHER(StringLiteral,containsNul)18 AST_MATCHER(StringLiteral, containsNul) {
19   for (size_t I = 0; I < Node.getLength(); ++I)
20     if (Node.getCodeUnit(I) == '\0')
21       return true;
22   return false;
23 }
24 } // namespace
25 
registerMatchers(MatchFinder * Finder)26 void StringLiteralWithEmbeddedNulCheck::registerMatchers(MatchFinder *Finder) {
27   // Match a string that contains embedded NUL character. Extra-checks are
28   // applied in |check| to find incorrectly escaped characters.
29   Finder->addMatcher(stringLiteral(containsNul()).bind("strlit"), this);
30 
31   // The remaining checks only apply to C++.
32   if (!getLangOpts().CPlusPlus)
33     return;
34 
35   const auto StrLitWithNul =
36       ignoringParenImpCasts(stringLiteral(containsNul()).bind("truncated"));
37 
38   // Match string constructor.
39   const auto StringConstructorExpr = expr(anyOf(
40       cxxConstructExpr(argumentCountIs(1),
41                        hasDeclaration(cxxMethodDecl(hasName("basic_string")))),
42       // If present, the second argument is the alloc object which must not
43       // be present explicitly.
44       cxxConstructExpr(argumentCountIs(2),
45                        hasDeclaration(cxxMethodDecl(hasName("basic_string"))),
46                        hasArgument(1, cxxDefaultArgExpr()))));
47 
48   // Detect passing a suspicious string literal to a string constructor.
49   // example: std::string str = "abc\0def";
50   Finder->addMatcher(traverse(TK_AsIs,
51       cxxConstructExpr(StringConstructorExpr, hasArgument(0, StrLitWithNul))),
52       this);
53 
54   // Detect passing a suspicious string literal through an overloaded operator.
55   Finder->addMatcher(cxxOperatorCallExpr(hasAnyArgument(StrLitWithNul)), this);
56 }
57 
check(const MatchFinder::MatchResult & Result)58 void StringLiteralWithEmbeddedNulCheck::check(
59     const MatchFinder::MatchResult &Result) {
60   if (const auto *SL = Result.Nodes.getNodeAs<StringLiteral>("strlit")) {
61     for (size_t Offset = 0, Length = SL->getLength(); Offset < Length;
62          ++Offset) {
63       // Find a sequence of character like "\0x12".
64       if (Offset + 3 < Length && SL->getCodeUnit(Offset) == '\0' &&
65           SL->getCodeUnit(Offset + 1) == 'x' &&
66           isDigit(SL->getCodeUnit(Offset + 2)) &&
67           isDigit(SL->getCodeUnit(Offset + 3))) {
68         diag(SL->getBeginLoc(), "suspicious embedded NUL character");
69         return;
70       }
71     }
72   }
73 
74   if (const auto *SL = Result.Nodes.getNodeAs<StringLiteral>("truncated")) {
75     diag(SL->getBeginLoc(),
76          "truncated string literal with embedded NUL character");
77   }
78 }
79 
80 } // namespace clang::tidy::bugprone
81