| //===- ReturnValueChecker - Applies guaranteed return values ----*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This defines ReturnValueChecker, which checks for calls with guaranteed |
| // boolean return value. It ensures the return value of each function call. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| #include "clang/StaticAnalyzer/Core/Checker.h" |
| #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
| #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
| #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| #include "llvm/ADT/Optional.h" |
| #include "llvm/ADT/SmallVector.h" |
| |
| using namespace clang; |
| using namespace ento; |
| |
| namespace { |
| class ReturnValueChecker : public Checker<check::PostCall, check::EndFunction> { |
| public: |
| // It sets the predefined invariant ('CDM') if the current call not break it. |
| void checkPostCall(const CallEvent &Call, CheckerContext &C) const; |
| |
| // It reports whether a predefined invariant ('CDM') is broken. |
| void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; |
| |
| private: |
| // The pairs are in the following form: {{{class, call}}, return value} |
| const CallDescriptionMap<bool> CDM = { |
| // These are known in the LLVM project: 'Error()' |
| {{{"ARMAsmParser", "Error"}}, true}, |
| {{{"HexagonAsmParser", "Error"}}, true}, |
| {{{"LLLexer", "Error"}}, true}, |
| {{{"LLParser", "Error"}}, true}, |
| {{{"MCAsmParser", "Error"}}, true}, |
| {{{"MCAsmParserExtension", "Error"}}, true}, |
| {{{"TGParser", "Error"}}, true}, |
| {{{"X86AsmParser", "Error"}}, true}, |
| // 'TokError()' |
| {{{"LLParser", "TokError"}}, true}, |
| {{{"MCAsmParser", "TokError"}}, true}, |
| {{{"MCAsmParserExtension", "TokError"}}, true}, |
| {{{"TGParser", "TokError"}}, true}, |
| // 'error()' |
| {{{"MIParser", "error"}}, true}, |
| {{{"WasmAsmParser", "error"}}, true}, |
| {{{"WebAssemblyAsmParser", "error"}}, true}, |
| // Other |
| {{{"AsmParser", "printError"}}, true}}; |
| }; |
| } // namespace |
| |
| static std::string getName(const CallEvent &Call) { |
| std::string Name = ""; |
| if (const auto *MD = dyn_cast<CXXMethodDecl>(Call.getDecl())) |
| if (const CXXRecordDecl *RD = MD->getParent()) |
| Name += RD->getNameAsString() + "::"; |
| |
| Name += Call.getCalleeIdentifier()->getName(); |
| return Name; |
| } |
| |
| // The predefinitions ('CDM') could break due to the ever growing code base. |
| // Check for the expected invariants and see whether they apply. |
| static Optional<bool> isInvariantBreak(bool ExpectedValue, SVal ReturnV, |
| CheckerContext &C) { |
| auto ReturnDV = ReturnV.getAs<DefinedOrUnknownSVal>(); |
| if (!ReturnDV) |
| return None; |
| |
| if (ExpectedValue) |
| return C.getState()->isNull(*ReturnDV).isConstrainedTrue(); |
| |
| return C.getState()->isNull(*ReturnDV).isConstrainedFalse(); |
| } |
| |
| void ReturnValueChecker::checkPostCall(const CallEvent &Call, |
| CheckerContext &C) const { |
| const bool *RawExpectedValue = CDM.lookup(Call); |
| if (!RawExpectedValue) |
| return; |
| |
| SVal ReturnV = Call.getReturnValue(); |
| bool ExpectedValue = *RawExpectedValue; |
| Optional<bool> IsInvariantBreak = isInvariantBreak(ExpectedValue, ReturnV, C); |
| if (!IsInvariantBreak) |
| return; |
| |
| // If the invariant is broken it is reported by 'checkEndFunction()'. |
| if (*IsInvariantBreak) |
| return; |
| |
| std::string Name = getName(Call); |
| const NoteTag *CallTag = C.getNoteTag( |
| [Name, ExpectedValue](BugReport &) -> std::string { |
| SmallString<128> Msg; |
| llvm::raw_svector_ostream Out(Msg); |
| |
| Out << '\'' << Name << "' returns " |
| << (ExpectedValue ? "true" : "false"); |
| return Out.str(); |
| }, |
| /*IsPrunable=*/true); |
| |
| ProgramStateRef State = C.getState(); |
| State = State->assume(ReturnV.castAs<DefinedOrUnknownSVal>(), ExpectedValue); |
| C.addTransition(State, CallTag); |
| } |
| |
| void ReturnValueChecker::checkEndFunction(const ReturnStmt *RS, |
| CheckerContext &C) const { |
| if (!RS || !RS->getRetValue()) |
| return; |
| |
| // We cannot get the caller in the top-frame. |
| const StackFrameContext *SFC = C.getStackFrame(); |
| if (C.getStackFrame()->inTopFrame()) |
| return; |
| |
| ProgramStateRef State = C.getState(); |
| CallEventManager &CMgr = C.getStateManager().getCallEventManager(); |
| CallEventRef<> Call = CMgr.getCaller(SFC, State); |
| if (!Call) |
| return; |
| |
| const bool *RawExpectedValue = CDM.lookup(*Call); |
| if (!RawExpectedValue) |
| return; |
| |
| SVal ReturnV = State->getSVal(RS->getRetValue(), C.getLocationContext()); |
| bool ExpectedValue = *RawExpectedValue; |
| Optional<bool> IsInvariantBreak = isInvariantBreak(ExpectedValue, ReturnV, C); |
| if (!IsInvariantBreak) |
| return; |
| |
| // If the invariant is appropriate it is reported by 'checkPostCall()'. |
| if (!*IsInvariantBreak) |
| return; |
| |
| std::string Name = getName(*Call); |
| const NoteTag *CallTag = C.getNoteTag( |
| [Name, ExpectedValue](BugReport &BR) -> std::string { |
| SmallString<128> Msg; |
| llvm::raw_svector_ostream Out(Msg); |
| |
| // The following is swapped because the invariant is broken. |
| Out << '\'' << Name << "' returns " |
| << (ExpectedValue ? "false" : "true"); |
| |
| return Out.str(); |
| }, |
| /*IsPrunable=*/false); |
| |
| C.addTransition(State, CallTag); |
| } |
| |
| void ento::registerReturnValueChecker(CheckerManager &Mgr) { |
| Mgr.registerChecker<ReturnValueChecker>(); |
| } |
| |
| bool ento::shouldRegisterReturnValueChecker(const LangOptions &LO) { |
| return true; |
| } |