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,204 @@ +//===- 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: The checker operates only when strict-aliasing is enabled with +// corresponding compiler flag. +// +// NOTE: There are differences in strict aliasing rules between C, C++ +// Standards. 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) { + // NOTE: Despite the function name `isCanonical()`, it also check whether + // the type is unqualified. (See implementation) + assert(From.isCanonical() && "The type shall be an unqualified canonical."); + assert(To.isCanonical() && "The type shall be an unqualified canonical."); + 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 isSameDynamic() || isCharOrByte() || isOppositeSign(); + } + // - the dynamic type of the object + bool isSameDynamic() { + if (From == To) + return true; + + if (const CXXRecordDecl *FromDecl = From->getAsCXXRecordDecl()) + if (const CXXRecordDecl *ToDecl = To->getAsCXXRecordDecl()) + return FromDecl->isDerivedFrom(ToDecl); + + return false; + } + // - 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 { + BugType BT{this, "Strict Aliasing Rule", + "Access Violation through unallowed type."}; + +public: + void checkLocation(SVal Location, bool IsLoad, const Stmt *S, + CheckerContext &C) const { + // Handle Expr only. + if (!isa(S)) + return; + + 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 { + // TODO: Check getUnqualifiedDesugaredType. + if (!T.isNull()) { + T = T->getCanonicalTypeUnqualified(); + T.removeLocalFastQualifiers(); + } + return T; + } + + QualType getOriginalType(CheckerContext &C, SVal V, QualType T) const { + if (V.isUnknownOrUndef()) + return QualType{}; + + 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())); + + // Unwrap ElementRegion and CXXBaseObjectRegion nesting to get the base + // region. + const MemRegion *Base = MRV->getRegion()->StripCasts(true); + + // Get type of the original declaration. + if (const VarRegion *VR = dyn_cast(Base)) + return getCanonicalUnqualifiedType(VR->getValueType()); + + // Get type from other types of regions. + // TODO: Support other regions in a proper way if would need. + return getCanonicalUnqualifiedType(V.getType(C.getASTContext())); + } + + QualType getAliasedType(QualType T) const { + T = T->getPointeeType(); + // If there is no pointee type(is null), then, it most likely is a cast from + // non-pointer (etc. integer) to pointer. + // TODO: Handle this case in a proper way, if any. + return getCanonicalUnqualifiedType(T); + } + + const MemRegion *getElementRegionBase(const ElementRegion *ER) const { + const MemRegion *R = ER->getSuperRegion(); + while ((ER = dyn_cast(R))) + R = ER->getSuperRegion(); + 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(); + 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(); + const bool IsStrictAliasing = !CM.getCodeGenOpts().RelaxedAliasing; + // Support from C++20 for now. + // TODO: Support C++98/11/14/17. Shall we? + // TODO: Support C. + return IsStrictAliasing && (LO.CPlusPlus20 || LO.CPlusPlus2b); +} Index: clang/test/Analysis/Checkers/StrictAliasingChecker/strict-aliasing.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/Checkers/StrictAliasingChecker/strict-aliasing.cpp @@ -0,0 +1,524 @@ +// RUN: %clang_cc1 -std=c++20 -analyze -analyzer-config eagerly-assume=false -analyzer-checker=debug.ExprInspection,alpha.core.StrictAliasing -verify %s +// NOTE: -relaxed-aliasing flag disables StrictAliasing checker. + +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 EnumInt : int {}; + +union UnionUchar { + unsigned char x; + char y; +}; +union UnionInt { + int x; +}; +class Class {}; +class ClassInt { +public: + int x; +}; +class Base { +public: + int x; +}; +struct AnotherBase { + int y; +}; +class Derived : public AnotherBase, public Base { + int z; +}; +class MostDerived : public Derived { + int w; +}; + +using AliasedStdByte = std::byte; +using AliasedChar = char; +using AliasedSChar = const signed char; +using AliasedInt = int; +using AliasedUInt = const unsigned int; +using AliasedULong = unsigned long; +using AliasedBase = Base; +using AliasedAnotherBase = AnotherBase; + +namespace ns1 { + +void int_casts() { + using MyInt = int; + MyInt x = {}; + // int to records + { + auto *ptr = (Class *)&x; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (ClassInt *)&x; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (UnionUchar *)&x; + ptr->x = 42; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (UnionInt *)&x; + ptr->x = 42; // expected-warning{{Undefined behavior}} + } + // int to records + { + auto *ptr = (std::byte *)&x; + auto y = *ptr; // no-warning + } + { + auto *ptr = (std::OtherByte *)&x; + auto y = *ptr; // expected-warning{{Undefined behavior. Attempting to access the stored value of type}} + } + { + auto *ptr = (EnumInt *)&x; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + // int to scalars + { + auto *ptr = (const char *)&x; + auto y = *ptr; // no-warning + } + { + auto *ptr = (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 = (const volatile unsigned int *)&x; + auto y = *ptr; // no-warning + } + { + auto *ptr = (signed int *)&x; + *ptr = 24; // 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}} + } + // int to aliases + { + 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}} + } +} + +void derived_class_casts() { + Derived d; + // record to records + { + auto *ptr = (MostDerived *)&d; + auto y = ptr->x; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (Derived *)&d; + auto y = ptr->x; // no-warning + } + { + auto *ptr = (Base *)&d; + auto y = ptr->x; // no-warning + } + { + auto *ptr = (const AnotherBase *)&d; + auto y = ptr->y; // no-warning + } + { + auto *ptr = (ClassInt *)&d; + auto y = ptr->x; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (Class *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (const ClassInt *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (UnionUchar *)&d; + ptr->x = 42; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (UnionInt *)&d; + ptr->x = 42; // expected-warning{{Undefined behavior}} + } + // record to scalars + { + auto *ptr = (char *)&d; + auto y = *ptr; // no-warning + } + { + auto *ptr = (const unsigned char *)&d; + auto y = *ptr; // no-warning + } + { + auto *ptr = (signed char *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (short *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (unsigned short *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (signed short *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (int *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (unsigned int *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (signed int *)&d; + *ptr = 24; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (long *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (unsigned long *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (signed long *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (long long *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (unsigned long long *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (signed long long *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (float *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (double *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (long double *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + // record to aliases + { + auto *ptr = (AliasedStdByte *)&d; + auto y = *ptr; // no-warning + } + { + auto *ptr = (AliasedChar *)&d; + auto y = *ptr; // no-warning + } + { + auto *ptr = (AliasedSChar *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (AliasedULong *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (AliasedInt *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (AliasedUInt *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (AliasedULong *)&d; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (AliasedBase *)&d; + auto y = ptr->x; // no-warning + } + { + auto *ptr = (AliasedAnotherBase *)&d; + auto y = (*ptr).y; // no-warning + } +} + +void base_class_casts() { + Base b; + // record to records + { + auto *ptr = (MostDerived *)&b; + auto y = ptr->x; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (Derived *)&b; + auto y = ptr->x; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (Base *)&b; + auto y = ptr->x; // no-warning + } + { + auto *ptr = (const AnotherBase *)&b; + auto y = ptr->y; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (ClassInt *)&b; + auto y = ptr->x; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (Class *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (const ClassInt *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (UnionUchar *)&b; + ptr->x = 42; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (UnionInt *)&b; + ptr->x = 42; // expected-warning{{Undefined behavior}} + } + // record to scalars + { + auto *ptr = (char *)&b; + auto y = *ptr; // no-warning + } + { + auto *ptr = (const unsigned char *)&b; + auto y = *ptr; // no-warning + } + { + auto *ptr = (signed char *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (short *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (unsigned short *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (signed short *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (int *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (unsigned int *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (signed int *)&b; + *ptr = 24; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (long *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (unsigned long *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (signed long *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (long long *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (unsigned long long *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (signed long long *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (float *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (double *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (long double *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + // record to aliases + { + auto *ptr = (AliasedStdByte *)&b; + auto y = *ptr; // no-warning + } + { + auto *ptr = (AliasedChar *)&b; + auto y = *ptr; // no-warning + } + { + auto *ptr = (AliasedSChar *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (AliasedULong *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (AliasedInt *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (AliasedUInt *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (AliasedULong *)&b; + auto y = *ptr; // expected-warning{{Undefined behavior}} + } + { + auto *ptr = (AliasedBase *)&b; + auto y = ptr->x; // no-warning + } + { + auto *ptr = (AliasedAnotherBase *)&b; + auto y = (*ptr).y; // expected-warning{{Undefined behavior}} + } +} + +// This produces nested CXXBaseObjectRegion. +void nested_casts() { + MostDerived md; + { + auto *ptr1 = (Derived *)&md; // Base{md,Derived} + auto *ptr2 = (Base *)ptr1; // Base{Base{md,Derived},Base} + auto y = ptr2->x; // no-warning + } + { + auto *ptr1 = (Base *)&md; + auto *ptr2 = (Derived *)ptr1; + auto y = ptr2->x; // no-warning + } + { + auto *ptr1 = (Base *)&md; + auto *ptr2 = (AnotherBase *)ptr1; + auto *ptr3 = (MostDerived *)ptr2; + auto y = ptr3->x; // no-warning + } + { + auto *ptr1 = (Base *)&md; + auto *ptr2 = (MostDerived *)ptr1; + auto *ptr3 = (char *)ptr2; + auto y = *ptr3; // no-warning + } + { + auto *ptr1 = (UnionInt *)&md; + // TODO: Should not warn. + auto *ptr2 = (char *)ptr1; // expected-warning{{Undefined behavior}} + auto *ptr3 = (Base *)ptr2; + auto y = ptr3->x; // no-warning + } +} + +} // namespace ns1