Skip to content

Commit b8481a3

Browse files
committed
[clang-reorder-fields] Prevent rewriting unsupported cases
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 76bd5da commit b8481a3

File tree

6 files changed

+130
-0
lines changed

6 files changed

+130
-0
lines changed

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "clang/AST/Decl.h"
2020
#include "clang/AST/RecursiveASTVisitor.h"
2121
#include "clang/ASTMatchers/ASTMatchFinder.h"
22+
#include "clang/Basic/SourceLocation.h"
2223
#include "clang/Lex/Lexer.h"
2324
#include "clang/Tooling/Refactoring.h"
2425
#include "llvm/ADT/STLExtras.h"
@@ -50,6 +51,63 @@ static const RecordDecl *findDefinition(StringRef RecordName,
5051
return selectFirst<RecordDecl>("recordDecl", Results);
5152
}
5253

54+
static bool isSafeToRewrite(const RecordDecl *Decl, const ASTContext &Context) {
55+
// All following checks expect at least one field declaration.
56+
if (Decl->field_empty())
57+
return true;
58+
59+
// Don't attempt to rewrite if there is a declaration like 'int a, b;'.
60+
SourceLocation LastTypeLoc;
61+
for (const auto &Field : Decl->fields()) {
62+
SourceLocation TypeLoc =
63+
Field->getTypeSourceInfo()->getTypeLoc().getBeginLoc();
64+
if (LastTypeLoc.isValid() && TypeLoc == LastTypeLoc)
65+
return false;
66+
LastTypeLoc = TypeLoc;
67+
}
68+
69+
// Don't attempt to rewrite if a single macro expansion creates multiple
70+
// fields.
71+
SourceLocation LastMacroLoc;
72+
for (const auto &Field : Decl->fields()) {
73+
if (!Field->getLocation().isMacroID())
74+
continue;
75+
SourceLocation MacroLoc =
76+
Context.getSourceManager().getExpansionLoc(Field->getLocation());
77+
if (LastMacroLoc.isValid() && MacroLoc == LastMacroLoc)
78+
return false;
79+
LastMacroLoc = MacroLoc;
80+
}
81+
82+
// Prevent rewriting if there are preprocessor directives present between the
83+
// start of the first field and the end of last field.
84+
const SourceManager &SM = Context.getSourceManager();
85+
std::pair<FileID, unsigned> FileAndOffset =
86+
SM.getDecomposedLoc(Decl->field_begin()->getBeginLoc());
87+
auto LastField = Decl->field_begin();
88+
while (std::next(LastField) != Decl->field_end())
89+
++LastField;
90+
unsigned EndOffset = SM.getFileOffset(LastField->getEndLoc());
91+
StringRef SrcBuffer = SM.getBufferData(FileAndOffset.first);
92+
Lexer L(SM.getLocForStartOfFile(FileAndOffset.first), Context.getLangOpts(),
93+
SrcBuffer.data(), SrcBuffer.data() + FileAndOffset.second,
94+
SrcBuffer.data() + SrcBuffer.size());
95+
IdentifierTable Identifiers(Context.getLangOpts());
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 false;
104+
}
105+
}
106+
}
107+
108+
return true;
109+
}
110+
53111
/// Calculates the new order of fields.
54112
///
55113
/// \returns empty vector if the list of fields doesn't match the definition.
@@ -341,6 +399,8 @@ class ReorderingConsumer : public ASTConsumer {
341399
const RecordDecl *RD = findDefinition(RecordName, Context);
342400
if (!RD)
343401
return;
402+
if (!isSafeToRewrite(RD, Context))
403+
return;
344404
SmallVector<unsigned, 4> NewFieldsOrder =
345405
getNewFieldsOrder(RD, DesiredFieldsOrder);
346406
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 y; // CHECK-NEXT: {{^ int y;}}
11+
int x; // 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)