1 | //===--- StaticAccessedThroughInstanceCheck.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 "StaticAccessedThroughInstanceCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
12 | #include "llvm/ADT/StringRef.h" |
13 | |
14 | using namespace clang::ast_matchers; |
15 | |
16 | namespace clang::tidy::readability { |
17 | |
18 | namespace { |
19 | AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } |
20 | } // namespace |
21 | |
22 | static unsigned getNameSpecifierNestingLevel(const QualType &QType) { |
23 | if (const auto *ElType = QType->getAs<ElaboratedType>()) { |
24 | if (const NestedNameSpecifier *NestedSpecifiers = ElType->getQualifier()) { |
25 | unsigned NameSpecifierNestingLevel = 1; |
26 | do { |
27 | NameSpecifierNestingLevel++; |
28 | NestedSpecifiers = NestedSpecifiers->getPrefix(); |
29 | } while (NestedSpecifiers); |
30 | |
31 | return NameSpecifierNestingLevel; |
32 | } |
33 | } |
34 | return 0; |
35 | } |
36 | |
37 | void StaticAccessedThroughInstanceCheck::storeOptions( |
38 | ClangTidyOptions::OptionMap &Opts) { |
39 | Options.store(Options&: Opts, LocalName: "NameSpecifierNestingThreshold" , |
40 | Value: NameSpecifierNestingThreshold); |
41 | } |
42 | |
43 | void StaticAccessedThroughInstanceCheck::registerMatchers(MatchFinder *Finder) { |
44 | Finder->addMatcher( |
45 | NodeMatch: memberExpr(hasDeclaration(InnerMatcher: anyOf(cxxMethodDecl(isStatic()), |
46 | varDecl(hasStaticStorageDuration()), |
47 | enumConstantDecl()))) |
48 | .bind(ID: "memberExpression" ), |
49 | Action: this); |
50 | } |
51 | |
52 | void StaticAccessedThroughInstanceCheck::check( |
53 | const MatchFinder::MatchResult &Result) { |
54 | const auto *MemberExpression = |
55 | Result.Nodes.getNodeAs<MemberExpr>(ID: "memberExpression" ); |
56 | |
57 | if (MemberExpression->getBeginLoc().isMacroID()) |
58 | return; |
59 | |
60 | const Expr *BaseExpr = MemberExpression->getBase(); |
61 | |
62 | const QualType BaseType = |
63 | BaseExpr->getType()->isPointerType() |
64 | ? BaseExpr->getType()->getPointeeType().getUnqualifiedType() |
65 | : BaseExpr->getType().getUnqualifiedType(); |
66 | |
67 | const ASTContext *AstContext = Result.Context; |
68 | PrintingPolicy PrintingPolicyWithSuppressedTag(AstContext->getLangOpts()); |
69 | PrintingPolicyWithSuppressedTag.SuppressTagKeyword = true; |
70 | PrintingPolicyWithSuppressedTag.SuppressUnwrittenScope = true; |
71 | |
72 | PrintingPolicyWithSuppressedTag.PrintAsCanonical = |
73 | !BaseExpr->getType()->isTypedefNameType(); |
74 | |
75 | std::string BaseTypeName = |
76 | BaseType.getAsString(Policy: PrintingPolicyWithSuppressedTag); |
77 | |
78 | // Ignore anonymous structs/classes which will not have an identifier |
79 | const RecordDecl *RecDecl = BaseType->getAsCXXRecordDecl(); |
80 | if (!RecDecl || RecDecl->getIdentifier() == nullptr) |
81 | return; |
82 | |
83 | // Do not warn for CUDA built-in variables. |
84 | if (StringRef(BaseTypeName).starts_with(Prefix: "__cuda_builtin_" )) |
85 | return; |
86 | |
87 | SourceLocation MemberExprStartLoc = MemberExpression->getBeginLoc(); |
88 | auto CreateFix = [&] { |
89 | return FixItHint::CreateReplacement( |
90 | RemoveRange: CharSourceRange::getCharRange(B: MemberExprStartLoc, |
91 | E: MemberExpression->getMemberLoc()), |
92 | Code: BaseTypeName + "::" ); |
93 | }; |
94 | |
95 | { |
96 | auto Diag = |
97 | diag(Loc: MemberExprStartLoc, Description: "static member accessed through instance" ); |
98 | |
99 | if (getNameSpecifierNestingLevel(QType: BaseType) > NameSpecifierNestingThreshold) |
100 | return; |
101 | |
102 | if (!BaseExpr->HasSideEffects(Ctx: *AstContext, |
103 | /* IncludePossibleEffects =*/true)) { |
104 | Diag << CreateFix(); |
105 | return; |
106 | } |
107 | } |
108 | |
109 | diag(Loc: MemberExprStartLoc, Description: "member base expression may carry some side effects" , |
110 | Level: DiagnosticIDs::Level::Note) |
111 | << BaseExpr->getSourceRange() << CreateFix(); |
112 | } |
113 | |
114 | } // namespace clang::tidy::readability |
115 | |