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
29using namespace clang;
30using namespace ento;
31using llvm::formatv;
32
33namespace {
34class ReturnValueChecker : public Checker<check::PostCall> {
35public:
36 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
37
38private:
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
63static 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
73void 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
101void ento::registerReturnValueChecker(CheckerManager &Mgr) {
102 Mgr.registerChecker<ReturnValueChecker>();
103}
104
105bool ento::shouldRegisterReturnValueChecker(const CheckerManager &mgr) {
106 return true;
107}
108