balazske created this revision.
Herald added subscribers: steakhal, ASDenysPetrov, martong, gamesh411, dkrupp, 
donat.nagy, Szelethus, mikhail.ramalho, a.sidorin, szepet, baloghadamsoftware, 
xazax.hun, mgorny.
Herald added a reviewer: Szelethus.
balazske requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Add a checker to maintain the system-defined value 'errno'.
The value is supposed to be set in the future by existing or
new checkers that evaluate errno-modifying function calls.

The patch is work-in-progress because the possible definitions
of `errno` on various implementations should be collected.
Some tests (dependent on these) are missing yet.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D120310

Files:
  clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
  clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
  clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
  clang/lib/StaticAnalyzer/Checkers/Errno.h
  clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp
  clang/lib/StaticAnalyzer/Core/MemRegion.cpp
  clang/test/Analysis/Inputs/errno_func.h
  clang/test/Analysis/Inputs/errno_var.h
  clang/test/Analysis/Inputs/system-header-simulator.h
  clang/test/Analysis/analyzer-enabled-checkers.c
  clang/test/Analysis/errno.c
  clang/test/Analysis/global-region-invalidation.c
  clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c

Index: clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c
===================================================================
--- clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c
+++ 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
Index: clang/test/Analysis/global-region-invalidation.c
===================================================================
--- clang/test/Analysis/global-region-invalidation.c
+++ 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.
Index: clang/test/Analysis/errno.c
===================================================================
--- /dev/null
+++ 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}}
+}
Index: clang/test/Analysis/analyzer-enabled-checkers.c
===================================================================
--- clang/test/Analysis/analyzer-enabled-checkers.c
+++ 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
Index: clang/test/Analysis/Inputs/system-header-simulator.h
===================================================================
--- clang/test/Analysis/Inputs/system-header-simulator.h
+++ 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);
Index: clang/test/Analysis/Inputs/errno_var.h
===================================================================
--- /dev/null
+++ 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;
Index: clang/test/Analysis/Inputs/errno_func.h
===================================================================
--- /dev/null
+++ 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())
Index: clang/lib/StaticAnalyzer/Core/MemRegion.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Core/MemRegion.cpp
+++ 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<SymbolicRegion>(sym, getUnknownRegion());
+const SymbolicRegion *
+MemRegionManager::getSymbolicRegion(SymbolRef sym,
+                                    const MemSpaceRegion *MemSpace) {
+  if (MemSpace == nullptr)
+    MemSpace = getUnknownRegion();
+  return getSubRegion<SymbolicRegion>(sym, MemSpace);
 }
 
 const SymbolicRegion *MemRegionManager::getSymbolicHeapRegion(SymbolRef Sym) {
Index: clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp
===================================================================
--- /dev/null
+++ 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<check::BeginFunction, check::LiveSymbols, eval::Call> {
+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<const MemRegion *>(State->get<ErrnoRegion>());
+}
+
+} // 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<VarDecl>(D))
+        return ACtx.getSourceManager().isInSystemHeader(VD->getLocation()) &&
+               VD->hasExternalStorage() &&
+               VD->getType().getCanonicalType() == ACtx.IntTy;
+      return false;
+    });
+    if (Found == LookupRes.end())
+      return nullptr;
+
+    return cast<VarDecl>(*Found);
+  };
+
+  auto GetErrnoFunc = [&ACtx]() -> const FunctionDecl * {
+    SmallVector<const Decl *> 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<FunctionDecl>(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<FunctionDecl>(*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<ErrnoRegion>(reinterpret_cast<const void *>(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<ErrnoRegion>(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<ErrnoRegion>();
+}
+
+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<ErrnoChecker>();
+}
+
+bool ento::shouldRegisterErrnoChecker(const CheckerManager &mgr) {
+  return true;
+}
+
+void ento::registerErrnoTesterChecker(CheckerManager &Mgr) {
+  auto *Checker = Mgr.getChecker<ErrnoChecker>();
+  Checker->TestMode = true;
+}
+
+bool ento::shouldRegisterErrnoTesterChecker(const CheckerManager &Mgr) {
+  return true;
+}
Index: clang/lib/StaticAnalyzer/Checkers/Errno.h
===================================================================
--- /dev/null
+++ 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
Index: clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -40,6 +40,7 @@
   DynamicTypePropagation.cpp
   DynamicTypeChecker.cpp
   EnumCastOutOfRangeChecker.cpp
+  ErrnoChecker.cpp
   ExprInspectionChecker.cpp
   FixedAddressChecker.cpp
   FuchsiaHandleChecker.cpp
Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
===================================================================
--- clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
+++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h
@@ -764,7 +764,8 @@
     assert(s->getType()->isAnyPointerType() ||
            s->getType()->isReferenceType() ||
            s->getType()->isBlockPointerType());
-    assert(isa<UnknownSpaceRegion>(sreg) || isa<HeapSpaceRegion>(sreg));
+    assert(isa<UnknownSpaceRegion>(sreg) || isa<HeapSpaceRegion>(sreg) ||
+           isa<GlobalSystemSpaceRegion>(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);
Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ 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<HasDocumentation>;
+
 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<NotDocumented>;
 
+/// Same as above (see StreamTesterChecker).
+/// Make sure that ErrnoChecker is also enabled.
+def ErrnoTesterChecker : Checker<"ErrnoTest">,
+  HelpText<"Check modeling aspects of 'errno'.">,
+  Documentation<NotDocumented>;
+
 def ExprInspectionChecker : Checker<"ExprInspection">,
   HelpText<"Check the analyzer's understanding of expressions">,
   Documentation<NotDocumented>;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to