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; +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; + } // end "alpha.core" //===----------------------------------------------------------------------===// 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/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 { + mutable std::unique_ptr BT; + +public: + void checkLocation(SVal Location, bool IsLoad, const Stmt *S, + CheckerContext &C) const { + assert(isa(S) && "Stmt is expected to be Expr."); + const QualType ExprTy = cast(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() && "Location shall be a Loc."); + V = C.getState()->getSVal(V.castAs(), T); + + auto MRV = V.getAs(); + 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(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(R); + while (ER) { + R = ER->getSuperRegion(); + ER = dyn_cast(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(this, "Strict Aliasing Rule", + "Access Violation through unallowed type."); + auto Report = std::make_unique(*BT, OS.str(), Node); + C.emitReport(std::move(Report)); + } +}; +} // end anonymous namespace + +//===----------------------------------------------------------------------===// +// Registration. +//===----------------------------------------------------------------------===// + +void ento::registerStrictAliasingChecker(CheckerManager &CM) { + CM.registerChecker(); +} + +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/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 +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