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 { |
18 | AST_MATCHER(StringLiteral, containsNul) { |
19 | for (size_t I = 0; I < Node.getLength(); ++I) |
20 | if (Node.getCodeUnit(i: I) == '\0') |
21 | return true; |
22 | return false; |
23 | } |
24 | } // namespace |
25 | |
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(NodeMatch: stringLiteral(containsNul()).bind(ID: "strlit" ), Action: this); |
30 | |
31 | // The remaining checks only apply to C++. |
32 | if (!getLangOpts().CPlusPlus) |
33 | return; |
34 | |
35 | const auto StrLitWithNul = |
36 | ignoringParenImpCasts(InnerMatcher: stringLiteral(containsNul()).bind(ID: "truncated" )); |
37 | |
38 | // Match string constructor. |
39 | const auto StringConstructorExpr = expr(anyOf( |
40 | cxxConstructExpr(argumentCountIs(N: 1), |
41 | hasDeclaration(InnerMatcher: cxxMethodDecl(hasName(Name: "basic_string" )))), |
42 | // If present, the second argument is the alloc object which must not |
43 | // be present explicitly. |
44 | cxxConstructExpr(argumentCountIs(N: 2), |
45 | hasDeclaration(InnerMatcher: cxxMethodDecl(hasName(Name: "basic_string" ))), |
46 | hasArgument(N: 1, InnerMatcher: cxxDefaultArgExpr())))); |
47 | |
48 | // Detect passing a suspicious string literal to a string constructor. |
49 | // example: std::string str = "abc\0def"; |
50 | Finder->addMatcher(NodeMatch: traverse(TK: TK_AsIs, |
51 | InnerMatcher: cxxConstructExpr(StringConstructorExpr, hasArgument(N: 0, InnerMatcher: StrLitWithNul))), |
52 | Action: this); |
53 | |
54 | // Detect passing a suspicious string literal through an overloaded operator. |
55 | Finder->addMatcher(NodeMatch: cxxOperatorCallExpr(hasAnyArgument(InnerMatcher: StrLitWithNul)), Action: this); |
56 | } |
57 | |
58 | void StringLiteralWithEmbeddedNulCheck::check( |
59 | const MatchFinder::MatchResult &Result) { |
60 | if (const auto *SL = Result.Nodes.getNodeAs<StringLiteral>(ID: "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(i: Offset) == '\0' && |
65 | SL->getCodeUnit(i: Offset + 1) == 'x' && |
66 | isDigit(c: SL->getCodeUnit(i: Offset + 2)) && |
67 | isDigit(c: SL->getCodeUnit(i: Offset + 3))) { |
68 | diag(Loc: SL->getBeginLoc(), Description: "suspicious embedded NUL character" ); |
69 | return; |
70 | } |
71 | } |
72 | } |
73 | |
74 | if (const auto *SL = Result.Nodes.getNodeAs<StringLiteral>(ID: "truncated" )) { |
75 | diag(Loc: SL->getBeginLoc(), |
76 | Description: "truncated string literal with embedded NUL character" ); |
77 | } |
78 | } |
79 | |
80 | } // namespace clang::tidy::bugprone |
81 | |