[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
This commit is contained in:
committed by
GitHub
parent
b00ddce731
commit
a17b5bce8c
@@ -19,6 +19,8 @@
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/AST/RecursiveASTVisitor.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/Basic/LangOptions.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Lex/Lexer.h"
|
||||
#include "clang/Tooling/Refactoring.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
@@ -50,6 +52,85 @@ static const RecordDecl *findDefinition(StringRef RecordName,
|
||||
return selectFirst<RecordDecl>("recordDecl", Results);
|
||||
}
|
||||
|
||||
static bool declaresMultipleFieldsInStatement(const RecordDecl *Decl) {
|
||||
SourceLocation LastTypeLoc;
|
||||
for (const auto &Field : Decl->fields()) {
|
||||
SourceLocation TypeLoc =
|
||||
Field->getTypeSourceInfo()->getTypeLoc().getBeginLoc();
|
||||
if (LastTypeLoc.isValid() && TypeLoc == LastTypeLoc)
|
||||
return true;
|
||||
LastTypeLoc = TypeLoc;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool declaresMultipleFieldsInMacro(const RecordDecl *Decl,
|
||||
const SourceManager &SrcMgr) {
|
||||
SourceLocation LastMacroLoc;
|
||||
for (const auto &Field : Decl->fields()) {
|
||||
if (!Field->getLocation().isMacroID())
|
||||
continue;
|
||||
SourceLocation MacroLoc = SrcMgr.getExpansionLoc(Field->getLocation());
|
||||
if (LastMacroLoc.isValid() && MacroLoc == LastMacroLoc)
|
||||
return true;
|
||||
LastMacroLoc = MacroLoc;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool containsPreprocessorDirectives(const RecordDecl *Decl,
|
||||
const SourceManager &SrcMgr,
|
||||
const LangOptions &LangOpts) {
|
||||
std::pair<FileID, unsigned> FileAndOffset =
|
||||
SrcMgr.getDecomposedLoc(Decl->field_begin()->getBeginLoc());
|
||||
assert(!Decl->field_empty());
|
||||
auto LastField = Decl->field_begin();
|
||||
while (std::next(LastField) != Decl->field_end())
|
||||
++LastField;
|
||||
unsigned EndOffset = SrcMgr.getFileOffset(LastField->getEndLoc());
|
||||
StringRef SrcBuffer = SrcMgr.getBufferData(FileAndOffset.first);
|
||||
Lexer L(SrcMgr.getLocForStartOfFile(FileAndOffset.first), LangOpts,
|
||||
SrcBuffer.data(), SrcBuffer.data() + FileAndOffset.second,
|
||||
SrcBuffer.data() + SrcBuffer.size());
|
||||
IdentifierTable Identifiers(LangOpts);
|
||||
clang::Token T;
|
||||
while (!L.LexFromRawLexer(T) && L.getCurrentBufferOffset() < EndOffset) {
|
||||
if (T.getKind() == tok::hash) {
|
||||
L.LexFromRawLexer(T);
|
||||
if (T.getKind() == tok::raw_identifier) {
|
||||
clang::IdentifierInfo &II = Identifiers.get(T.getRawIdentifier());
|
||||
if (II.getPPKeywordID() != clang::tok::pp_not_keyword)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isSafeToRewrite(const RecordDecl *Decl, const ASTContext &Context) {
|
||||
// All following checks expect at least one field declaration.
|
||||
if (Decl->field_empty())
|
||||
return true;
|
||||
|
||||
// Don't attempt to rewrite if there is a declaration like 'int a, b;'.
|
||||
if (declaresMultipleFieldsInStatement(Decl))
|
||||
return false;
|
||||
|
||||
const SourceManager &SrcMgr = Context.getSourceManager();
|
||||
|
||||
// Don't attempt to rewrite if a single macro expansion creates multiple
|
||||
// fields.
|
||||
if (declaresMultipleFieldsInMacro(Decl, SrcMgr))
|
||||
return false;
|
||||
|
||||
// Prevent rewriting if there are preprocessor directives present between the
|
||||
// start of the first field and the end of last field.
|
||||
if (containsPreprocessorDirectives(Decl, SrcMgr, Context.getLangOpts()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Calculates the new order of fields.
|
||||
///
|
||||
/// \returns empty vector if the list of fields doesn't match the definition.
|
||||
@@ -345,6 +426,8 @@ public:
|
||||
const RecordDecl *RD = findDefinition(RecordName, Context);
|
||||
if (!RD)
|
||||
return;
|
||||
if (!isSafeToRewrite(RD, Context))
|
||||
return;
|
||||
SmallVector<unsigned, 4> NewFieldsOrder =
|
||||
getNewFieldsOrder(RD, DesiredFieldsOrder);
|
||||
if (NewFieldsOrder.empty())
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s
|
||||
|
||||
namespace bar {
|
||||
|
||||
#define FIELDS_DECL int x; int y; // CHECK: {{^#define FIELDS_DECL int x; int y;}}
|
||||
|
||||
// The order of fields should not change.
|
||||
struct Foo {
|
||||
FIELDS_DECL // CHECK: {{^ FIELDS_DECL}}
|
||||
int z; // CHECK-NEXT: {{^ int z;}}
|
||||
};
|
||||
|
||||
} // end namespace bar
|
||||
@@ -0,0 +1,11 @@
|
||||
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s
|
||||
|
||||
namespace bar {
|
||||
|
||||
// The order of fields should not change.
|
||||
struct Foo {
|
||||
int x, y; // CHECK: {{^ int x, y;}}
|
||||
double z; // CHECK-NEXT: {{^ double z;}}
|
||||
};
|
||||
|
||||
} // end namespace bar
|
||||
@@ -0,0 +1,15 @@
|
||||
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order y,x %s -- | FileCheck %s
|
||||
|
||||
namespace bar {
|
||||
|
||||
#define DEFINE_FOO
|
||||
|
||||
// This is okay to reorder.
|
||||
#ifdef DEFINE_FOO
|
||||
struct Foo {
|
||||
int x; // CHECK: {{^ int y;}}
|
||||
int y; // CHECK-NEXT: {{^ int x;}}
|
||||
};
|
||||
#endif
|
||||
|
||||
} // end namespace bar
|
||||
@@ -0,0 +1,15 @@
|
||||
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order y,x %s -- | FileCheck %s
|
||||
|
||||
namespace bar {
|
||||
|
||||
#define DEFINE_FIELDS
|
||||
|
||||
// This is okay to reorder.
|
||||
struct Foo {
|
||||
#ifdef DEFINE_FIELDS // CHECK: {{^#ifdef DEFINE_FIELDS}}
|
||||
int x; // CHECK-NEXT: {{^ int y;}}
|
||||
int y; // CHECK-NEXT: {{^ int x;}}
|
||||
#endif // CHECK-NEXT: {{^#endif}}
|
||||
};
|
||||
|
||||
} // end namespace bar
|
||||
@@ -0,0 +1,16 @@
|
||||
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s
|
||||
|
||||
namespace bar {
|
||||
|
||||
#define ADD_Z
|
||||
|
||||
// The order of fields should not change.
|
||||
struct Foo {
|
||||
int x; // CHECK: {{^ int x;}}
|
||||
int y; // CHECK-NEXT: {{^ int y;}}
|
||||
#ifdef ADD_Z // CHECK-NEXT: {{^#ifdef ADD_Z}}
|
||||
int z; // CHECK-NEXT: {{^ int z;}}
|
||||
#endif // CHECK-NEXT: {{^#endif}}
|
||||
};
|
||||
|
||||
} // end namespace bar
|
||||
Reference in New Issue
Block a user