Skip to content

Commit 95634ba

Browse files
committed
[clang-tidy] Add check 'bugprone-cast-to-struct'
1 parent e50372c commit 95634ba

File tree

9 files changed

+290
-0
lines changed

9 files changed

+290
-0
lines changed

clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "BranchCloneCheck.h"
1919
#include "CapturingThisInMemberVariableCheck.h"
2020
#include "CastingThroughVoidCheck.h"
21+
#include "CastToStructCheck.h"
2122
#include "ChainedComparisonCheck.h"
2223
#include "ComparePointerToMemberVirtualFunctionCheck.h"
2324
#include "CopyConstructorInitCheck.h"
@@ -125,6 +126,7 @@ class BugproneModule : public ClangTidyModule {
125126
"bugprone-capturing-this-in-member-variable");
126127
CheckFactories.registerCheck<CastingThroughVoidCheck>(
127128
"bugprone-casting-through-void");
129+
CheckFactories.registerCheck<CastToStructCheck>("bugprone-cast-to-struct");
128130
CheckFactories.registerCheck<ChainedComparisonCheck>(
129131
"bugprone-chained-comparison");
130132
CheckFactories.registerCheck<ComparePointerToMemberVirtualFunctionCheck>(

clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ add_clang_library(clangTidyBugproneModule STATIC
1414
BugproneTidyModule.cpp
1515
CapturingThisInMemberVariableCheck.cpp
1616
CastingThroughVoidCheck.cpp
17+
CastToStructCheck.cpp
1718
ChainedComparisonCheck.cpp
1819
ComparePointerToMemberVirtualFunctionCheck.cpp
1920
CopyConstructorInitCheck.cpp
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//===--- CastToStructCheck.cpp - clang-tidy -------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "CastToStructCheck.h"
10+
#include "../utils/Matchers.h"
11+
#include "../utils/OptionsUtils.h"
12+
#include "clang/ASTMatchers/ASTMatchFinder.h"
13+
14+
using namespace clang::ast_matchers;
15+
16+
namespace clang::tidy::bugprone {
17+
18+
CastToStructCheck::CastToStructCheck(StringRef Name, ClangTidyContext *Context)
19+
: ClangTidyCheck(Name, Context),
20+
IgnoredFunctions(
21+
utils::options::parseStringList(Options.get("IgnoredFunctions", ""))),
22+
IgnoredFromTypes(
23+
utils::options::parseStringList(Options.get("IgnoredFromTypes", ""))),
24+
IgnoredToTypes(
25+
utils::options::parseStringList(Options.get("IgnoredToTypes", ""))) {}
26+
27+
void CastToStructCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
28+
Options.store(Opts, "IgnoredFunctions",
29+
utils::options::serializeStringList(IgnoredFunctions));
30+
Options.store(Opts, "IgnoredFromTypes",
31+
utils::options::serializeStringList(IgnoredFromTypes));
32+
Options.store(Opts, "IgnoredToTypes",
33+
utils::options::serializeStringList(IgnoredToTypes));
34+
}
35+
36+
void CastToStructCheck::registerMatchers(MatchFinder *Finder) {
37+
auto FromPointee =
38+
qualType(hasUnqualifiedDesugaredType(type().bind("FromType")),
39+
unless(qualType(matchers::matchesAnyListedTypeName(
40+
IgnoredFromTypes, false))))
41+
.bind("FromPointee");
42+
auto ToPointee =
43+
qualType(hasUnqualifiedDesugaredType(recordType().bind("ToType")),
44+
unless(qualType(
45+
matchers::matchesAnyListedTypeName(IgnoredToTypes, false))))
46+
.bind("ToPointee");
47+
auto FromPtrType = qualType(pointsTo(FromPointee)).bind("FromPtr");
48+
auto ToPtrType = qualType(pointsTo(ToPointee)).bind("ToPtr");
49+
Finder->addMatcher(
50+
cStyleCastExpr(hasSourceExpression(hasType(FromPtrType)),
51+
hasType(ToPtrType),
52+
unless(hasAncestor(functionDecl(
53+
matchers::matchesAnyListedName(IgnoredFunctions)))))
54+
.bind("CastExpr"),
55+
this);
56+
}
57+
58+
void CastToStructCheck::check(const MatchFinder::MatchResult &Result) {
59+
const auto *const FoundCastExpr =
60+
Result.Nodes.getNodeAs<CStyleCastExpr>("CastExpr");
61+
const auto *const FromPtr = Result.Nodes.getNodeAs<QualType>("FromPtr");
62+
const auto *const ToPtr = Result.Nodes.getNodeAs<QualType>("ToPtr");
63+
const auto *const FromPointee =
64+
Result.Nodes.getNodeAs<QualType>("FromPointee");
65+
const auto *const ToPointee = Result.Nodes.getNodeAs<QualType>("ToPointee");
66+
const auto *const FromType = Result.Nodes.getNodeAs<Type>("FromType");
67+
const auto *const ToType = Result.Nodes.getNodeAs<RecordType>("ToType");
68+
if (!FromPointee || !ToPointee)
69+
return;
70+
if (FromType->isVoidType() || FromType->isUnionType() ||
71+
ToType->isUnionType())
72+
return;
73+
if (FromType == ToType)
74+
return;
75+
diag(FoundCastExpr->getExprLoc(),
76+
"casting a %0 pointer to a "
77+
"%1 pointer and accessing a field can lead to memory "
78+
"access errors or data corruption")
79+
<< *FromPtr << *ToPtr;
80+
}
81+
82+
} // namespace clang::tidy::bugprone
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//===--- CastToStructCheck.h - clang-tidy -----------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CASTTOSTRUCTCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CASTTOSTRUCTCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
15+
16+
/// Finds casts from pointers to struct or scalar type to pointers to struct
17+
/// type.
18+
///
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/cast-to-struct.html
21+
class CastToStructCheck : public ClangTidyCheck {
22+
public:
23+
CastToStructCheck(StringRef Name, ClangTidyContext *Context);
24+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
25+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
26+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
27+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
28+
return LangOpts.C99;
29+
}
30+
31+
private:
32+
std::vector<llvm::StringRef> IgnoredFunctions;
33+
std::vector<llvm::StringRef> IgnoredFromTypes;
34+
std::vector<llvm::StringRef> IgnoredToTypes;
35+
};
36+
37+
} // namespace clang::tidy::bugprone
38+
39+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CASTTOSTRUCTCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ Improvements to clang-tidy
112112
New checks
113113
^^^^^^^^^^
114114

115+
- New :doc:`bugprone-cast-to-struct
116+
<clang-tidy/checks/bugprone/cast-to-struct>` check.
117+
118+
Finds casts from pointers to struct or scalar type to pointers to struct type.
119+
115120
- New :doc:`bugprone-invalid-enum-default-initialization
116121
<clang-tidy/checks/bugprone/invalid-enum-default-initialization>` check.
117122

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
.. title:: clang-tidy - bugprone-cast-to-struct
2+
3+
bugprone-cast-to-struct
4+
=======================
5+
6+
Finds casts from pointers to struct or scalar type to pointers to struct type.
7+
8+
Casts between pointers to different structs can be unsafe because it is possible
9+
to access uninitialized or undefined data after the cast. There may be issues
10+
with type compatibility or data alignment. Cast from a pointer to a scalar type
11+
(which points often to an array or memory block) to a `struct` type pointer can
12+
be unsafe for similar reasons. This check warns at casts from any non-`struct`
13+
type to a `struct` type. No warning is produced at cast from type `void *` (this
14+
is the usual way of allocating memory with `malloc`-like functions). It is
15+
possible to specify additional types to ignore by the check. In addition,
16+
`union` types are completely excluded from the check. The check does not take
17+
into account type compatibility or data layout, only the names of the types.
18+
19+
.. code-block:: c
20+
21+
void test1(char *p) {
22+
struct S1 *s;
23+
s = (struct S1 *)p; // warn: 'char *' is converted to 'struct S1 *'
24+
}
25+
26+
void test2(struct S1 *p) {
27+
struct S2 *s;
28+
s = (struct S2 *)p; // warn: 'struct S1 *' is converted to 'struct S2 *'
29+
}
30+
31+
void test3(void) {
32+
struct S1 *s;
33+
s = (struct S1 *)calloc(1, sizeof(struct S1)); // no warning
34+
}
35+
36+
Options
37+
-------
38+
39+
.. option:: IgnoredFromTypes
40+
41+
Semicolon-separated list of types for which the checker should not warn if
42+
encountered at cast source. Can contain regular expressions. The `*`
43+
character (for pointer type) is not needed in the type names.
44+
45+
.. option:: IgnoredToTypes
46+
47+
Semicolon-separated list of types for which the checker should not warn if
48+
encountered at cast destination. Can contain regular expressions. The `*`
49+
character (for pointer type) is not needed in the type names.
50+
51+
.. option:: IgnoredFunctions
52+
53+
List of function names from which the checker should produce no warnings. Can
54+
contain regular expressions.

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Clang-Tidy Checks
8585
:doc:`bugprone-bool-pointer-implicit-conversion <bugprone/bool-pointer-implicit-conversion>`, "Yes"
8686
:doc:`bugprone-branch-clone <bugprone/branch-clone>`,
8787
:doc:`bugprone-capturing-this-in-member-variable <bugprone/capturing-this-in-member-variable>`,
88+
:doc:`bugprone-cast-to-struct <bugprone/cast-to-struct>`,
8889
:doc:`bugprone-casting-through-void <bugprone/casting-through-void>`,
8990
:doc:`bugprone-chained-comparison <bugprone/chained-comparison>`,
9091
:doc:`bugprone-compare-pointer-to-member-virtual-function <bugprone/compare-pointer-to-member-virtual-function>`,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %check_clang_tidy -check-suffixes=FUNC %s bugprone-cast-to-struct %t -- \
2+
// RUN: -config="{CheckOptions: {bugprone-cast-to-struct.IgnoredFunctions: 'ignored_f$'}}"
3+
// RUN: %check_clang_tidy -check-suffixes=FROM-TY %s bugprone-cast-to-struct %t -- \
4+
// RUN: -config="{CheckOptions: {bugprone-cast-to-struct.IgnoredFromTypes: 'int'}}"
5+
// RUN: %check_clang_tidy -check-suffixes=TO-TY %s bugprone-cast-to-struct %t -- \
6+
// RUN: -config="{CheckOptions: {bugprone-cast-to-struct.IgnoredToTypes: 'IgnoredType'}}"
7+
8+
struct IgnoredType {
9+
int a;
10+
};
11+
12+
struct OtherType {
13+
int a;
14+
int b;
15+
};
16+
17+
void ignored_f(char *p) {
18+
struct OtherType *p1;
19+
p1 = (struct OtherType *)p;
20+
// CHECK-MESSAGES-FROM-TY: :[[@LINE-1]]:8: warning: casting a 'char *' pointer to a 'struct OtherType *' pointer and accessing a field can lead to memory access errors or data corruption
21+
// CHECK-MESSAGES-TO-TY: :[[@LINE-2]]:8: warning: casting a 'char *' pointer to a 'struct OtherType *' pointer and accessing a field can lead to memory access errors or data corruption
22+
}
23+
24+
void ignored_from_type(int *p) {
25+
struct OtherType *p1;
26+
p1 = (struct OtherType *)p;
27+
// CHECK-MESSAGES-FUNC: :[[@LINE-1]]:8: warning: casting a 'int *' pointer to a 'struct OtherType *' pointer and accessing a field can lead to memory access errors or data corruption
28+
// CHECK-MESSAGES-TO-TY: :[[@LINE-2]]:8: warning: casting a 'int *' pointer to a 'struct OtherType *' pointer and accessing a field can lead to memory access errors or data corruption
29+
}
30+
31+
void ignored_to_type(char *p) {
32+
struct IgnoredType *p1;
33+
p1 = (struct IgnoredType *)p;
34+
// CHECK-MESSAGES-FUNC: :[[@LINE-1]]:8: warning: casting a 'char *' pointer to a 'struct IgnoredType *' pointer and accessing a field can lead to memory access errors or data corruption
35+
// CHECK-MESSAGES-FROM-TY: :[[@LINE-2]]:8: warning: casting a 'char *' pointer to a 'struct IgnoredType *' pointer and accessing a field can lead to memory access errors or data corruption
36+
}
37+
38+
struct OtherType *test_void_is_always_ignored(void *p) {
39+
return (struct OtherType *)p;
40+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// RUN: %check_clang_tidy %s bugprone-cast-to-struct %t
2+
3+
struct S1 {
4+
int a;
5+
};
6+
7+
struct S2 {
8+
char a;
9+
};
10+
11+
union U1 {
12+
int a;
13+
char b;
14+
};
15+
16+
union U2 {
17+
struct S1 a;
18+
char b;
19+
};
20+
21+
typedef struct S1 TyS1;
22+
typedef struct S1 *TyPS1;
23+
24+
typedef union U1 *TyPU1;
25+
26+
typedef int int_t;
27+
typedef int * int_ptr_t;
28+
29+
struct S1 *test_simple(char *p) {
30+
return (struct S1 *)p;
31+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: casting a 'char *' pointer to a 'struct S1 *' pointer and accessing a field can lead to memory access errors or data corruption [bugprone-cast-to-struct]
32+
struct S1 *s;
33+
int i;
34+
s = (struct S1 *)&i;
35+
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: casting a 'int *' pointer to a 'struct S1 *' pointer and accessing a field can lead to memory access errors or data corruption [bugprone-cast-to-struct]
36+
}
37+
38+
struct S1 *test_cast_from_void(void *p) {
39+
return (struct S1 *)p;
40+
}
41+
42+
struct S1 *test_cast_from_struct(struct S2 *p) {
43+
return (struct S1 *)p;
44+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: casting a 'struct S2 *' pointer to a 'struct S1 *' pointer and accessing a field can lead to memory access errors or data corruption [bugprone-cast-to-struct]
45+
}
46+
47+
TyPS1 test_cast_from_similar(struct S1 *p) {
48+
return (TyPS1)p;
49+
}
50+
51+
void test_typedef(char *p1, int_t *p2, int_ptr_t p3) {
52+
TyS1 *a = (TyS1 *)p1;
53+
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: casting a 'char *' pointer to a 'TyS1 *' (aka 'struct S1 *') pointer and accessing a field can lead to memory access errors or data corruption [bugprone-cast-to-struct]
54+
TyPS1 b = (TyPS1)p1;
55+
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: casting a 'char *' pointer to a 'TyPS1' (aka 'struct S1 *') pointer and accessing a field can lead to memory access errors or data corruption [bugprone-cast-to-struct]
56+
struct S1 *c = (struct S1 *)p2;
57+
// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: casting a 'int_t *' (aka 'int *') pointer to a 'struct S1 *' pointer and accessing a field can lead to memory access errors or data corruption [bugprone-cast-to-struct]
58+
struct S1 *d = (struct S1 *)p3;
59+
// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: casting a 'int_ptr_t' (aka 'int *') pointer to a 'struct S1 *' pointer and accessing a field can lead to memory access errors or data corruption [bugprone-cast-to-struct]
60+
}
61+
62+
void test_union(char *p1, union U1 *p2, TyPU1 p3) {
63+
union U1 *a = (union U1 *)p1;
64+
struct S1 *b = (struct S1 *)p2;
65+
struct S1 *c = (struct S1 *)p3;
66+
}

0 commit comments

Comments
 (0)