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