1 | //===- ReturnValueChecker - Check methods always returning true -*- C++ -*-===// |
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 | // This defines ReturnValueChecker, which models a very specific coding |
10 | // convention within the LLVM/Clang codebase: there several classes that have |
11 | // Error() methods which always return true. |
12 | // This checker was introduced to eliminate false positives caused by this |
13 | // peculiar "always returns true" invariant. (Normally, the analyzer assumes |
14 | // that a function returning `bool` can return both `true` and `false`, because |
15 | // otherwise it could've been a `void` function.) |
16 | // |
17 | //===----------------------------------------------------------------------===// |
18 | |
19 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
20 | #include "clang/StaticAnalyzer/Core/Checker.h" |
21 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
22 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" |
23 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
24 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
25 | #include "llvm/ADT/SmallVector.h" |
26 | #include "llvm/Support/FormatVariadic.h" |
27 | #include <optional> |
28 | |
29 | using namespace clang; |
30 | using namespace ento; |
31 | using llvm::formatv; |
32 | |
33 | namespace { |
34 | class ReturnValueChecker : public Checker<check::PostCall> { |
35 | public: |
36 | void checkPostCall(const CallEvent &Call, CheckerContext &C) const; |
37 | |
38 | private: |
39 | const CallDescriptionSet Methods = { |
40 | // These are known in the LLVM project: 'Error()' |
41 | {CDM::CXXMethod, {"ARMAsmParser" , "Error" }}, |
42 | {CDM::CXXMethod, {"HexagonAsmParser" , "Error" }}, |
43 | {CDM::CXXMethod, {"LLLexer" , "Error" }}, |
44 | {CDM::CXXMethod, {"LLParser" , "Error" }}, |
45 | {CDM::CXXMethod, {"MCAsmParser" , "Error" }}, |
46 | {CDM::CXXMethod, {"MCAsmParserExtension" , "Error" }}, |
47 | {CDM::CXXMethod, {"TGParser" , "Error" }}, |
48 | {CDM::CXXMethod, {"X86AsmParser" , "Error" }}, |
49 | // 'TokError()' |
50 | {CDM::CXXMethod, {"LLParser" , "TokError" }}, |
51 | {CDM::CXXMethod, {"MCAsmParser" , "TokError" }}, |
52 | {CDM::CXXMethod, {"MCAsmParserExtension" , "TokError" }}, |
53 | {CDM::CXXMethod, {"TGParser" , "TokError" }}, |
54 | // 'error()' |
55 | {CDM::CXXMethod, {"MIParser" , "error" }}, |
56 | {CDM::CXXMethod, {"WasmAsmParser" , "error" }}, |
57 | {CDM::CXXMethod, {"WebAssemblyAsmParser" , "error" }}, |
58 | // Other |
59 | {CDM::CXXMethod, {"AsmParser" , "printError" }}}; |
60 | }; |
61 | } // namespace |
62 | |
63 | static std::string getName(const CallEvent &Call) { |
64 | std::string Name; |
65 | if (const auto *MD = dyn_cast<CXXMethodDecl>(Val: Call.getDecl())) |
66 | if (const CXXRecordDecl *RD = MD->getParent()) |
67 | Name += RD->getNameAsString() + "::" ; |
68 | |
69 | Name += Call.getCalleeIdentifier()->getName(); |
70 | return Name; |
71 | } |
72 | |
73 | void ReturnValueChecker::checkPostCall(const CallEvent &Call, |
74 | CheckerContext &C) const { |
75 | if (!Methods.contains(Call)) |
76 | return; |
77 | |
78 | auto ReturnV = Call.getReturnValue().getAs<DefinedOrUnknownSVal>(); |
79 | |
80 | if (!ReturnV) |
81 | return; |
82 | |
83 | ProgramStateRef State = C.getState(); |
84 | if (ProgramStateRef StTrue = State->assume(Cond: *ReturnV, Assumption: true)) { |
85 | // The return value can be true, so transition to a state where it's true. |
86 | std::string Msg = |
87 | formatv(Fmt: "'{0}' returns true (by convention)" , Vals: getName(Call)); |
88 | C.addTransition(State: StTrue, Tag: C.getNoteTag(Note: Msg, /*IsPrunable=*/true)); |
89 | return; |
90 | } |
91 | // Paranoia: if the return value is known to be false (which is highly |
92 | // unlikely, it's easy to ensure that the method always returns true), then |
93 | // produce a note that highlights that this unusual situation. |
94 | // Note that this checker is 'hidden' so it cannot produce a bug report. |
95 | std::string Msg = formatv(Fmt: "'{0}' returned false, breaking the convention " |
96 | "that it always returns true" , |
97 | Vals: getName(Call)); |
98 | C.addTransition(State, Tag: C.getNoteTag(Note: Msg, /*IsPrunable=*/true)); |
99 | } |
100 | |
101 | void ento::registerReturnValueChecker(CheckerManager &Mgr) { |
102 | Mgr.registerChecker<ReturnValueChecker>(); |
103 | } |
104 | |
105 | bool ento::shouldRegisterReturnValueChecker(const CheckerManager &mgr) { |
106 | return true; |
107 | } |
108 | |