Skip to content

Commit a17b5bc

Browse files
authored
[clang-reorder-fields] Prevent rewriting unsupported cases (#142149)
Add checks to prevent rewriting when doing so might result in incorrect code. The following cases are checked: - There are multiple field declarations in one statement like `int a, b` - Multiple fields are created from a single macro expansion - Preprocessor directives are present in the struct
1 parent b00ddce commit a17b5bc

File tree

6 files changed

+153
-0
lines changed

6 files changed

+153
-0
lines changed

clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include "clang/AST/Decl.h"
2020
#include "clang/AST/RecursiveASTVisitor.h"
2121
#include "clang/ASTMatchers/ASTMatchFinder.h"
22+
#include "clang/Basic/LangOptions.h"
23+
#include "clang/Basic/SourceLocation.h"
2224
#include "clang/Lex/Lexer.h"
2325
#include "clang/Tooling/Refactoring.h"
2426
#include "llvm/ADT/STLExtras.h"
@@ -50,6 +52,85 @@ static const RecordDecl *findDefinition(StringRef RecordName,
5052
return selectFirst<RecordDecl>("recordDecl", Results);
5153
}
5254

55+
static bool declaresMultipleFieldsInStatement(const RecordDecl *Decl) {
56+
SourceLocation LastTypeLoc;
57+
for (const auto &Field : Decl->fields()) {
58+
SourceLocation TypeLoc =
59+
Field->getTypeSourceInfo()->getTypeLoc().getBeginLoc();
60+
if (LastTypeLoc.isValid() && TypeLoc == LastTypeLoc)
61+
return true;
62+
LastTypeLoc = TypeLoc;
63+
}
64+
return false;
65+
}
66+
67+
static bool declaresMultipleFieldsInMacro(const RecordDecl *Decl,
68+
const SourceManager &SrcMgr) {
69+
SourceLocation LastMacroLoc;
70+
for (const auto &Field : Decl->fields()) {
71+
if (!Field->getLocation().isMacroID())
72+
continue;
73+
SourceLocation MacroLoc = SrcMgr.getExpansionLoc(Field->getLocation());
74+
if (LastMacroLoc.isValid() && MacroLoc == LastMacroLoc)
75+
return true;
76+
LastMacroLoc = MacroLoc;
77+
}
78+
return false;
79+
}
80+
81+
static bool containsPreprocessorDirectives(const RecordDecl *Decl,
82+
const SourceManager &SrcMgr,
83+
const LangOptions &LangOpts) {
84+
std::pair<FileID, unsigned> FileAndOffset =
85+
SrcMgr.getDecomposedLoc(Decl->field_begin()->getBeginLoc());
86+
assert(!Decl->field_empty());
87+
auto LastField = Decl->field_begin();
88+
while (std::next(LastField) != Decl->field_end())
89+
++LastField;
90+
unsigned EndOffset = SrcMgr.getFileOffset(LastField->getEndLoc());
91+
StringRef SrcBuffer = SrcMgr.getBufferData(FileAndOffset.first);
92+
Lexer L(SrcMgr.getLocForStartOfFile(FileAndOffset.first), LangOpts,
93+
SrcBuffer.data(), SrcBuffer.data() + FileAndOffset.second,
94+
SrcBuffer.data() + SrcBuffer.size());
95+
IdentifierTable Identifiers(LangOpts);
96+
clang::Token T;
97+
while (!L.LexFromRawLexer(T) && L.getCurrentBufferOffset() < EndOffset) {
98+
if (T.getKind() == tok::hash) {
99+
L.LexFromRawLexer(T);
100+
if (T.getKind() == tok::raw_identifier) {
101+
clang::IdentifierInfo &II = Identifiers.get(T.getRawIdentifier());
102+
if (II.getPPKeywordID() != clang::tok::pp_not_keyword)
103+
return true;
104+
}
105+
}
106+
}
107+
return false;
108+
}
109+
110+
static bool isSafeToRewrite(const RecordDecl *Decl, const ASTContext &Context) {
111+
// All following checks expect at least one field declaration.
112+
if (Decl->field_empty())
113+
return true;
114+
115+
// Don't attempt to rewrite if there is a declaration like 'int a, b;'.
116+
if (declaresMultipleFieldsInStatement(Decl))
117+
return false;
118+
119+
const SourceManager &SrcMgr = Context.getSourceManager();
120+
121+
// Don't attempt to rewrite if a single macro expansion creates multiple
122+
// fields.
123+
if (declaresMultipleFieldsInMacro(Decl, SrcMgr))
124+
return false;
125+
126+
// Prevent rewriting if there are preprocessor directives present between the
127+
// start of the first field and the end of last field.
128+
if (containsPreprocessorDirectives(Decl, SrcMgr, Context.getLangOpts()))
129+
return false;
130+
131+
return true;
132+
}
133+
53134
/// Calculates the new order of fields.
54135
///
55136
/// \returns empty vector if the list of fields doesn't match the definition.
@@ -345,6 +426,8 @@ class ReorderingConsumer : public ASTConsumer {
345426
const RecordDecl *RD = findDefinition(RecordName, Context);
346427
if (!RD)
347428
return;
429+
if (!isSafeToRewrite(RD, Context))
430+
return;
348431
SmallVector<unsigned, 4> NewFieldsOrder =
349432
getNewFieldsOrder(RD, DesiredFieldsOrder);
350433
if (NewFieldsOrder.empty())
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s
2+
3+
namespace bar {
4+
5+
#define FIELDS_DECL int x; int y; // CHECK: {{^#define FIELDS_DECL int x; int y;}}
6+
7+
// The order of fields should not change.
8+
struct Foo {
9+
FIELDS_DECL // CHECK: {{^ FIELDS_DECL}}
10+
int z; // CHECK-NEXT: {{^ int z;}}
11+
};
12+
13+
} // end namespace bar
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s
2+
3+
namespace bar {
4+
5+
// The order of fields should not change.
6+
struct Foo {
7+
int x, y; // CHECK: {{^ int x, y;}}
8+
double z; // CHECK-NEXT: {{^ double z;}}
9+
};
10+
11+
} // end namespace bar
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order y,x %s -- | FileCheck %s
2+
3+
namespace bar {
4+
5+
#define DEFINE_FOO
6+
7+
// This is okay to reorder.
8+
#ifdef DEFINE_FOO
9+
struct Foo {
10+
int x; // CHECK: {{^ int y;}}
11+
int y; // CHECK-NEXT: {{^ int x;}}
12+
};
13+
#endif
14+
15+
} // end namespace bar
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order y,x %s -- | FileCheck %s
2+
3+
namespace bar {
4+
5+
#define DEFINE_FIELDS
6+
7+
// This is okay to reorder.
8+
struct Foo {
9+
#ifdef DEFINE_FIELDS // CHECK: {{^#ifdef DEFINE_FIELDS}}
10+
int x; // CHECK-NEXT: {{^ int y;}}
11+
int y; // CHECK-NEXT: {{^ int x;}}
12+
#endif // CHECK-NEXT: {{^#endif}}
13+
};
14+
15+
} // end namespace bar
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s
2+
3+
namespace bar {
4+
5+
#define ADD_Z
6+
7+
// The order of fields should not change.
8+
struct Foo {
9+
int x; // CHECK: {{^ int x;}}
10+
int y; // CHECK-NEXT: {{^ int y;}}
11+
#ifdef ADD_Z // CHECK-NEXT: {{^#ifdef ADD_Z}}
12+
int z; // CHECK-NEXT: {{^ int z;}}
13+
#endif // CHECK-NEXT: {{^#endif}}
14+
};
15+
16+
} // end namespace bar

0 commit comments

Comments
 (0)