Index: clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -21,6 +21,7 @@ #include "ForwardDeclarationNamespaceCheck.h" #include "ForwardingReferenceOverloadCheck.h" #include "InaccurateEraseCheck.h" +#include "IncorrectPointerCastCheck.h" #include "IncorrectRoundingsCheck.h" #include "IntegerDivisionCheck.h" #include "LambdaFunctionNameCheck.h" @@ -82,6 +83,8 @@ "bugprone-forwarding-reference-overload"); CheckFactories.registerCheck( "bugprone-inaccurate-erase"); + CheckFactories.registerCheck( + "bugprone-incorrect-pointer-cast"); CheckFactories.registerCheck( "bugprone-incorrect-roundings"); CheckFactories.registerCheck( Index: clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -13,6 +13,7 @@ ForwardDeclarationNamespaceCheck.cpp ForwardingReferenceOverloadCheck.cpp InaccurateEraseCheck.cpp + IncorrectPointerCastCheck.cpp IncorrectRoundingsCheck.cpp IntegerDivisionCheck.cpp LambdaFunctionNameCheck.cpp Index: clang-tools-extra/clang-tidy/bugprone/IncorrectPointerCastCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/bugprone/IncorrectPointerCastCheck.h @@ -0,0 +1,49 @@ +//===--- IncorrectPointerCastCheck.h - clang-tidy ---------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCORRECTPOINTERCASTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCORRECTPOINTERCASTCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Warns for cases when pointer is cast and the pointed to type is incompatible +/// with allocated memory area type. This may lead to access memory based on +/// invalid memory layout. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-incorrect-pointer-cast.html +class IncorrectPointerCastCheck : public ClangTidyCheck { +public: + IncorrectPointerCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnForDifferentSignedness( + Options.get("WarnForDifferentSignedness", false)), + IgnoreReinterpretCast(Options.get("IgnoreReinterpretCast", false)) {} + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + /// This option can be configured to warn when the pointed to type signedness + /// is different from the allocated type. The default is false because this + /// option might be noisy on some code bases. + const bool WarnForDifferentSignedness; + /// This option can be configured to do not warn when reinterpter cast is + /// used. The default is false. + const bool IgnoreReinterpretCast; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCORRECTPOINTERCASTCHECK_H Index: clang-tools-extra/clang-tidy/bugprone/IncorrectPointerCastCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/bugprone/IncorrectPointerCastCheck.cpp @@ -0,0 +1,112 @@ +//===--- IncorrectPointerCastCheck.cpp - clang-tidy -------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "IncorrectPointerCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecordLayout.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +void IncorrectPointerCastCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnForDifferentSignedness", WarnForDifferentSignedness); + Options.store(Opts, "IgnoreReinterpretCast", IgnoreReinterpretCast); +} + +void IncorrectPointerCastCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(cStyleCastExpr(hasCastKind(CK_BitCast), + unless(isInTemplateInstantiation())) + .bind("cast"), + this); + if (!IgnoreReinterpretCast) { + Finder->addMatcher( + cxxReinterpretCastExpr(hasCastKind(CK_BitCast), + unless(isInTemplateInstantiation())) + .bind("cast"), + this); + } +} + +void IncorrectPointerCastCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext &Context = *Result.Context; + const auto *castExpr = Result.Nodes.getNodeAs("cast"); + + const QualType SrcType = castExpr->getSubExpr()->getType(); + const QualType DestType = castExpr->getType(); + + if (!SrcType->isPointerType() || !DestType->isPointerType()) + return; + + if (SrcType->isDependentType() || DestType->isDependentType()) + return; + + const QualType SrcPointedType = SrcType->getPointeeType(); + const QualType DestPointedType = DestType->getPointeeType(); + + if (SrcPointedType->isIncompleteType() || DestPointedType->isIncompleteType()) + return; + + if (Context.getIntWidth(SrcPointedType) < + Context.getIntWidth(DestPointedType)) { + diag(castExpr->getBeginLoc(), + "cast from %0 to %1 may lead to access memory based on invalid memory " + "layout; pointed to type is wider than the allocated type") + << SrcPointedType << DestPointedType; + } else if (Result.Context->getTypeAlign(SrcPointedType) < + Result.Context->getTypeAlign(DestPointedType)) { + diag(castExpr->getBeginLoc(), + "cast from %0 to %1 may lead to access memory based on invalid " + "memory layout; pointed to type is strictly aligned than the " + "allocated type") + << SrcPointedType << DestPointedType; + } else if (SrcPointedType->isStructureType() && + DestPointedType->isStructureType()) { + const auto *SrcTypeRecordDecl = SrcPointedType->getAsRecordDecl(); + const auto *DestTypeRecordDecl = DestPointedType->getAsRecordDecl(); + bool FieldsAreSame = true; + + for (RecordDecl::field_iterator + SrcIterator = SrcTypeRecordDecl->field_begin(), + SrcEnd = SrcTypeRecordDecl->field_end(), + DestIterator = DestTypeRecordDecl->field_begin(), + DestEnd = DestTypeRecordDecl->field_end(); + SrcIterator != SrcEnd && DestIterator != DestEnd && FieldsAreSame; + ++SrcIterator, ++DestIterator) { + const FieldDecl &SrcField = **SrcIterator; + const FieldDecl &DestField = **DestIterator; + if (SrcField.getType() != DestField.getType() || SrcIterator == SrcEnd) { + FieldsAreSame = false; + } + } + + if (!FieldsAreSame) { + diag(castExpr->getBeginLoc(), + "cast from %0 to %1 may lead to access memory based on invalid " + "memory layout; struct members are incompatible") + << SrcPointedType << DestPointedType; + } + } else if (WarnForDifferentSignedness && + ((SrcPointedType->isSignedIntegerType() && + DestPointedType->isUnsignedIntegerType()) || + (SrcPointedType->isUnsignedIntegerType() && + DestPointedType->isSignedIntegerType()))) { + diag(castExpr->getBeginLoc(), + "cast from %0 to %1 may lead to access memory based on invalid " + "memory layout; different signedness types") + << SrcPointedType << DestPointedType; + } +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang Index: clang-tools-extra/docs/ReleaseNotes.rst =================================================================== --- clang-tools-extra/docs/ReleaseNotes.rst +++ clang-tools-extra/docs/ReleaseNotes.rst @@ -77,6 +77,13 @@ Checks for cases where addition should be performed in the ``absl::Time`` domain. +- New :doc:`bugprone-incorrect-pointer-cast + ` check + + Warns for cases when pointer is cast and the pointed to type is + incompatible with allocated memory area type. This may lead to access memory + based on invalid memory layout. + - New :doc:`abseil-duration-conversion-cast ` check. Index: clang-tools-extra/docs/clang-tidy/checks/bugprone-incorrect-pointer-cast.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/bugprone-incorrect-pointer-cast.rst @@ -0,0 +1,85 @@ +.. title:: clang-tidy - bugprone-incorrect-pointer-cast + +bugprone-incorrect-pointer-cast +=============================== + +Warns for cases when pointer is cast and the pointed to type is wider than the +allocated type. +For example `char` vs `int`, `long` vs `char` etc. +Also warns for cases when the pointed to type layout is different from the +allocated type layout, like different structs, `int` vs `float`/`double`, +different signedness. + +Allows pointer casts if the pointed to struct type is "part" of the allocated +type. +Which means the allocated type contains the pointed to type member by member. + +Options +------- + +.. option:: WarnForDifferentSignedness + + This option can be configured to warn when the pointed to type signedness + is different from the allocated type. + Disabled by default because this option might be noisy on some code bases. + +.. option:: IgnoreReinterpretCast + + This option can be configured to do not warn when reinterpter cast is used. + Disabled by default but this option might be useful on code bases where + `reinterpret_cast` is used carefully. + +Examples +------- + +Cast char pointer to integer pointer. +Check will warn because of cast to a wider type. + +.. code-block:: c++ + + char c = 'a'; + int *i = (int *)&c; + +Cast between structs. +Check will allow to cast to a narrower struct if it is part of the source struct +member by member. + +.. code-block:: c++ + + struct S1 { + int a; + }; + + struct S2 { + int a; + double b; + }; + + struct S3 { + double y; + long x; + }; + + struct S2 s2; + struct S1 *s1 = (struct S1 *)&s2; // Won't warn. Struct "S2" contains struct + // "S2" member by member. + struct S3 *s3 = (struct S3 *)&s2; // Warning because of different type + // layout. + +Cast with `reinterpret_cast`. +If the `IgnoreReinterpretCast` option is `0`, check will warn for these +kind of casts. + +.. code-block:: c++ + + char c = 'x'; + int *i = reinterpret_cast(&c); + +Cast between different signedness types. +If the `WarnForDifferentSignedness` option is `1`, check will warn for these +kind of casts. + +.. code-block:: c++ + + unsigned int u; + int i = (int *)&u; Index: clang-tools-extra/docs/clang-tidy/checks/list.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/list.rst +++ clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -46,6 +46,7 @@ bugprone-forward-declaration-namespace bugprone-forwarding-reference-overload bugprone-inaccurate-erase + bugprone-incorrect-pointer-cast bugprone-incorrect-roundings bugprone-integer-division bugprone-lambda-function-name Index: clang-tools-extra/test/clang-tidy/bugprone-incorrect-pointer-cast.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/bugprone-incorrect-pointer-cast.cpp @@ -0,0 +1,206 @@ +// RUN: %check_clang_tidy %s bugprone-incorrect-pointer-cast %t -- \ +// RUN: -config="{CheckOptions: [{key: bugprone-incorrect-pointer-cast.WarnForDifferentSignedness, value: 1}]}" -- + +bool b; +char __attribute__((aligned(4))) a[16]; + +struct S0 { + char a[16]; +}; + +struct S01 { + char __attribute__((aligned(4))) a[16]; + struct S0 __attribute__((aligned(4))) s0; +}; + +struct S1 { + int a; + int b; +}; + +struct S2 { + int a; +}; + +struct S3 { + int a; + double b; +}; + +struct S4 { + int x; + double y; +}; + +struct S5 { + double y; + int x; +}; + +struct __attribute__((aligned(16))) SAligned { + char buffer[16]; +}; + +void initDouble(double *d) { + *d = 0.5; +} + +void castCharToInt(void) { + char c = 'x'; + b = (int *)&c; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'char' to 'int' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] + b = reinterpret_cast(&c); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'char' to 'int' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] +} + +void castCharToSort(char c) { + b = (short *)&c; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'char' to 'short' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] + b = reinterpret_cast(&c); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'char' to 'short' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] +} + +void castShortToInt(short s) { + b = (int *)&s; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'short' to 'int' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] + b = reinterpret_cast(&s); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'short' to 'int' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] +} + +void castWideCharToLong(wchar_t wc) { + b = (long *)&wc; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'wchar_t' to 'long' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] + b = reinterpret_cast(&wc); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'wchar_t' to 'long' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] +} + +void castFloatToDouble(float f) { + initDouble((double *)&f); + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: cast from 'float' to 'double' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] + initDouble(reinterpret_cast(&f)); + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: cast from 'float' to 'double' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] +} + +void castToS2(char *data, unsigned offset) { + b = (struct S2 *)(data + offset); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'char' to 'struct S2' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] + b = reinterpret_cast(data + offset); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'char' to 'struct S2' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] +} + +void castS3ToS1(struct S3 s3) { + b = (struct S1 *)&s3; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'struct S3' to 'struct S1' may lead to access memory based on invalid memory layout; struct members are incompatible [bugprone-incorrect-pointer-cast] + b = reinterpret_cast(&s3); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'struct S3' to 'struct S1' may lead to access memory based on invalid memory layout; struct members are incompatible [bugprone-incorrect-pointer-cast] +} + +void castS4ToS5(struct S4 s4) { + b = (struct S5 *)&s4; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'struct S4' to 'struct S5' may lead to access memory based on invalid memory layout; struct members are incompatible [bugprone-incorrect-pointer-cast] + b = reinterpret_cast(&s4); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'struct S4' to 'struct S5' may lead to access memory based on invalid memory layout; struct members are incompatible [bugprone-incorrect-pointer-cast] +} + +void castULongToLong(unsigned long ul) { + b = (long *)&ul; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'unsigned long' to 'long' may lead to access memory based on invalid memory layout; different signedness types [bugprone-incorrect-pointer-cast] + b = reinterpret_cast(&ul); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'unsigned long' to 'long' may lead to access memory based on invalid memory layout; different signedness types [bugprone-incorrect-pointer-cast] +} + +void castIntToUInt(int i) { + b = (unsigned int *)&i; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'int' to 'unsigned int' may lead to access memory based on invalid memory layout; different signedness types [bugprone-incorrect-pointer-cast] + b = reinterpret_cast(&i); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'int' to 'unsigned int' may lead to access memory based on invalid memory layout; different signedness types [bugprone-incorrect-pointer-cast] +} + +void castToAlignedStruct(char *P) { + b = (struct SAligned *)P; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'char' to 'struct SAligned' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] +} + +void castCharToIntWithReinterpretCast(void) { + char c = 'x'; + b = reinterpret_cast(&c); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'char' to 'int' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] +} + +void TestDifferentAlignment(struct S01 s) { + int *i = (int *)s.a; + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: cast from 'char' to 'int' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] + i = (int *)&s.s0; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'struct S0' to 'int' may lead to access memory based on invalid memory layout; pointed to type is strictly aligned than the allocated type [bugprone-incorrect-pointer-cast] + i = (int *)a; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: cast from 'char' to 'int' may lead to access memory based on invalid memory layout; pointed to type is wider than the allocated type [bugprone-incorrect-pointer-cast] +} + +// negatives +void castIntToFloat(int i) { + float *f = (float *)&i; +} + +void castCharToChar(char *p) { + char *c = (char *)p; +} + +void castShortToChar(short s) { + char *c = (char *)&s; +} + +void initInt(int *i) { + *i = 1; +} + +void castIntToInt() { + int i; + initInt(&i); +} + +void castS1ToS2() { + struct S1 s1; + struct S2 *s2 = (struct S2 *)&s1; +} + +void castS4ToS3() { + struct S4 s4; + struct S3 *s3 = (struct S3 *)&s4; +} + +void IncompleteType(char *P) { + struct B *b = (struct B *)P; +} + +// Casts from void* are a special case. +void CastFromVoidPointer(void *P) { + char *a = (char *)P; + short *b = (short *)P; + int *c = (int *)P; + + const volatile void *P2 = P; + char *d = (char *)P2; + short *e = (short *)P2; + int *f = (int *)P2; + + const char *g = (const char *)P2; + const short *h = (const short *)P2; + const int *i = (const int *)P2; + + const volatile char *j = (const volatile char *)P2; + const volatile short *k = (const volatile short *)P2; + const volatile int *l = (const volatile int *)P2; +} + +typedef int (*FnTy)(void); +unsigned int func(void); + +FnTy testFunc(void) { + return (FnTy)&func; +} + +struct W; +void function3(struct W *v) { + int *i = (int *)v; + struct W *u = (struct W *)i; +}