================
@@ -0,0 +1,326 @@
+//===- NullTerminatedChecker.cpp - Check null_terminated params -*- 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 NullTerminatedChecker, which checks for arguments treated as
+// buffers that are expected to be null-terminated (ends with a zero-valued
+// element). A constant-size array is considered null-terminated if any of its
+// elements may be zero on the current path.
+//
+// Parameters are marked as expecting null-terminated buffers using:
+//   __attribute__((annotate("null_terminated")))
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/Attr.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/SmallBitVector.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+class NullTerminatedChecker : public Checker<check::PreCall> {
+public:
+  // TODO: region-store-max-binding-fanout defaults to 128, meaning a single
+  // bind only covers that many elements. The 1024 option here is only truly
+  // respected when the array is built by separate bind operations, e.g.,
+  // the case of straight-line writes:
+  //
+  // int a[500];
+  // a[0] = val;
+  // a[1] = val;
+  // ...
+  // a[499] = val;
+  int MaxArraySize = 1024;
+
+  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
+
+private:
+  const BugType BT{this, "Array not null-terminated", "API"};
+
+  /// Return true if the parameter has annotate("null_terminated").
+  static bool isNullTerminatedParam(const ParmVarDecl *Param);
+
+  /// Return true if any element in [0, \p ArraySize) can be zero.
+  bool mayContainZeroElement(ProgramStateRef State, SValBuilder &SVB,
+                             QualType EltTy, uint64_t ArraySize,
+                             const TypedValueRegion *Arr) const;
+};
+
+/// Return true if we can't prove \p Val is non-zero on the current path.
+bool mayBeZero(ProgramStateRef State, SVal Val) {
+  // Unknown or undefined: can't reason either way.
+  auto DV = Val.getAs<DefinedSVal>();
+  if (!DV)
+    return true;
+
+  // Try the fast lookup first (much cheaper than assuming condition for
+  // concrete values).
+  ConditionTruthVal IsZero = State->isNull(*DV);
+  if (IsZero.isConstrainedFalse())
+    return false;
+  if (IsZero.isConstrainedTrue())
+    return true;
+
+  // For an atomic symbol, the solver won't do any better than the preceding
+  // check, so we cannot prove anything further (hence it may be zero).
+  SymbolRef Sym = DV->getAsSymbol(/*IncludeBaseRegion=*/true);
+  if (Sym && isa<SymbolData>(Sym))
+    return true;
+
+  // Worst case: ask the solver if the value can be zero.
+  assert(!DV->isConstant() &&
+         "Constants should have been handled by the fast path");
+  return static_cast<bool>(State->assume(*DV, /*Assumption=*/false));
+}
+
+/// Load element \p Idx of the array \p Arr from the store. This is relatively
+/// expensive since we are essentially asking the analyzer to work out a value
+/// (rather than just read a preexisting binding), so this should be a last
+/// resort call.
+SVal loadElement(ProgramStateRef State, SValBuilder &SVB, QualType EltTy,
+                 uint64_t Idx, const TypedValueRegion *Arr) {
+  SVal EltAddr =
+      State->getLValue(EltTy, SVB.makeArrayIndex(Idx), loc::MemRegionVal(Arr));
+  if (auto EltLoc = EltAddr.getAs<Loc>())
+    return State->getSVal(*EltLoc);
+  return UnknownVal();
+}
+
+/// Map the direct bindings of a memory cluster onto the elements of one array,
+/// looking for an element that may be zero.
+class ElementBindingScanner : public StoreManager::ClusterBindingsHandler {
+  ProgramStateRef State;
+  ASTContext &Ctx;
+  /// The region the offset of the array is relative to.
+  const MemRegion *OffsetRegion;
+  /// Offset of the array within \c OffsetRegion, in bits.
+  uint64_t ArrOffset;
+  uint64_t EltBits;
+
+  /// Elements that have a direct binding.
+  llvm::SmallBitVector Covered;
+  /// A binding overlaps an element without us knowing which part of it.
+  bool Imprecise = false;
+  /// An element that has a direct binding may be zero.
+  bool FoundPossibleZero = false;
+
+public:
+  ElementBindingScanner(ProgramStateRef State, ASTContext &Ctx,
+                        RegionOffset ArrOffset, uint64_t EltBits,
+                        uint64_t ArraySize)
+      : State(State), Ctx(Ctx), OffsetRegion(ArrOffset.getRegion()),
+        ArrOffset(ArrOffset.getOffset()), EltBits(EltBits), Covered(ArraySize) 
{
+  }
+
+  bool foundPossibleZero() const { return FoundPossibleZero; }
+  bool isImprecise() const { return Imprecise; }
+  bool hasElementWithoutBinding() const { return !Covered.all(); }
+  bool hasBinding(uint64_t Idx) const { return Covered[Idx]; }
+
+  /// Check a binding to see if we can reason about elements in the array.
+  /// Return true if we should keep checking/iterating over the rest of the
+  /// bindings in the cluster.
+  bool handleBinding(StoreManager &, Store, const MemRegion *Region,
+                     std::optional<uint64_t> BitOffset,
+                     StoreManager::BindingKind Kind, SVal Val) override {
+    // Skip default bindings: we can't tell which elements this applies to,
+    // which will end up getting loaded later on, so safe to skip.
+    if (Kind == StoreManager::BindingKind::Default)
+      return true;
+
+    // Skip symbolic offsets: we can't map the location to an index. But the
+    // write would drop existing overlapping concrete bindings anyway, which
+    // would then load as unknown, so skipping is sound. Bailing entirely
+    // (returning false) would skip bindings for sibling objects (e.g., writes
+    // to `s.b[i]` should not make `s.a` unknown), so we skip.
+    if (!BitOffset)
+      return true;
+
+    // Skip bindings not measured from the array's own offset because the
+    // offsets aren't comparable.
+    if (Region != OffsetRegion)
+      return true;
+
+    // The binding belongs to another object within the cluster, so there's
+    // nothing to learn here.
+    if (*BitOffset < ArrOffset)
+      return true; // Precedes the array
+    uint64_t Rel = *BitOffset - ArrOffset;
+    uint64_t Idx = Rel / EltBits;
+    if (Idx >= Covered.size())
+      return true; // Follows the array
+
+    // Bail when the offset is either not element-aligned, or when the width
+    // doesn't match element size (e.g., writing a char into an int array on
+    // x86).
+    if (Rel % EltBits != 0 || getBindingWidth(Val) != EltBits) {
+      Imprecise = true;
+      return false;
+    }
+
+    Covered.set(Idx);
+    if (mayBeZero(State, Val)) {
+      FoundPossibleZero = true;
+      return false;
+    }
+    return true;
+  }
+
+private:
+  /// Return the number of bits the value of a binding occupies, if known.
+  std::optional<uint64_t> getBindingWidth(SVal Val) const {
+    QualType T = Val.getType(Ctx);
+    if (T.isNull() || T->isFunctionType() || T->isIncompleteType())
+      return std::nullopt;
+    return Ctx.getTypeSize(T);
+  }
+};
+} // namespace
+
+bool NullTerminatedChecker::isNullTerminatedParam(const ParmVarDecl *Param) {
+  return llvm::any_of(Param->specific_attrs<AnnotateAttr>(),
+                      [](const AnnotateAttr *Ann) {
+                        return Ann->getAnnotation() == "null_terminated";
+                      });
+}
+
+bool NullTerminatedChecker::mayContainZeroElement(
+    ProgramStateRef State, SValBuilder &SVB, QualType EltTy, uint64_t 
ArraySize,
+    const TypedValueRegion *Arr) const {
+  ASTContext &Ctx = State->getStateManager().getContext();
+
+  // Bindings are keyed by an offset from the base region of the cluster, so we
+  // need the offset of the array itself to map them onto its elements.
+  RegionOffset Offset = Arr->getAsOffset();
+  if (!Offset.getRegion() || Offset.hasSymbolicOffset())
+    return true;
+  uint64_t EltBits = Ctx.getTypeSize(EltTy);
+  if (EltBits == 0)
+    return true;
+
+  ElementBindingScanner Scanner(State, Ctx, Offset, EltBits, ArraySize);
+  State->getStateManager().getStoreManager().iterClusterBindings(
+      State->getStore(), Offset.getRegion()->getBaseRegion(), Scanner);
+
+  if (Scanner.foundPossibleZero() || Scanner.isImprecise())
+    return true;
+  if (!Scanner.hasElementWithoutBinding())
+    return false;
+
+  // The remaining elements are either covered by a default binding or
+  // uninitialized. Iterate backwards, starting at the end of the array, since
+  // terminators are usually closer to the end.
+  for (uint64_t I = ArraySize; I-- > 0;) {
+    if (Scanner.hasBinding(I))
+      continue;
+    if (mayBeZero(State, loadElement(State, SVB, EltTy, I, Arr)))
+      return true;
+  }
+  return false;
+}
+
+void NullTerminatedChecker::checkPreCall(const CallEvent &Call,
+                                         CheckerContext &C) const {
+  const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
+  if (!FD)
+    return;
+
+  ProgramStateRef State = C.getState();
+  SValBuilder &SVB = C.getSValBuilder();
+  ASTContext &Ctx = C.getASTContext();
+
+  unsigned NumParams = FD->getNumParams();
+  unsigned NumArgs = Call.getNumArgs();
+
+  // The call to min handles the case when |NumParams| != |NumArgs|.
+  for (unsigned I = 0, N = std::min(NumParams, NumArgs); I < N; ++I) {
+    const ParmVarDecl *Param = FD->getParamDecl(I);
+    if (!isNullTerminatedParam(Param))
+      continue;
+
+    SVal ArgVal = Call.getArgSVal(I);
+    const MemRegion *R = ArgVal.getAsRegion();
+    if (!R)
+      continue;
+
+    // Strip ElementRegion wrappers (array-to-pointer decay produces
+    // &Element{Array, 0}).
+    R = R->StripCasts();
+    if (const auto *ER = dyn_cast<ElementRegion>(R))
+      R = ER->getSuperRegion();
----------------
NagyDonat wrote:

```suggestion
    // Strip ElementRegion wrappers (array-to-pointer decay produces
    // &Element{Array, 0}).
    R = R->StripCasts();
```
If you check out the source code of the method `StripCasts`, you will see that 
it does exactly the "strip ElementRegion wrappers" logic that you describe in 
the source code (especially if you pass `/*StripBaseAndDerivedCasts =*/ false` 
overriding the optional value of the default argument).

There is absolutely no need to manually strip another element region layer 
after this (without even checking whether it is a cast-like wrapper or an 
`ElementRegion` that encodes a nonzero offset).

https://github.com/llvm/llvm-project/pull/188128
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to