Skip to content
2 changes: 2 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "BoolPointerImplicitConversionCheck.h"
#include "BranchCloneCheck.h"
#include "CapturingThisInMemberVariableCheck.h"
#include "CastToStructCheck.h"
#include "CastingThroughVoidCheck.h"
#include "ChainedComparisonCheck.h"
#include "ComparePointerToMemberVirtualFunctionCheck.h"
Expand Down Expand Up @@ -127,6 +128,7 @@ class BugproneModule : public ClangTidyModule {
"bugprone-capturing-this-in-member-variable");
CheckFactories.registerCheck<CastingThroughVoidCheck>(
"bugprone-casting-through-void");
CheckFactories.registerCheck<CastToStructCheck>("bugprone-cast-to-struct");
CheckFactories.registerCheck<ChainedComparisonCheck>(
"bugprone-chained-comparison");
CheckFactories.registerCheck<ComparePointerToMemberVirtualFunctionCheck>(
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ add_clang_library(clangTidyBugproneModule STATIC
BugproneTidyModule.cpp
CapturingThisInMemberVariableCheck.cpp
CastingThroughVoidCheck.cpp
CastToStructCheck.cpp
ChainedComparisonCheck.cpp
ComparePointerToMemberVirtualFunctionCheck.cpp
CopyConstructorInitCheck.cpp
Expand Down
97 changes: 97 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/CastToStructCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//===--- CastToStructCheck.cpp - clang-tidy -------------------------------===//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New style:

Suggested change
//===--- CastToStructCheck.cpp - clang-tidy -------------------------------===//
//===----------------------------------------------------------------------===//

//
// 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 "CastToStructCheck.h"
#include "../utils/Matchers.h"
#include "../utils/OptionsUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

namespace clang::tidy::bugprone {

namespace {

AST_MATCHER(Type, charType) { return Node.isCharType(); }
AST_MATCHER(Type, unionType) { return Node.isUnionType(); }

} // namespace

CastToStructCheck::CastToStructCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
IgnoredCasts(
utils::options::parseStringList(Options.get("IgnoredCasts", ""))) {
IgnoredCastsRegex.reserve(IgnoredCasts.size());
for (const auto &Str : IgnoredCasts) {
std::string WholeWordRegex;
WholeWordRegex.reserve(Str.size() + 2);
WholeWordRegex.push_back('^');
WholeWordRegex.append(Str);
WholeWordRegex.push_back('$');
IgnoredCastsRegex.emplace_back(WholeWordRegex);
}
}

void CastToStructCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "IgnoredCasts",
utils::options::serializeStringList(IgnoredCasts));
}

void CastToStructCheck::registerMatchers(MatchFinder *Finder) {
auto FromPointee =
qualType(hasUnqualifiedDesugaredType(type().bind("FromType")),
unless(voidType()), unless(charType()), unless(unionType()))
.bind("FromPointee");
auto ToPointee =
qualType(hasUnqualifiedDesugaredType(
recordType(unless(hasDeclaration(recordDecl(isUnion()))))
.bind("ToType")))
.bind("ToPointee");
auto FromPtrType = qualType(pointsTo(FromPointee)).bind("FromPtr");
auto ToPtrType = qualType(pointsTo(ToPointee)).bind("ToPtr");
Finder->addMatcher(cStyleCastExpr(hasSourceExpression(hasType(FromPtrType)),
hasType(ToPtrType))
.bind("CastExpr"),
this);
}

void CastToStructCheck::check(const MatchFinder::MatchResult &Result) {
const auto *const FoundCastExpr =
Result.Nodes.getNodeAs<CStyleCastExpr>("CastExpr");
const auto *const FromPtr = Result.Nodes.getNodeAs<QualType>("FromPtr");
const auto *const ToPtr = Result.Nodes.getNodeAs<QualType>("ToPtr");
const auto *const FromType = Result.Nodes.getNodeAs<Type>("FromType");
const auto *const ToType = Result.Nodes.getNodeAs<RecordType>("ToType");

if (FromType == ToType)
return;

auto CheckNameIgnore = [this](const std::string &FromName,
const std::string &ToName) {
bool FromMatch = false;
for (auto [Idx, Regex] : llvm::enumerate(IgnoredCastsRegex)) {
if (Idx % 2 == 0) {
FromMatch = Regex.match(FromName);
} else {
if (FromMatch && Regex.match(ToName))
return true;
}
}
return false;
};

if (CheckNameIgnore(FromPtr->getAsString(), ToPtr->getAsString()))
return;

diag(FoundCastExpr->getExprLoc(), "casting a %0 pointer to a "
"%1 pointer can lead to memory "
"access errors or data corruption")
<< *FromPtr << *ToPtr;
}

} // namespace clang::tidy::bugprone
39 changes: 39 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/CastToStructCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//===--- CastToStructCheck.h - clang-tidy -----------------------*- C++ -*-===//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Suggested change
//===--- CastToStructCheck.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_CASTTOSTRUCTCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CASTTOSTRUCTCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::bugprone {

/// Finds casts from pointers to struct or scalar type to pointers to struct
/// type.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/cast-to-struct.html
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Suggested change
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/cast-to-struct.html
/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/cast-to-struct.html

class CastToStructCheck : public ClangTidyCheck {
public:
CastToStructCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
// C++ has different options for casting that make such a check less useful.
return !LangOpts.CPlusPlus;
}

private:
std::vector<llvm::StringRef> IgnoredCasts;
std::vector<llvm::Regex> IgnoredCastsRegex;
};

} // namespace clang::tidy::bugprone

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CASTTOSTRUCTCHECK_H
5 changes: 5 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^

- New :doc:`bugprone-cast-to-struct
<clang-tidy/checks/bugprone/cast-to-struct>` check.

Finds casts from pointers to struct or scalar type to pointers to struct type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Finds casts from pointers to struct or scalar type to pointers to struct type.
Finds casts from pointers to ``struct`` or scalar type to pointers to ``struct`` type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I do not want to use the word "struct" as a C keyword, instead to describe the type created with struct keyword, in this case I think the code style is not needed.


- New :doc:`bugprone-invalid-enum-default-initialization
<clang-tidy/checks/bugprone/invalid-enum-default-initialization>` check.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
.. title:: clang-tidy - bugprone-cast-to-struct

bugprone-cast-to-struct
=======================

Finds casts from pointers to struct or scalar type to pointers to struct type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Finds casts from pointers to struct or scalar type to pointers to struct type.
Finds casts from pointers to ``struct`` or scalar type to pointers to ``struct`` type.


Casts between pointers to different structs can be unsafe because it is possible
to access uninitialized or undefined data after the cast. Cast from a
scalar-type pointer (which points often to an array or memory block) to a
``struct`` type pointer can be unsafe for similar reasons. This check warns at
pointer casts from any non-struct type to a struct type. No warning is produced
at cast from type ``void *`` (this is the usual way of allocating memory with
``malloc``-like functions) and ``char *`` types (which are used often as
pointers into data buffers). In addition, ``union`` types are excluded from the
check. It is possible to specify additional types to ignore. The check does not
take into account type compatibility or data layout, only the names of the
types.

.. code-block:: c

void test1(int *p) {
struct S1 *s;
s = (struct S1 *)p; // warn: 'int *' is converted to 'struct S1 *'
}

void test2(struct S1 *p) {
struct S2 *s;
s = (struct S2 *)p; // warn: 'struct S1 *' is converted to 'struct S2 *'
}

void test3(void) {
struct S1 *s;
s = (struct S1 *)calloc(1, sizeof(struct S1)); // no warning
}

Options
-------

.. option:: IgnoredCasts

Can contain a semicolon-separated list of type names that specify cast
types to ignore. The list should contain pairs of type names in a way that
the first type is the "from" type, the second is the "to" type in a cast
expression. The types in a pair and the pairs itself are separated by
`;` characters. The parts between `;` characters are matched as regular
expressions over the whole type name. For example
`struct S1 \*;struct T1 \*;short \*;struct T1 \*` specifies that the check
does not produce warning for casts from ``struct S1 *`` to ``struct T1 *``
and casts from ``short *`` to ``struct T1 *`` (the `*` character needs to be
escaped). The type name in the cast expression is matched without resolution
of ``typedef`` types.

Default value of the option is an empty string.
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Clang-Tidy Checks
:doc:`bugprone-bool-pointer-implicit-conversion <bugprone/bool-pointer-implicit-conversion>`, "Yes"
:doc:`bugprone-branch-clone <bugprone/branch-clone>`,
:doc:`bugprone-capturing-this-in-member-variable <bugprone/capturing-this-in-member-variable>`,
:doc:`bugprone-cast-to-struct <bugprone/cast-to-struct>`,
:doc:`bugprone-casting-through-void <bugprone/casting-through-void>`,
:doc:`bugprone-chained-comparison <bugprone/chained-comparison>`,
:doc:`bugprone-compare-pointer-to-member-virtual-function <bugprone/compare-pointer-to-member-virtual-function>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// RUN: %check_clang_tidy %s bugprone-cast-to-struct %t -- \
// RUN: -config="{CheckOptions: {bugprone-cast-to-struct.IgnoredCasts: 'int \*;struct S1 \*;TYPE \*;struct S2 \*;TYPE_P;struct S3 \*;struct S4 \*;struct S. \*'}}"

struct S1 {
};

struct S2 {
};

struct S3 {
};

struct S4 {
};

typedef int TYPE;
typedef int * TYPE_P;
typedef unsigned char uchar;

void test1(int *p0, TYPE *p1, TYPE_P p2) {
struct S1 *s1;
struct S4 *s4;

s1 = (struct S1 *)p0; // no warning
s1 = (struct S1 *)p1; // CHECK-MESSAGES: warning: casting a 'TYPE *' (aka 'int *') pointer to a 'struct S1 *'
s1 = (struct S1 *)p2; // CHECK-MESSAGES: warning: casting a 'TYPE_P' (aka 'int *') pointer to a 'struct S1 *'
s4 = (struct S4 *)p0; // CHECK-MESSAGES: warning: casting a 'int *' pointer to a 'struct S4 *'
}

void test2(int *p0, TYPE *p1, TYPE_P p2) {
struct S2 *s2;
struct S4 *s4;

s2 = (struct S2 *)p0; // CHECK-MESSAGES: warning: casting a 'int *' pointer to a 'struct S2 *'
s2 = (struct S2 *)p1; // no warning
s2 = (struct S2 *)p2; // CHECK-MESSAGES: warning: casting a 'TYPE_P' (aka 'int *') pointer to a 'struct S2 *'
s4 = (struct S4 *)p1; // CHECK-MESSAGES: warning: casting a 'TYPE *' (aka 'int *') pointer to a 'struct S4 *'
}

void test3(int *p0, TYPE *p1, TYPE_P p2) {
struct S3 *s3;
struct S4 *s4;

s3 = (struct S3 *)p0; // CHECK-MESSAGES: warning: casting a 'int *' pointer to a 'struct S3 *'
s3 = (struct S3 *)p1; // CHECK-MESSAGES: warning: casting a 'TYPE *' (aka 'int *') pointer to a 'struct S3 *'
s3 = (struct S3 *)p2; // no warning
s4 = (struct S4 *)p2; // CHECK-MESSAGES: warning: casting a 'TYPE_P' (aka 'int *') pointer to a 'struct S4 *'
}

void test_wildcard(struct S4 *p1, struct S1 *p2) {
struct S1 *s1;
struct S2 *s2;
s1 = (struct S1 *)p1;
s2 = (struct S2 *)p1;
s2 = (struct S2 *)p2; // CHECK-MESSAGES: warning: casting a 'struct S1 *' pointer to a 'struct S2 *'
}

void test_default_ignore(void *p1, char *p2, uchar *p3) {
struct S4 *s4;
s4 = (struct S4 *)p1;
s4 = (struct S4 *)p2;
s4 = (struct S4 *)p3;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// RUN: %check_clang_tidy %s bugprone-cast-to-struct %t

struct S1 {
int a;
};

struct S2 {
char a;
};

union U1 {
int a;
char b;
};

union U2 {
struct S1 a;
char b;
};

typedef struct S1 TyS1;
typedef struct S1 *TyPS1;

typedef union U1 *TyPU1;

typedef int int_t;
typedef int * int_ptr_t;

struct S1 *test_simple(short *p) {
return (struct S1 *)p;
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: casting a 'short *' pointer to a 'struct S1 *' pointer can lead to memory access errors or data corruption [bugprone-cast-to-struct]
struct S1 *s;
int i;
s = (struct S1 *)&i;
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: casting a 'int *' pointer to a 'struct S1 *' pointer can lead to memory access errors or data corruption [bugprone-cast-to-struct]
}

struct S1 *test_cast_from_void(void *p) {
return (struct S1 *)p;
}

struct S1 *test_cast_from_struct(struct S2 *p) {
return (struct S1 *)p;
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: casting a 'struct S2 *' pointer to a 'struct S1 *' pointer can lead to memory access errors or data corruption [bugprone-cast-to-struct]
}

TyPS1 test_cast_from_similar(struct S1 *p) {
return (TyPS1)p;
}

void test_typedef(short *p1, int_t *p2, int_ptr_t p3) {
TyS1 *a = (TyS1 *)p1;
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: casting a 'short *' pointer to a 'TyS1 *' (aka 'struct S1 *') pointer can lead to memory access errors or data corruption [bugprone-cast-to-struct]
TyPS1 b = (TyPS1)p1;
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: casting a 'short *' pointer to a 'TyPS1' (aka 'struct S1 *') pointer can lead to memory access errors or data corruption [bugprone-cast-to-struct]
struct S1 *c = (struct S1 *)p2;
// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: casting a 'int_t *' (aka 'int *') pointer to a 'struct S1 *' pointer can lead to memory access errors or data corruption [bugprone-cast-to-struct]
struct S1 *d = (struct S1 *)p3;
// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: casting a 'int_ptr_t' (aka 'int *') pointer to a 'struct S1 *' pointer can lead to memory access errors or data corruption [bugprone-cast-to-struct]
}

void test_union(short *p1, union U1 *p2, TyPU1 p3) {
union U1 *a = (union U1 *)p1;
struct S1 *b = (struct S1 *)p2;
struct S1 *c = (struct S1 *)p3;
}