Skip to content

Commit

Permalink
[ODRHash] Hash RecordDecl and diagnose discovered mismatches.
Browse files Browse the repository at this point in the history
When two modules contain struct/union with the same name, check the
definitions are equivalent and diagnose if they are not. This is similar
to `CXXRecordDecl` where we already discover and diagnose mismatches.

rdar://problem/56764293

Differential Revision: https://reviews.llvm.org/D71734
  • Loading branch information
vsapsai committed Jan 19, 2023
1 parent 9bdcf87 commit 160bc16
Show file tree
Hide file tree
Showing 13 changed files with 618 additions and 3 deletions.
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ code bases.
these definitions were allowed. Note that such definitions are ODR
violations if the header is included more than once.

- Clang now diagnoses if structs/unions with the same name are different in
different used modules. Behavior in C and Objective-C language modes now is
the same as in C++.

What's New in Clang |release|?
==============================
Some of the major new features and improvements to Clang are listed
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3998,6 +3998,7 @@ class RecordDecl : public TagDecl {
// to save some space. Use the provided accessors to access it.
public:
friend class DeclContext;
friend class ASTDeclReader;
/// Enum that represents the different ways arguments are passed to and
/// returned from function calls. This takes into account the target-specific
/// and version-specific rules along with the rules determined by the
Expand Down Expand Up @@ -4253,9 +4254,16 @@ class RecordDecl : public TagDecl {
/// nullptr is returned if no named data member exists.
const FieldDecl *findFirstNamedDataMember() const;

/// Get precomputed ODRHash or add a new one.
unsigned getODRHash();

private:
/// Deserialize just the fields.
void LoadFieldsFromExternalStorage() const;

/// True if a valid hash is stored in ODRHash.
bool hasODRHash() const { return RecordDeclBits.ODRHash; }
void setODRHash(unsigned Hash) { RecordDeclBits.ODRHash = Hash; }
};

class FileScopeAsmDecl : public Decl {
Expand Down
6 changes: 5 additions & 1 deletion clang/include/clang/AST/DeclBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -1577,10 +1577,14 @@ class DeclContext {

/// Indicates whether this struct has had its field layout randomized.
uint64_t IsRandomized : 1;

/// True if a valid hash is stored in ODRHash. This should shave off some
/// extra storage and prevent CXXRecordDecl to store unused bits.
uint64_t ODRHash : 26;
};

/// Number of non-inherited bits in RecordDeclBitfields.
enum { NumRecordDeclBits = 15 };
enum { NumRecordDeclBits = 41 };

/// Stores the bits used by OMPDeclareReductionDecl.
/// If modified NumOMPDeclareReductionDeclBits and the accessor
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/AST/ODRDiagsEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ class ODRDiagsEmitter {
const CXXRecordDecl *SecondRecord,
const struct CXXRecordDecl::DefinitionData *SecondDD) const;

/// Diagnose ODR mismatch between 2 RecordDecl that are not CXXRecordDecl.
///
/// Returns true if found a mismatch and diagnosed it.
bool diagnoseMismatch(const RecordDecl *FirstRecord,
const RecordDecl *SecondRecord) const;

/// Diagnose ODR mismatch between 2 ObjCProtocolDecl.
///
/// Returns true if found a mismatch and diagnosed it.
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/AST/ODRHash.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ class ODRHash {
// more information than the AddDecl class.
void AddCXXRecordDecl(const CXXRecordDecl *Record);

// Use this for ODR checking records in C/Objective-C between modules. This
// method compares more information than the AddDecl class.
void AddRecordDecl(const RecordDecl *Record);

// Use this for ODR checking functions between modules. This method compares
// more information than the AddDecl class. SkipBody will process the
// hash as if the function has no body.
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Serialization/ASTReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,10 @@ class ASTReader
llvm::SmallDenseMap<CXXRecordDecl *, llvm::SmallVector<DataPointers, 2>, 2>
PendingOdrMergeFailures;

/// C/ObjC record definitions in which we found an ODR violation.
llvm::SmallDenseMap<RecordDecl *, llvm::SmallVector<RecordDecl *, 2>, 2>
PendingRecordOdrMergeFailures;

/// Function definitions in which we found an ODR violation.
llvm::SmallDenseMap<FunctionDecl *, llvm::SmallVector<FunctionDecl *, 2>, 2>
PendingFunctionOdrMergeFailures;
Expand Down
14 changes: 14 additions & 0 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4711,6 +4711,7 @@ RecordDecl::RecordDecl(Kind DK, TagKind TK, const ASTContext &C,
setParamDestroyedInCallee(false);
setArgPassingRestrictions(APK_CanPassInRegs);
setIsRandomized(false);
setODRHash(0);
}

RecordDecl *RecordDecl::Create(const ASTContext &C, TagKind TK, DeclContext *DC,
Expand Down Expand Up @@ -4885,6 +4886,19 @@ const FieldDecl *RecordDecl::findFirstNamedDataMember() const {
return nullptr;
}

unsigned RecordDecl::getODRHash() {
if (hasODRHash())
return RecordDeclBits.ODRHash;

// Only calculate hash on first call of getODRHash per record.
ODRHash Hash;
Hash.AddRecordDecl(this);
// For RecordDecl the ODRHash is stored in the remaining 26
// bit of RecordDeclBits, adjust the hash to accomodate.
setODRHash(Hash.CalculateHash() >> 6);
return RecordDeclBits.ODRHash;
}

//===----------------------------------------------------------------------===//
// BlockDecl Implementation
//===----------------------------------------------------------------------===//
Expand Down
95 changes: 95 additions & 0 deletions clang/lib/AST/ODRDiagsEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1547,6 +1547,101 @@ bool ODRDiagsEmitter::diagnoseMismatch(
return true;
}

bool ODRDiagsEmitter::diagnoseMismatch(const RecordDecl *FirstRecord,
const RecordDecl *SecondRecord) const {
if (FirstRecord == SecondRecord)
return false;

std::string FirstModule = getOwningModuleNameForDiagnostic(FirstRecord);
std::string SecondModule = getOwningModuleNameForDiagnostic(SecondRecord);

auto PopulateHashes = [](DeclHashes &Hashes, const RecordDecl *Record,
const DeclContext *DC) {
for (const Decl *D : Record->decls()) {
if (!ODRHash::isSubDeclToBeProcessed(D, DC))
continue;
Hashes.emplace_back(D, computeODRHash(D));
}
};

DeclHashes FirstHashes;
DeclHashes SecondHashes;
const DeclContext *DC = FirstRecord;
PopulateHashes(FirstHashes, FirstRecord, DC);
PopulateHashes(SecondHashes, SecondRecord, DC);

DiffResult DR = FindTypeDiffs(FirstHashes, SecondHashes);
ODRMismatchDecl FirstDiffType = DR.FirstDiffType;
ODRMismatchDecl SecondDiffType = DR.SecondDiffType;
const Decl *FirstDecl = DR.FirstDecl;
const Decl *SecondDecl = DR.SecondDecl;

if (FirstDiffType == Other || SecondDiffType == Other) {
diagnoseSubMismatchUnexpected(DR, FirstRecord, FirstModule, SecondRecord,
SecondModule);
return true;
}

if (FirstDiffType != SecondDiffType) {
diagnoseSubMismatchDifferentDeclKinds(DR, FirstRecord, FirstModule,
SecondRecord, SecondModule);
return true;
}

assert(FirstDiffType == SecondDiffType);
switch (FirstDiffType) {
// Already handled.
case EndOfClass:
case Other:
// C++ only, invalid in this context.
case PublicSpecifer:
case PrivateSpecifer:
case ProtectedSpecifer:
case StaticAssert:
case CXXMethod:
case TypeAlias:
case Friend:
case FunctionTemplate:
// Cannot be contained by RecordDecl, invalid in this context.
case ObjCMethod:
case ObjCProperty:
llvm_unreachable("Invalid diff type");

case Field: {
if (diagnoseSubMismatchField(FirstRecord, FirstModule, SecondModule,
cast<FieldDecl>(FirstDecl),
cast<FieldDecl>(SecondDecl)))
return true;
break;
}
case TypeDef: {
if (diagnoseSubMismatchTypedef(FirstRecord, FirstModule, SecondModule,
cast<TypedefNameDecl>(FirstDecl),
cast<TypedefNameDecl>(SecondDecl),
/*IsTypeAlias=*/false))
return true;
break;
}
case Var: {
if (diagnoseSubMismatchVar(FirstRecord, FirstModule, SecondModule,
cast<VarDecl>(FirstDecl),
cast<VarDecl>(SecondDecl)))
return true;
break;
}
}

Diag(FirstDecl->getLocation(),
diag::err_module_odr_violation_mismatch_decl_unknown)
<< FirstRecord << FirstModule.empty() << FirstModule << FirstDiffType
<< FirstDecl->getSourceRange();
Diag(SecondDecl->getLocation(),
diag::note_module_odr_violation_mismatch_decl_unknown)
<< SecondModule.empty() << SecondModule << FirstDiffType
<< SecondDecl->getSourceRange();
return true;
}

bool ODRDiagsEmitter::diagnoseMismatch(
const FunctionDecl *FirstFunction,
const FunctionDecl *SecondFunction) const {
Expand Down
18 changes: 18 additions & 0 deletions clang/lib/AST/ODRHash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,24 @@ void ODRHash::AddCXXRecordDecl(const CXXRecordDecl *Record) {
}
}

void ODRHash::AddRecordDecl(const RecordDecl *Record) {
assert(!isa<CXXRecordDecl>(Record) &&
"For CXXRecordDecl should call AddCXXRecordDecl.");
AddDecl(Record);

// Filter out sub-Decls which will not be processed in order to get an
// accurate count of Decl's.
llvm::SmallVector<const Decl *, 16> Decls;
for (Decl *SubDecl : Record->decls()) {
if (isSubDeclToBeProcessed(SubDecl, Record))
Decls.push_back(SubDecl);
}

ID.AddInteger(Decls.size());
for (const Decl *SubDecl : Decls)
AddSubDecl(SubDecl);
}

void ODRHash::AddFunctionDecl(const FunctionDecl *Function,
bool SkipBody) {
assert(Function && "Expecting non-null pointer.");
Expand Down
35 changes: 33 additions & 2 deletions clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9504,6 +9504,7 @@ void ASTReader::finishPendingActions() {

void ASTReader::diagnoseOdrViolations() {
if (PendingOdrMergeFailures.empty() && PendingOdrMergeChecks.empty() &&
PendingRecordOdrMergeFailures.empty() &&
PendingFunctionOdrMergeFailures.empty() &&
PendingEnumOdrMergeFailures.empty() &&
PendingObjCProtocolOdrMergeFailures.empty())
Expand All @@ -9528,6 +9529,15 @@ void ASTReader::diagnoseOdrViolations() {
}
}

// Trigger the import of the full definition of each record in C/ObjC.
auto RecordOdrMergeFailures = std::move(PendingRecordOdrMergeFailures);
PendingRecordOdrMergeFailures.clear();
for (auto &Merge : RecordOdrMergeFailures) {
Merge.first->decls_begin();
for (auto &D : Merge.second)
D->decls_begin();
}

// Trigger the import of functions.
auto FunctionOdrMergeFailures = std::move(PendingFunctionOdrMergeFailures);
PendingFunctionOdrMergeFailures.clear();
Expand Down Expand Up @@ -9645,8 +9655,9 @@ void ASTReader::diagnoseOdrViolations() {
}
}

if (OdrMergeFailures.empty() && FunctionOdrMergeFailures.empty() &&
EnumOdrMergeFailures.empty() && ObjCProtocolOdrMergeFailures.empty())
if (OdrMergeFailures.empty() && RecordOdrMergeFailures.empty() &&
FunctionOdrMergeFailures.empty() && EnumOdrMergeFailures.empty() &&
ObjCProtocolOdrMergeFailures.empty())
return;

// Ensure we don't accidentally recursively enter deserialization while
Expand Down Expand Up @@ -9685,6 +9696,26 @@ void ASTReader::diagnoseOdrViolations() {
}
}

// Issue any pending ODR-failure diagnostics for RecordDecl in C/ObjC. Note
// that in C++ this is done as a part of CXXRecordDecl ODR checking.
for (auto &Merge : RecordOdrMergeFailures) {
// If we've already pointed out a specific problem with this class, don't
// bother issuing a general "something's different" diagnostic.
if (!DiagnosedOdrMergeFailures.insert(Merge.first).second)
continue;

RecordDecl *FirstRecord = Merge.first;
bool Diagnosed = false;
for (auto *SecondRecord : Merge.second) {
if (DiagsEmitter.diagnoseMismatch(FirstRecord, SecondRecord)) {
Diagnosed = true;
break;
}
}
(void)Diagnosed;
assert(Diagnosed && "Unable to emit ODR diagnostic.");
}

// Issue ODR failures diagnostics for functions.
for (auto &Merge : FunctionOdrMergeFailures) {
FunctionDecl *FirstFunction = Merge.first;
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,7 @@ ASTDeclReader::VisitRecordDeclImpl(RecordDecl *RD) {

void ASTDeclReader::VisitRecordDecl(RecordDecl *RD) {
VisitRecordDeclImpl(RD);
RD->setODRHash(Record.readInt());

// Maintain the invariant of a redeclaration chain containing only
// a single definition.
Expand All @@ -849,6 +850,8 @@ void ASTDeclReader::VisitRecordDecl(RecordDecl *RD) {
Reader.MergedDeclContexts.insert(std::make_pair(RD, OldDef));
RD->demoteThisDefinitionToDeclaration();
Reader.mergeDefinitionVisibility(OldDef, RD);
if (OldDef->getODRHash() != RD->getODRHash())
Reader.PendingRecordOdrMergeFailures[OldDef].push_back(RD);
} else {
OldDef = RD;
}
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/Serialization/ASTWriterDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,10 @@ void ASTDeclWriter::VisitRecordDecl(RecordDecl *D) {
Record.push_back(D->hasNonTrivialToPrimitiveCopyCUnion());
Record.push_back(D->isParamDestroyedInCallee());
Record.push_back(D->getArgPassingRestrictions());
// Only compute this for C/Objective-C, in C++ this is computed as part
// of CXXRecordDecl.
if (!isa<CXXRecordDecl>(D))
Record.push_back(D->getODRHash());

if (D->getDeclContext() == D->getLexicalDeclContext() &&
!D->hasAttrs() &&
Expand Down Expand Up @@ -2127,6 +2131,8 @@ void ASTWriter::WriteDeclAbbrevs() {
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1));
// getArgPassingRestrictions
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 2));
// ODRHash
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 26));

// DC
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // LexicalOffset
Expand Down
Loading

0 comments on commit 160bc16

Please sign in to comment.