ASDenysPetrov created this revision.
ASDenysPetrov added reviewers: rsmith, martong, NoQ, vsavchenko, steakhal, 
aaron.ballman, xazax.hun, Szelethus.
ASDenysPetrov added a project: clang.
Herald added subscribers: manas, jeroen.dobbelaere, dkrupp, donat.nagy, 
mikhail.ramalho, a.sidorin, rnkovacs, szepet, baloghadamsoftware, mgorny.
ASDenysPetrov requested review of this revision.
Herald added a subscriber: cfe-commits.

`StrictAliasingChecker` implements checks on violation of the next paragraph of 
the Standard which is known as **Strict Aliasing Rule**. It operates on 
variable loads and stores. The checker compares the original type of the value 
with the type of the pointer with which the value is aliased.
The paragraph:

> C++20 7.2.1 p11 [basic.lval]:
>  If a program attempts to access the stored value of an object through a 
> glvalue whose type is not similar to one of the following types the behavior 
> is undefined:
>
> - the dynamic type of the object,
> - a type that is the signed or unsigned type corresponding to the dynamic 
> type of the object, or
> - a char, unsigned char, or std​::​byte type.

Example:

  int x = 42;
  auto c = *((char*)&x); // The original type is `int`. The aliased type is 
`char`. OK. 
  auto f = *((float*)&x); // The original type is `int`. The aliased type is 
`float`. UB. 


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D114718

Files:
  clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
  clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
  clang/lib/StaticAnalyzer/Checkers/StrictAliasingChecker.cpp
  clang/test/Analysis/Checkers/StrictAliasingChecker/strict-aliasing.cpp

Index: clang/test/Analysis/Checkers/StrictAliasingChecker/strict-aliasing.cpp
===================================================================
--- /dev/null
+++ clang/test/Analysis/Checkers/StrictAliasingChecker/strict-aliasing.cpp
@@ -0,0 +1,160 @@
+// RUN: %clang_cc1 -analyze -analyzer-config eagerly-assume=false -analyzer-checker=debug.ExprInspection,alpha.core.StrictAliasing -verify %s
+
+template <typename T>
+void clang_analyzer_dump(T x);
+void clang_analyzer_eval(int);
+
+namespace std {
+enum class byte : unsigned char {};
+enum class otherByte : unsigned char {};
+}; // namespace std
+enum class intEnum : int {};
+
+class Class {};
+class ClassInt {
+  int x;
+};
+
+using AliasedStdByte = std::byte;
+using AliasedChar = char;
+using AliasedSChar = signed char;
+using AliasedInt = int;
+using AliasedUInt = unsigned int;
+using AliasedULong = unsigned long;
+
+namespace ns1 {
+
+void var_cast() {
+  using MyInt = int;
+  MyInt x = {};
+  {
+    auto *ptr = (std::byte *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (std::otherByte *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (intEnum *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (Class *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (ClassInt *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (char *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (unsigned char *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (const char *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (const unsigned char *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (signed char *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (short *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (unsigned short *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (signed short *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (int *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (unsigned int *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (signed int *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (long *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (unsigned long *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (signed long *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (long long *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (unsigned long long *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (signed long long *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (float *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (double *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (long double *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (AliasedStdByte *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (AliasedChar *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (AliasedSChar *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (AliasedULong *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+  {
+    auto *ptr = (AliasedInt *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (AliasedUInt *)&x;
+    auto y = *ptr; // no-warning
+  }
+  {
+    auto *ptr = (AliasedULong *)&x;
+    auto y = *ptr; // expected-warning{{Undefined behavior}}
+  }
+}
+
+} // namespace ns1
Index: clang/lib/StaticAnalyzer/Checkers/StrictAliasingChecker.cpp
===================================================================
--- /dev/null
+++ clang/lib/StaticAnalyzer/Checkers/StrictAliasingChecker.cpp
@@ -0,0 +1,181 @@
+//===- StrictAliasingChecker - ... ------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// StrictAliasingChecker implements checks on violation of the next paragraph of
+// the Standard which is known as `Strict Aliasing Rule`.
+// C++20 7.2.1 p11 [basic.lval]:
+//  If a program attempts to access the stored value of an object through a
+//  glvalue whose type is not similar to one of the following types the behavior
+//  is undefined:
+//  - the dynamic type of the object,
+//  - a type that is the signed or unsigned type corresponding to the dynamic
+//    type of the object, or
+//  - a char, unsigned char, or std::byte type.
+//
+// NOTE: C, C++ Standards have differences in their strict aliasing rules.
+// Now we only impelement checks since C++20 Standard (there were changes
+// applied in C++20 http://wg21.link/cwg2051).
+// 
+// TODO: Support C++98/11/14/17. Shall we?
+// TODO: Support C.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/Attr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+
+class AccessInferrer {
+  QualType From;
+  QualType To;
+  ASTContext &Ctx;
+
+public:
+  // Check whether the given types submit to the Strict Aliasing Rule.
+  //
+  // NOTE: User must provide canonical and unqualified QualType's for the
+  // correct result.
+  static bool canAccess(QualType From, QualType To, ASTContext &Ctx) {
+    AccessInferrer AI(From, To, Ctx);
+    return AI.canAccessImpl();
+  }
+
+private:
+  AccessInferrer(QualType From, QualType To, ASTContext &Ctx)
+      : From(From), To(To), Ctx(Ctx) {}
+  bool canAccessImpl() {
+    return isSame() || isCharOrByte() || isOppositeSign();
+  }
+  // - the dynamic type of the object
+  bool isSame() { return From == To; }
+  // - a char, unsigned char, or std::byte type.
+  bool isCharOrByte() {
+    return To == Ctx.CharTy || To == Ctx.UnsignedCharTy || To->isStdByteType();
+  }
+  // - a type that is the signed or unsigned type corresponding to the dynamic
+  //   type of the object
+  bool isOppositeSign() {
+    QualType OppositeSignTy;
+    if (To->isUnsignedIntegerOrEnumerationType())
+      OppositeSignTy = Ctx.getCorrespondingSignedType(To);
+    else if (To->isSignedIntegerOrEnumerationType())
+      OppositeSignTy = Ctx.getCorrespondingUnsignedType(To);
+    return From == OppositeSignTy;
+  }
+};
+
+class StrictAliasingChecker : public Checker<check::Location> {
+  mutable std::unique_ptr<BugType> BT;
+
+public:
+  void checkLocation(SVal Location, bool IsLoad, const Stmt *S,
+                     CheckerContext &C) const {
+    assert(isa<Expr>(S) && "Stmt is expected to be Expr.");
+    const QualType ExprTy = cast<Expr>(S)->getType();
+
+    const QualType AliasedTy = getAliasedType(ExprTy);
+    // TODO: Handle this case in a proper way, if any.
+    if (AliasedTy.isNull())
+      return;
+
+    const QualType OrigTy = getOriginalType(C, Location, ExprTy);
+    // TODO: Handle this case in a proper way, if any.
+    if (OrigTy.isNull())
+      return;
+
+    if (!AccessInferrer::canAccess(OrigTy, AliasedTy, C.getASTContext()))
+      reportBug(C, OrigTy, AliasedTy);
+  }
+
+private:
+  // FIXME: Probably, we should have such function in QualType class.
+  // Existing `T->getCanonicalTypeUnqualified()` does not return unqualified
+  // type, which, apparently, is expected.
+  QualType getCanonicalUnqualifiedType(QualType T) const {
+    T = T->getCanonicalTypeUnqualified();
+    T.removeLocalFastQualifiers();
+    return T;
+  }
+
+  QualType getOriginalType(CheckerContext &C, SVal V, QualType T) const {
+    assert(V.getAs<Loc>() && "Location shall be a Loc.");
+    V = C.getState()->getSVal(V.castAs<Loc>(), T);
+
+    auto MRV = V.getAs<loc::MemRegionVal>();
+    if (!MRV.hasValue())
+      return getCanonicalUnqualifiedType(V.getType(C.getASTContext()));
+
+    const MemRegion *const Base = getRegionBase(MRV->getRegion());
+    // TODO: Support other regions.
+    if (const VarRegion *VR = dyn_cast<VarRegion>(Base))
+      return getCanonicalUnqualifiedType(VR->getDecl()->getType());
+
+    return QualType{};
+  }
+
+  QualType getAliasedType(QualType T) const {
+    T = T->getPointeeType();
+
+    // If there is no pointee type, then, it most likely is a cast from
+    // non-pointer (etc. integer) to pointer.
+    // TODO: Handle this case in a proper way, if any.
+    if (!T.isNull())
+      return getCanonicalUnqualifiedType(T);
+
+    return T;
+  }
+
+  const MemRegion *getRegionBase(const MemRegion *R) const {
+    auto ER = dyn_cast<ElementRegion>(R);
+    while (ER) {
+      R = ER->getSuperRegion();
+      ER = dyn_cast<ElementRegion>(R);
+    }
+    return R;
+  }
+
+  void reportBug(CheckerContext &C, QualType From, QualType To) const {
+    SmallString<256> Buf;
+    llvm::raw_svector_ostream OS(Buf);
+    OS << "Undefined behavior. Attempting to access the stored value of type ";
+    OS << "'" << From.getAsString() << "'";
+    OS << " through unallowed type ";
+    OS << "'" << To.getAsString() << "'.";
+
+    ExplodedNode *Node = C.generateNonFatalErrorNode();
+    if (!BT)
+      BT =
+          std::make_unique<BugType>(this, "Strict Aliasing Rule",
+                                    "Access Violation through unallowed type.");
+    auto Report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), Node);
+    C.emitReport(std::move(Report));
+  }
+};
+} // end anonymous namespace
+
+//===----------------------------------------------------------------------===//
+// Registration.
+//===----------------------------------------------------------------------===//
+
+void ento::registerStrictAliasingChecker(CheckerManager &CM) {
+  CM.registerChecker<StrictAliasingChecker>();
+}
+
+bool ento::shouldRegisterStrictAliasingChecker(const CheckerManager &CM) {
+  const LangOptions &LO = CM.getLangOpts();
+  // Ideally, the condition should be `LO.CPlusPlus11 || LO.CPlusPlus2b` but
+  // implemented checks can be partially applied for C++17 and lower versions.
+  return LO.CPlusPlus;
+}
Index: clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -105,6 +105,7 @@
   StdLibraryFunctionsChecker.cpp
   STLAlgorithmModeling.cpp
   StreamChecker.cpp
+  StrictAliasingChecker.cpp
   StringChecker.cpp
   Taint.cpp
   TaintTesterChecker.cpp
Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -298,6 +298,12 @@
   Dependencies<[PthreadLockBase]>,
   Documentation<HasAlphaDocumentation>;
 
+def StrictAliasingChecker : Checker<"StrictAliasing">,
+  HelpText<"Check conformity with Strict Alising Rule. Check an access to the "
+           "stored value through a glvalue whose type is not allowed by "
+           "the Standard. ([basic.lval])">,
+  Documentation<HasAlphaDocumentation>;
+
 } // end "alpha.core"
 
 //===----------------------------------------------------------------------===//
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to