diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -348,6 +348,10 @@ let ParentPackage = APIModeling in { +def ErrnoChecker : Checker<"Errno">, + HelpText<"Make the special value 'errno' available to other checkers.">, + Documentation; + def StdCLibraryFunctionsChecker : Checker<"StdCLibraryFunctions">, HelpText<"Improve modeling of the C standard library functions">, // Uninitialized value check is a mandatory dependency. This Checker asserts @@ -1556,6 +1560,12 @@ "purposes.">, Documentation; +/// Same as above (see StreamTesterChecker). +/// Make sure that ErrnoChecker is also enabled. +def ErrnoTesterChecker : Checker<"ErrnoTest">, + HelpText<"Check modeling aspects of 'errno'.">, + Documentation; + def ExprInspectionChecker : Checker<"ExprInspection">, HelpText<"Check the analyzer's understanding of expressions">, Documentation; diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h @@ -764,7 +764,8 @@ assert(s->getType()->isAnyPointerType() || s->getType()->isReferenceType() || s->getType()->isBlockPointerType()); - assert(isa(sreg) || isa(sreg)); + assert(isa(sreg) || isa(sreg) || + isa(sreg)); } public: @@ -1375,7 +1376,8 @@ const LocationContext *LC); /// Retrieve or create a "symbolic" memory region. - const SymbolicRegion* getSymbolicRegion(SymbolRef Sym); + const SymbolicRegion * + getSymbolicRegion(SymbolRef Sym, const MemSpaceRegion *MemSpace = nullptr); /// Return a unique symbolic region belonging to heap memory space. const SymbolicRegion *getSymbolicHeapRegion(SymbolRef sym); diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -40,6 +40,7 @@ DynamicTypePropagation.cpp DynamicTypeChecker.cpp EnumCastOutOfRangeChecker.cpp + ErrnoChecker.cpp ExprInspectionChecker.cpp FixedAddressChecker.cpp FuchsiaHandleChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/Errno.h b/clang/lib/StaticAnalyzer/Checkers/Errno.h new file mode 100644 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/Errno.h @@ -0,0 +1,46 @@ +//=== Errno.h - Tracking value of 'errno'. -------------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Defines inter-checker API for using the system value 'errno'. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ERRNO_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ERRNO_H + +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" + +namespace clang { +namespace ento { +namespace errno_check { + +/// Returns if modeling of 'errno' is available. +/// If not, the other functions here should not be used. +bool isErrnoAvailable(ProgramStateRef State); + +/// Returns the value of 'errno'. +/// Use only if 'isErrnoAvailable' is true. +SVal getErrnoValue(ProgramStateRef State); + +/// Set value of 'errno' to any SVal. +/// Use only if 'isErrnoAvailable' is true. +ProgramStateRef setErrnoValue(ProgramStateRef State, + const LocationContext *LCtx, SVal Value); + +/// Set value of 'errno' to a concrete (signed) integer. +/// Use only if 'isErrnoAvailable' is true. +ProgramStateRef setErrnoValue(ProgramStateRef State, CheckerContext &C, + uint64_t Value); + +} // namespace errno_check +} // namespace ento +} // namespace clang + +#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ERRNO_H diff --git a/clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp @@ -0,0 +1,236 @@ +//=== ErrnoChecker.cpp ------------------------------------------*- 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 ErrnoChecker, which is used to make the system value 'errno' +// available to other checkers. +// +//===----------------------------------------------------------------------===// + +#include "Errno.h" +#include "clang/AST/ParentMapContext.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/ADT/STLExtras.h" + +using namespace clang; +using namespace ento; + +namespace { + +// Name of the "errno" variable. +// FIXME: Is there a system where it is not called "errno" but is a variable? +const char *ErrnoVarName = "errno"; +// Names of functions that return a location of the "errno" value. +// FIXME: Check if there are other similar function names. +llvm::StringRef ErrnoLocationFuncNames[] = {"__errno_location"}; + +class ErrnoChecker + : public Checker { +public: + void checkBeginFunction(CheckerContext &C) const; + void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + + bool TestMode = false; + +private: + CallDescriptionSet ErrnoLocationCalls{{"__errno_location", 0, 0}}; + CallDescription TestCall = {"ErrnoTesterChecker_set_errno", 1}; +}; +} // namespace + +/// Store a MemRegion that contains the 'errno' integer value. +/// The value is null if the 'errno' value was not recognized in the AST. +REGISTER_TRAIT_WITH_PROGRAMSTATE(ErrnoRegion, const void *) + +namespace { + +/// An internal function accessing the errno region. +/// Returns null if there isn't any associated memory region. +inline const MemRegion *getErrnoRegion(ProgramStateRef State) { + return reinterpret_cast(State->get()); +} + +} // namespace + +void ErrnoChecker::checkBeginFunction(CheckerContext &C) const { + if (!C.inTopFrame()) + return; + + ASTContext &ACtx = C.getASTContext(); + + auto GetErrnoVar = [&ACtx]() -> const VarDecl * { + IdentifierInfo &II = ACtx.Idents.get(ErrnoVarName); + auto LookupRes = ACtx.getTranslationUnitDecl()->lookup(&II); + auto Found = llvm::find_if(LookupRes, [&ACtx](const Decl *D) { + if (auto *VD = dyn_cast(D)) + return ACtx.getSourceManager().isInSystemHeader(VD->getLocation()) && + VD->hasExternalStorage() && + VD->getType().getCanonicalType() == ACtx.IntTy; + return false; + }); + if (Found == LookupRes.end()) + return nullptr; + + return cast(*Found); + }; + + auto GetErrnoFunc = [&ACtx]() -> const FunctionDecl * { + SmallVector LookupRes; + for (StringRef ErrnoName : ErrnoLocationFuncNames) { + IdentifierInfo &II = ACtx.Idents.get(ErrnoName); + llvm::append_range(LookupRes, ACtx.getTranslationUnitDecl()->lookup(&II)); + } + + auto Found = llvm::find_if(LookupRes, [&ACtx](const Decl *D) { + if (auto *FD = dyn_cast(D)) + return ACtx.getSourceManager().isInSystemHeader(FD->getLocation()) && + FD->isExternC() && FD->getNumParams() == 0 && + FD->getReturnType() == ACtx.getPointerType(ACtx.IntTy); + return false; + }); + if (Found == LookupRes.end()) + return nullptr; + + return cast(*Found); + }; + + ProgramStateRef State = C.getState(); + + if (const VarDecl *ErrnoVar = GetErrnoVar()) { + // There is an external 'errno' variable. + // Use its memory region. + // The memory region for an 'errno'-like variable is allocated in system + // space by MemRegionManager. + const MemRegion *ErrnoR = + State->getRegion(ErrnoVar, C.getLocationContext()); + assert(ErrnoR && "Memory region should exist for the 'errno' variable."); + State = State->set(reinterpret_cast(ErrnoR)); + } else if (GetErrnoFunc()) { + // There is a function that returns the location of 'errno'. + // We must create a memory region for it in system space. + // Currently a symbolic region is used with an artifical symbol. + // FIXME: It is better to have a custom (new) kind of MemRegion for such + // cases. + SValBuilder &SVB = C.getSValBuilder(); + MemRegionManager &RMgr = C.getStateManager().getRegionManager(); + + const MemSpaceRegion *GlobalSystemSpace = + RMgr.getGlobalsRegion(MemRegion::GlobalSystemSpaceRegionKind); + + // Create an artifical symbol for the region. + // It is not possible to associate a statement or expression in this case. + const SymbolConjured *Sym = + SVB.conjureSymbol(nullptr, C.getLocationContext(), ACtx.IntTy, 1); + + // The symbolic region is untyped, create a typed sub-region in it. + // The ElementRegion is used to make the errno region a typed region. + const MemRegion *ErrnoR = RMgr.getElementRegion( + ACtx.IntTy, SVB.makeZeroArrayIndex(), + RMgr.getSymbolicRegion(Sym, GlobalSystemSpace), C.getASTContext()); + State = State->set(ErrnoR); + } else { + return; + } + + // Errno is initialized to 0 at program start. + State = errno_check::setErrnoValue(State, C, 0); + + C.addTransition(State); +} + +bool ErrnoChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { + if (ErrnoLocationCalls.contains(Call)) { + ProgramStateRef State = C.getState(); + + const MemRegion *ErrnoR = getErrnoRegion(State); + if (!ErrnoR) + return false; + + // The function returns the location of the 'errno' value (a pointer to it). + State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), + loc::MemRegionVal{ErrnoR}); + C.addTransition(State); + + return true; + } + + if (TestMode && TestCall.matches(Call)) { + C.addTransition(errno_check::setErrnoValue( + C.getState(), C.getLocationContext(), Call.getArgSVal(0))); + return true; + } + + return false; +} + +void ErrnoChecker::checkLiveSymbols(ProgramStateRef State, + SymbolReaper &SR) const { + // The special errno region should never garbage collected. + const auto *ErrnoR = getErrnoRegion(State); + if (ErrnoR) + SR.markLive(ErrnoR); +} + +namespace clang { +namespace ento { +namespace errno_check { + +bool isErrnoAvailable(ProgramStateRef State) { + return State->get(); +} + +SVal getErrnoValue(ProgramStateRef State) { + const MemRegion *ErrnoR = getErrnoRegion(State); + assert(ErrnoR && "No 'errno' available."); + QualType IntTy = State->getAnalysisManager().getASTContext().IntTy; + return State->getSVal(ErrnoR, IntTy); +} + +ProgramStateRef setErrnoValue(ProgramStateRef State, + const LocationContext *LCtx, SVal Value) { + const MemRegion *ErrnoR = getErrnoRegion(State); + assert(ErrnoR && "No 'errno' available."); + return State->bindLoc(loc::MemRegionVal{ErrnoR}, Value, LCtx); +} + +ProgramStateRef setErrnoValue(ProgramStateRef State, CheckerContext &C, + uint64_t Value) { + const MemRegion *ErrnoR = getErrnoRegion(State); + assert(ErrnoR && "No 'errno' available."); + return State->bindLoc( + loc::MemRegionVal{ErrnoR}, + C.getSValBuilder().makeIntVal(Value, C.getASTContext().IntTy), + C.getLocationContext()); +} + +} // namespace errno_check +} // namespace ento +} // namespace clang + +void ento::registerErrnoChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterErrnoChecker(const CheckerManager &mgr) { + return true; +} + +void ento::registerErrnoTesterChecker(CheckerManager &Mgr) { + auto *Checker = Mgr.getChecker(); + Checker->TestMode = true; +} + +bool ento::shouldRegisterErrnoTesterChecker(const CheckerManager &Mgr) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp --- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -1154,8 +1154,12 @@ } /// getSymbolicRegion - Retrieve or create a "symbolic" memory region. -const SymbolicRegion *MemRegionManager::getSymbolicRegion(SymbolRef sym) { - return getSubRegion(sym, getUnknownRegion()); +const SymbolicRegion * +MemRegionManager::getSymbolicRegion(SymbolRef sym, + const MemSpaceRegion *MemSpace) { + if (MemSpace == nullptr) + MemSpace = getUnknownRegion(); + return getSubRegion(sym, MemSpace); } const SymbolicRegion *MemRegionManager::getSymbolicHeapRegion(SymbolRef Sym) { diff --git a/clang/test/Analysis/Inputs/errno_func.h b/clang/test/Analysis/Inputs/errno_func.h new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/Inputs/errno_func.h @@ -0,0 +1,5 @@ +#pragma clang system_header + +// Define 'errno' as a macro that calls a function. +int *__errno_location(); +#define errno (*__errno_location()) diff --git a/clang/test/Analysis/Inputs/errno_var.h b/clang/test/Analysis/Inputs/errno_var.h new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/Inputs/errno_var.h @@ -0,0 +1,5 @@ +#pragma clang system_header + +// Define 'errno' as an extern variable in a system header. +// This may be not allowed in C99. +extern int errno; diff --git a/clang/test/Analysis/Inputs/system-header-simulator.h b/clang/test/Analysis/Inputs/system-header-simulator.h --- a/clang/test/Analysis/Inputs/system-header-simulator.h +++ b/clang/test/Analysis/Inputs/system-header-simulator.h @@ -59,9 +59,6 @@ int ferror(FILE *stream); int fileno(FILE *stream); -// Note, on some platforms errno macro gets replaced with a function call. -extern int errno; - size_t strlen(const char *); char *strcpy(char *restrict, const char *restrict); diff --git a/clang/test/Analysis/analyzer-enabled-checkers.c b/clang/test/Analysis/analyzer-enabled-checkers.c --- a/clang/test/Analysis/analyzer-enabled-checkers.c +++ b/clang/test/Analysis/analyzer-enabled-checkers.c @@ -7,6 +7,7 @@ // CHECK: OVERVIEW: Clang Static Analyzer Enabled Checkers List // CHECK-EMPTY: // CHECK-NEXT: core.CallAndMessageModeling +// CHECK-NEXT: apiModeling.Errno // CHECK-NEXT: apiModeling.StdCLibraryFunctions // CHECK-NEXT: apiModeling.TrustNonnull // CHECK-NEXT: apiModeling.TrustReturnsNonnull diff --git a/clang/test/Analysis/errno.c b/clang/test/Analysis/errno.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/errno.c @@ -0,0 +1,26 @@ +// RUN: %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=apiModeling.Errno \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -analyzer-checker=debug.ErrnoTest + +#include "Inputs/errno_var.h" +#include "Inputs/system-header-simulator.h" + +void clang_analyzer_eval(int); +void ErrnoTesterChecker_set_errno(int); + +void test() { + // Test if errno is initialized. + clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}} + + ErrnoTesterChecker_set_errno(1); + + // Test if errno was recognized and changed. + clang_analyzer_eval(errno == 1); // expected-warning{{TRUE}} + + FILE *F = fopen("/a/b", "r"); + + // Test if errno was invalidated. + clang_analyzer_eval(errno); // expected-warning{{UNKNOWN}} +} diff --git a/clang/test/Analysis/global-region-invalidation.c b/clang/test/Analysis/global-region-invalidation.c --- a/clang/test/Analysis/global-region-invalidation.c +++ b/clang/test/Analysis/global-region-invalidation.c @@ -3,6 +3,7 @@ void clang_analyzer_eval(int); // Note, we do need to include headers here, since the analyzer checks if the function declaration is located in a system header. +#include "Inputs/errno_var.h" #include "Inputs/system-header-simulator.h" // Test that system header does not invalidate the internal global. diff --git a/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c b/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c --- a/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c +++ b/clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c @@ -21,6 +21,7 @@ // CHECK-NEXT: alpha.unix.Stream // CHECK-NEXT: apiModeling.StdCLibraryFunctions // CHECK-NEXT: alpha.unix.StdCLibraryFunctionArgs +// CHECK-NEXT: apiModeling.Errno // CHECK-NEXT: apiModeling.TrustNonnull // CHECK-NEXT: apiModeling.TrustReturnsNonnull // CHECK-NEXT: apiModeling.llvm.CastValue