Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -95,10 +95,10 @@ def LLVM : Package<"llvm">; def LLVMAlpha : Package<"llvm">, ParentPackage; -// The APIModeling package is for checkers that model APIs and don't perform -// any diagnostics. These checkers are always turned on; this package is -// intended for API modeling that is not controlled by the target triple. -def APIModeling : Package<"apiModeling">, Hidden; +// The APIModeling package is for checkers that model APIs. These checkers are +// always turned on; this package is intended for API modeling that is not +// controlled by the target triple. +def APIModeling : Package<"apiModeling">; def GoogleAPIModeling : Package<"google">, ParentPackage, Hidden; def Debug : Package<"debug">, Hidden; @@ -274,6 +274,20 @@ let ParentPackage = APIModeling in { +def ReturnValueChecker : Checker<"ReturnValue">, + HelpText<"Model the guaranteed boolean return value of function calls">, + CheckerOptions<[ + CmdLineOption, + ]>, + Documentation; + def StdCLibraryFunctionsChecker : Checker<"StdCLibraryFunctions">, HelpText<"Improve modeling of the C standard library functions">, Documentation; Index: clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -83,6 +83,7 @@ RetainCountChecker/RetainCountDiagnostics.cpp ReturnPointerRangeChecker.cpp ReturnUndefChecker.cpp + ReturnValueChecker.cpp RunLoopAutoreleaseLeakChecker.cpp SimpleStreamChecker.cpp SmartPtrModeling.cpp Index: clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp =================================================================== --- /dev/null +++ clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp @@ -0,0 +1,141 @@ +//===- 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/Basic/IdentifierTable.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.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" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringSet.h" + + +using namespace clang; +using namespace ento; + +namespace { +struct CallTy { + bool TruthValue; + StringRef Name; + Optional Class; +}; +} + +namespace { +class ReturnValueChecker : public Checker { +public: + void checkPostCall(const CallEvent &CE, CheckerContext &C) const; + void setCalls(StringRef Calls); + +private: + SmallVector CallVector; +}; +} // namespace + +void ReturnValueChecker::checkPostCall(const CallEvent &CE, + CheckerContext &C) const { + // Match the calls. + const CallTy *Call = nullptr; + if (const IdentifierInfo *II = CE.getCalleeIdentifier()) { + for (const auto &CurrentCall : CallVector) { + // Match by call name. + if (!II->getName().equals_lower(CurrentCall.Name)) + continue; + + // We are in the mode when no class specified. + if (!CurrentCall.Class.hasValue()) { + Call = &CurrentCall; + break; + } + + // Match by class name. + if (Optional ClassName = CurrentCall.Class) + if (const auto *MD = dyn_cast(CE.getDecl())) + if (!MD->getParent()->getName().equals_lower(*ClassName)) + continue; + + Call = &CurrentCall; + break; + } + } + + if (!Call) + return; + + // Create a note. + const NoteTag *CallTag = C.getNoteTag([Call](BugReport &BR) -> std::string { + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + + Out << '\''; + if (Optional ClassName = Call->Class) + Out << *ClassName << "::"; + + Out << Call->Name << "' always return " + << (Call->TruthValue ? "true" : "false"); + + return Out.str(); + }); + + // Set the return value with the note. + ProgramStateRef State = C.getState(); + SVal RetV = CE.getReturnValue(); + Optional RetDV = RetV.getAs(); + + State = State->assume(*RetDV, Call->TruthValue); + C.addTransition(State, CallTag); +} + +void ReturnValueChecker::setCalls(StringRef Calls) { + if (Calls == "\"\"" || Calls.size() == 0) + return; + + SmallVector RawTupleVector; + Calls.split(RawTupleVector, ';'); + + for (const auto &RawTuple : RawTupleVector) { + SmallVector Tuple; + RawTuple.split(Tuple, ':'); + + CallTy Call; + Call.TruthValue = Tuple[0].trim().equals("true"); + Call.Name = Tuple[1].trim(); + Call.Class = Tuple.size() == 3 ? Tuple[2].trim() : Optional(); + + CallVector.push_back(Call); + } +} + +void ento::registerReturnValueChecker(CheckerManager &Mgr) { + ReturnValueChecker *RVC = Mgr.registerChecker(); + + // These are known in the LLVM project. + StringRef LLVMCalls = "true:printError:AsmParser;" + "true:Error:LLLexer;" + "true:Error:MCAsmParser;" + "true:error:MIParser"; + RVC->setCalls(LLVMCalls); + + StringRef OptionalCalls = + Mgr.getAnalyzerOptions().getCheckerStringOption(RVC, "Calls"); + RVC->setCalls(OptionalCalls); +} + +bool ento::shouldRegisterReturnValueChecker(const LangOptions &LO) { + return true; +} Index: clang/test/Analysis/analyzer-config.c =================================================================== --- clang/test/Analysis/analyzer-config.c +++ clang/test/Analysis/analyzer-config.c @@ -9,6 +9,7 @@ // CHECK-NEXT: alpha.clone.CloneChecker:ReportNormalClones = true // CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtExec = 0x04 // CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtRead = 0x01 +// CHECK-NEXT: apiModeling.ReturnValue:Calls = "" // CHECK-NEXT: avoid-suppressing-null-argument-paths = false // CHECK-NEXT: c++-allocator-inlining = true // CHECK-NEXT: c++-container-inlining = false @@ -88,4 +89,4 @@ // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 85 +// CHECK-NEXT: num-entries = 86 Index: clang/test/Analysis/return-value-guaranteed.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/return-value-guaranteed.cpp @@ -0,0 +1,82 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-checker=apiModeling.ReturnValue \ +// RUN: -analyzer-config apiModeling.ReturnValue:Calls="true:error" \ +// RUN: -verify %s +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-checker=apiModeling.ReturnValue \ +// RUN: -analyzer-config \ +// RUN: apiModeling.ReturnValue:Calls="true:error;false:error:ErrorHandler" \ +// RUN: -verify %s + +// FIXME: expected-no-diagnostics + +void clang_analyzer_eval(bool); +void clang_analyzer_warnIfReached(); + + +struct Foo { int Field; }; +bool error(); +bool problem(); +void doSomething(); + +// We predefine the return value of 'error()' to 'true' and we cannot take the +// false-branches where error would occur. +namespace test_calls { +bool parseFoo(Foo &F) { + if (problem()) + return error(); + + F.Field = 0; + return false; +} + +bool parseFile() { + clang_analyzer_eval(error() == true); // FIXME: xpected-warning{{TRUE}} + + Foo F; + if (parseFoo(F)) + return true; + + if (F.Field == 0) { + // no-warning: "The left operand of '==' is a garbage value" was here. + doSomething(); + } + + return false; +} +} // namespace test_calls + +namespace test_classes { +struct ErrorHandler { + static bool error(); +}; + +bool parseFoo(Foo &F) { + if (problem()) + return error(); + + F.Field = 0; + return ErrorHandler::error(); +} + +bool parseFile() { + Foo F; + if (parseFoo(F)) + return true; + + if (F.Field == 0) { + // no-warning: "The left operand of '==' is a garbage value" was here. + doSomething(); + } + + return false; +} +} // namespace test_classes + +void test_note() { + clang_analyzer_eval(error() == true); // FIXME: xpected-warning{{TRUE}} + if (error()) + (void)(1 / !error()); +}