forked from OSchip/llvm-project
Add better ODR checking for modules.
When objects are imported for modules, there is a chance that a name collision will cause an ODR violation. Previously, only a small number of such violations were detected. This patch provides a stronger check based on AST nodes. The information needed to uniquely identify an object is taked from the AST and put into a one-dimensional byte stream. This stream is then hashed to give a value to represent the object, which is stored with the other object data in the module. When modules are loaded, and Decl's are merged, the hash values of the two Decl's are compared. Only Decl's with matched hash values will be merged. Mismatch hashes will generate a module error, and if possible, point to the first difference between the two objects. The transform from AST to byte stream is a modified depth first algorithm. Due to references between some AST nodes, a pure depth first algorithm could generate loops. For Stmt nodes, a straight depth first processing occurs. For Type and Decl nodes, they are replaced with an index number and only on first visit will these nodes be processed. As an optimization, boolean values are saved and stored together in reverse order at the end of the byte stream to lower the ammount of data that needs to be hashed. Compile time impact was measured at 1.5-2.0% during module building, and negligible during builds without module building. Differential Revision: https://reviews.llvm.org/D21675 llvm-svn: 293585
This commit is contained in:
parent
6c907a9bb3
commit
fa3d93a148
|
@ -458,6 +458,9 @@ class CXXRecordDecl : public RecordDecl {
|
|||
/// \brief Whether we are currently parsing base specifiers.
|
||||
unsigned IsParsingBaseSpecifiers : 1;
|
||||
|
||||
/// \brief A hash of parts of the class to help in ODR checking.
|
||||
unsigned ODRHash;
|
||||
|
||||
/// \brief The number of base class specifiers in Bases.
|
||||
unsigned NumBases;
|
||||
|
||||
|
@ -703,6 +706,9 @@ public:
|
|||
return data().IsParsingBaseSpecifiers;
|
||||
}
|
||||
|
||||
void computeODRHash();
|
||||
unsigned getODRHash() const { return data().ODRHash; }
|
||||
|
||||
/// \brief Sets the base classes of this struct or class.
|
||||
void setBases(CXXBaseSpecifier const * const *Bases, unsigned NumBases);
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
//===-- ODRHash.h - Hashing to diagnose ODR failures ------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
///
|
||||
/// \file
|
||||
/// This file contains the declaration of the ODRHash class, which calculates
|
||||
/// a hash based on AST nodes, which is stable across different runs.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/AST/DeclarationName.h"
|
||||
#include "clang/AST/Type.h"
|
||||
#include "clang/AST/TemplateBase.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/FoldingSet.h"
|
||||
#include "llvm/ADT/PointerUnion.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
namespace clang {
|
||||
|
||||
class Decl;
|
||||
class IdentifierInfo;
|
||||
class NestedNameSpecifer;
|
||||
class Stmt;
|
||||
class TemplateParameterList;
|
||||
|
||||
// ODRHash is used to calculate a hash based on AST node contents that
|
||||
// does not rely on pointer addresses. This allows the hash to not vary
|
||||
// between runs and is usable to detect ODR problems in modules. To use,
|
||||
// construct an ODRHash object, then call Add* methods over the nodes that
|
||||
// need to be hashed. Then call CalculateHash to get the hash value.
|
||||
// Typically, only one Add* call is needed. clear can be called to reuse the
|
||||
// object.
|
||||
class ODRHash {
|
||||
// Use DenseMaps to convert between Decl and Type pointers and an index value.
|
||||
llvm::DenseMap<const Decl*, unsigned> DeclMap;
|
||||
llvm::DenseMap<const Type*, unsigned> TypeMap;
|
||||
|
||||
// Save space by processing bools at the end.
|
||||
llvm::SmallVector<bool, 128> Bools;
|
||||
|
||||
llvm::FoldingSetNodeID ID;
|
||||
|
||||
public:
|
||||
ODRHash() {}
|
||||
|
||||
// Use this for ODR checking classes between modules. This method compares
|
||||
// more information than the AddDecl class.
|
||||
void AddCXXRecordDecl(const CXXRecordDecl *Record);
|
||||
|
||||
// Add AST nodes that need to be processes. Some nodes are processed
|
||||
// immediately while others are queued and processed later.
|
||||
void AddDecl(const Decl *D);
|
||||
void AddType(const Type *T);
|
||||
void AddQualType(QualType T);
|
||||
void AddStmt(const Stmt *S);
|
||||
void AddIdentifierInfo(const IdentifierInfo *II);
|
||||
void AddNestedNameSpecifier(const NestedNameSpecifier *NNS);
|
||||
void AddTemplateName(TemplateName Name);
|
||||
void AddDeclarationName(DeclarationName Name);
|
||||
void AddTemplateArgument(TemplateArgument TA);
|
||||
void AddTemplateParameterList(const TemplateParameterList *TPL);
|
||||
|
||||
// Reset the object for reuse.
|
||||
void clear();
|
||||
|
||||
// Processes any unvisited nodes in Pointers and computes the final hash
|
||||
// value.
|
||||
unsigned CalculateHash();
|
||||
|
||||
// Save booleans until the end to lower the size of data to process.
|
||||
void AddBoolean(bool value);
|
||||
};
|
||||
|
||||
} // end namespace clang
|
|
@ -39,6 +39,7 @@ namespace clang {
|
|||
class Expr;
|
||||
class IdentifierInfo;
|
||||
class LabelDecl;
|
||||
class ODRHash;
|
||||
class ParmVarDecl;
|
||||
class PrinterHelper;
|
||||
struct PrintingPolicy;
|
||||
|
@ -436,6 +437,8 @@ public:
|
|||
/// written in the source.
|
||||
void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context,
|
||||
bool Canonical) const;
|
||||
|
||||
void ProcessODRHash(llvm::FoldingSetNodeID &ID, ODRHash& Hash) const;
|
||||
};
|
||||
|
||||
/// DeclStmt - Adaptor class for mixing declarations with statements and
|
||||
|
|
|
@ -117,6 +117,70 @@ def note_module_odr_violation_different_definitions : Note<
|
|||
def err_module_odr_violation_different_instantiations : Error<
|
||||
"instantiation of %q0 is different in different modules">;
|
||||
|
||||
def err_module_odr_violation_mismatch_decl : Error<
|
||||
"%q0 has different definitions in different modules; first difference is "
|
||||
"%select{definition in module '%2'|defined here}1 found "
|
||||
"%select{end of class|public access specifier|private access specifier|"
|
||||
"protected access specifier|friend declaration|enum|"
|
||||
"static assert|typedef|type alias|method|constructor|destructor|"
|
||||
"conversion operator|field|other}3">;
|
||||
def note_module_odr_violation_mismatch_decl : Note<"but in '%0' found "
|
||||
"%select{end of class|public access specifier|private access specifier|"
|
||||
"protected access specifier|friend declaration|enum|"
|
||||
"static assert|typedef|type alias|method|constructor|destructor|"
|
||||
"conversion operator|field|other}1">;
|
||||
|
||||
def err_module_odr_violation_mismatch_decl_diff : Error<
|
||||
"%q0 has different definitions in different modules; first difference is "
|
||||
"%select{definition in module '%2'|defined here}1 found "
|
||||
"%select{friend %4|enum %4|element %4 in enum %5|"
|
||||
"element %4 in enum %5 with initializer|"
|
||||
"element %4 in enum %5 with no initializer|"
|
||||
"element %4 in enum %5 with initializer|"
|
||||
"enum %4 has %5 element%s5|"
|
||||
"static assert with condition|"
|
||||
"static assert with message|"
|
||||
"static assert with %select{|no}4 message|"
|
||||
"%select{typedef|type alias}4 name %5|"
|
||||
"method named %4|"
|
||||
"method %4 is %select{non-|}5static|"
|
||||
"method %4 is %select{not |}5inline|"
|
||||
"method %4 is %select{not |}5const|"
|
||||
"method %4 has %5 parameter%s5|"
|
||||
"method %4 has %ordinal5 parameter %select{named %7|with no name}6|"
|
||||
"method %4 has %ordinal5 parameter with type %6|"
|
||||
"method %4 has %ordinal5 parameter with default argument|"
|
||||
"method %4 has %ordinal5 parameter with %select{no |}6 default argument|"
|
||||
"method %4 has %select{|no }5body|"
|
||||
"method %4 has different body|"
|
||||
"field %4|"
|
||||
"%select{field|bitfield}5 %4|"
|
||||
"%select{non-mutable|mutable}5 %4}3">;
|
||||
def note_module_odr_violation_mismatch_decl_diff : Note<"but in '%0' found "
|
||||
"%select{other friend %2|other enum %2|different element %2 in enum %3|"
|
||||
"element %2 in enum %3 with initializer|"
|
||||
"element %2 in enum %3 with no initializer|"
|
||||
"element %2 in enum %3 with different initializer|"
|
||||
"enum %2 has %3 element%s3|"
|
||||
"static assert with different condition|"
|
||||
"static assert with different message|"
|
||||
"static assert with %select{|no}2 message|"
|
||||
"different %select{typedef|type alias}2 name %3|"
|
||||
"method named %2|"
|
||||
"method %2 is %select{non-|}3static|"
|
||||
"method %2 is %select{not |}3inline|"
|
||||
"method %2 is %select{not |}3const|"
|
||||
"method %2 has %3 parameter%s3|"
|
||||
"method %2 has %ordinal3 parameter %select{named %5|with no name}4|"
|
||||
"method %2 has %ordinal3 parameter with type %4|"
|
||||
"method %2 has %ordinal3 parameter with different default argument|"
|
||||
"method %2 has %ordinal3 parameter with %select{no |}4default argument|"
|
||||
"method %2 has %select{|no }3body|"
|
||||
"method %2 has different body|"
|
||||
"field %2|"
|
||||
"%select{field|bitfield}3 %2|"
|
||||
"%select{non-mutable|mutable}3 %2}1">;
|
||||
|
||||
def warn_module_uses_date_time : Warning<
|
||||
"%select{precompiled header|module}0 uses __DATE__ or __TIME__">,
|
||||
InGroup<DiagGroup<"pch-date-time">>;
|
||||
|
|
|
@ -40,6 +40,7 @@ add_clang_library(clangAST
|
|||
MicrosoftMangle.cpp
|
||||
NestedNameSpecifier.cpp
|
||||
NSAPI.cpp
|
||||
ODRHash.cpp
|
||||
OpenMPClause.cpp
|
||||
ParentMap.cpp
|
||||
RawCommentList.cpp
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "clang/AST/DeclTemplate.h"
|
||||
#include "clang/AST/Expr.h"
|
||||
#include "clang/AST/ExprCXX.h"
|
||||
#include "clang/AST/ODRHash.h"
|
||||
#include "clang/AST/TypeLoc.h"
|
||||
#include "clang/Basic/IdentifierTable.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
|
@ -71,8 +72,8 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
|
|||
ImplicitCopyAssignmentHasConstParam(true),
|
||||
HasDeclaredCopyConstructorWithConstParam(false),
|
||||
HasDeclaredCopyAssignmentWithConstParam(false), IsLambda(false),
|
||||
IsParsingBaseSpecifiers(false), NumBases(0), NumVBases(0), Bases(),
|
||||
VBases(), Definition(D), FirstFriend() {}
|
||||
IsParsingBaseSpecifiers(false), ODRHash(0), NumBases(0), NumVBases(0),
|
||||
Bases(), VBases(), Definition(D), FirstFriend() {}
|
||||
|
||||
CXXBaseSpecifier *CXXRecordDecl::DefinitionData::getBasesSlowCase() const {
|
||||
return Bases.get(Definition->getASTContext().getExternalSource());
|
||||
|
@ -371,6 +372,16 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
|
|||
data().IsParsingBaseSpecifiers = false;
|
||||
}
|
||||
|
||||
void CXXRecordDecl::computeODRHash() {
|
||||
if (!DefinitionData)
|
||||
return;
|
||||
|
||||
ODRHash Hash;
|
||||
Hash.AddCXXRecordDecl(this);
|
||||
|
||||
DefinitionData->ODRHash = Hash.CalculateHash();
|
||||
}
|
||||
|
||||
void CXXRecordDecl::addedClassSubobject(CXXRecordDecl *Subobj) {
|
||||
// C++11 [class.copy]p11:
|
||||
// A defaulted copy/move constructor for a class X is defined as
|
||||
|
|
|
@ -0,0 +1,892 @@
|
|||
//===-- ODRHash.cpp - Hashing to diagnose ODR failures ----------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
///
|
||||
/// \file
|
||||
/// This file implements the ODRHash class, which calculates a hash based
|
||||
/// on AST nodes, which is stable across different runs.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/AST/ODRHash.h"
|
||||
|
||||
#include "clang/AST/DeclVisitor.h"
|
||||
#include "clang/AST/NestedNameSpecifier.h"
|
||||
#include "clang/AST/StmtVisitor.h"
|
||||
#include "clang/AST/TypeVisitor.h"
|
||||
|
||||
using namespace clang;
|
||||
|
||||
// This method adds more information that AddDecl does.
|
||||
void ODRHash::AddCXXRecordDecl(const CXXRecordDecl *Record) {
|
||||
assert(Record && Record->hasDefinition() &&
|
||||
"Expected non-null record to be a definition.");
|
||||
AddDecl(Record);
|
||||
|
||||
// Additional information that is not needed in AddDecl. Do not move this
|
||||
// to ODRTypeVisitor.
|
||||
ID.AddInteger(Record->getNumBases());
|
||||
for (auto base : Record->bases()) {
|
||||
AddBoolean(base.isVirtual());
|
||||
AddQualType(base.getType());
|
||||
}
|
||||
|
||||
const ClassTemplateDecl *TD = Record->getDescribedClassTemplate();
|
||||
AddBoolean(TD);
|
||||
if (TD) {
|
||||
AddTemplateParameterList(TD->getTemplateParameters());
|
||||
}
|
||||
}
|
||||
|
||||
// Hashing for Stmt is with Stmt::Profile, since they derive from the same base
|
||||
// class.
|
||||
void ODRHash::AddStmt(const Stmt *S) {
|
||||
assert(S && "Expecting non-null pointer.");
|
||||
S->ProcessODRHash(ID, *this);
|
||||
}
|
||||
|
||||
void ODRHash::AddIdentifierInfo(const IdentifierInfo *II) {
|
||||
assert(II && "Expecting non-null pointer.");
|
||||
ID.AddString(II->getName());
|
||||
}
|
||||
|
||||
void ODRHash::AddNestedNameSpecifier(const NestedNameSpecifier *NNS) {
|
||||
assert(NNS && "Expecting non-null pointer.");
|
||||
const auto *Prefix = NNS->getPrefix();
|
||||
AddBoolean(Prefix);
|
||||
if (Prefix) {
|
||||
AddNestedNameSpecifier(Prefix);
|
||||
}
|
||||
|
||||
auto Kind = NNS->getKind();
|
||||
ID.AddInteger(Kind);
|
||||
switch (Kind) {
|
||||
case NestedNameSpecifier::Identifier:
|
||||
AddIdentifierInfo(NNS->getAsIdentifier());
|
||||
break;
|
||||
case NestedNameSpecifier::Namespace:
|
||||
AddDecl(NNS->getAsNamespace());
|
||||
break;
|
||||
case NestedNameSpecifier::NamespaceAlias:
|
||||
AddDecl(NNS->getAsNamespaceAlias());
|
||||
break;
|
||||
case NestedNameSpecifier::TypeSpec:
|
||||
case NestedNameSpecifier::TypeSpecWithTemplate:
|
||||
AddType(NNS->getAsType());
|
||||
break;
|
||||
case NestedNameSpecifier::Global:
|
||||
case NestedNameSpecifier::Super:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ODRHash::AddTemplateName(TemplateName Name) {
|
||||
const auto Kind = Name.getKind();
|
||||
ID.AddInteger(Kind);
|
||||
AddBoolean(Name.isDependent());
|
||||
AddBoolean(Name.isInstantiationDependent());
|
||||
switch (Kind) {
|
||||
case TemplateName::Template:
|
||||
AddDecl(Name.getAsTemplateDecl());
|
||||
break;
|
||||
case TemplateName::OverloadedTemplate: {
|
||||
const auto *Storage = Name.getAsOverloadedTemplate();
|
||||
ID.AddInteger(Storage->size());
|
||||
for (const auto *ND : *Storage) {
|
||||
AddDecl(ND);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TemplateName::QualifiedTemplate: {
|
||||
const auto *QTN = Name.getAsQualifiedTemplateName();
|
||||
AddNestedNameSpecifier(QTN->getQualifier());
|
||||
AddBoolean(QTN->hasTemplateKeyword());
|
||||
AddDecl(QTN->getDecl());
|
||||
break;
|
||||
}
|
||||
case TemplateName::DependentTemplate: {
|
||||
const auto *DTN = Name.getAsDependentTemplateName();
|
||||
AddBoolean(DTN->isIdentifier());
|
||||
if (DTN->isIdentifier()) {
|
||||
AddIdentifierInfo(DTN->getIdentifier());
|
||||
} else {
|
||||
ID.AddInteger(DTN->getOperator());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TemplateName::SubstTemplateTemplateParm: {
|
||||
const auto *Storage = Name.getAsSubstTemplateTemplateParm();
|
||||
AddDecl(Storage->getParameter());
|
||||
AddTemplateName(Storage->getReplacement());
|
||||
break;
|
||||
}
|
||||
case TemplateName::SubstTemplateTemplateParmPack: {
|
||||
const auto *Storage = Name.getAsSubstTemplateTemplateParmPack();
|
||||
AddDecl(Storage->getParameterPack());
|
||||
AddTemplateArgument(Storage->getArgumentPack());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ODRHash::AddDeclarationName(DeclarationName Name) {
|
||||
AddBoolean(Name.isEmpty());
|
||||
if (Name.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto Kind = Name.getNameKind();
|
||||
ID.AddInteger(Kind);
|
||||
switch (Kind) {
|
||||
case DeclarationName::Identifier:
|
||||
AddIdentifierInfo(Name.getAsIdentifierInfo());
|
||||
break;
|
||||
case DeclarationName::ObjCZeroArgSelector:
|
||||
case DeclarationName::ObjCOneArgSelector:
|
||||
case DeclarationName::ObjCMultiArgSelector: {
|
||||
Selector S = Name.getObjCSelector();
|
||||
AddBoolean(S.isNull());
|
||||
AddBoolean(S.isKeywordSelector());
|
||||
AddBoolean(S.isUnarySelector());
|
||||
unsigned NumArgs = S.getNumArgs();
|
||||
for (unsigned i = 0; i < NumArgs; ++i) {
|
||||
AddIdentifierInfo(S.getIdentifierInfoForSlot(i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DeclarationName::CXXConstructorName:
|
||||
case DeclarationName::CXXDestructorName:
|
||||
AddQualType(Name.getCXXNameType());
|
||||
break;
|
||||
case DeclarationName::CXXOperatorName:
|
||||
ID.AddInteger(Name.getCXXOverloadedOperator());
|
||||
break;
|
||||
case DeclarationName::CXXLiteralOperatorName:
|
||||
AddIdentifierInfo(Name.getCXXLiteralIdentifier());
|
||||
break;
|
||||
case DeclarationName::CXXConversionFunctionName:
|
||||
AddQualType(Name.getCXXNameType());
|
||||
break;
|
||||
case DeclarationName::CXXUsingDirective:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ODRHash::AddTemplateArgument(TemplateArgument TA) {
|
||||
const auto Kind = TA.getKind();
|
||||
ID.AddInteger(Kind);
|
||||
switch (Kind) {
|
||||
case TemplateArgument::Null:
|
||||
llvm_unreachable("Require valid TemplateArgument");
|
||||
case TemplateArgument::Type:
|
||||
AddQualType(TA.getAsType());
|
||||
break;
|
||||
case TemplateArgument::Declaration:
|
||||
AddDecl(TA.getAsDecl());
|
||||
break;
|
||||
case TemplateArgument::NullPtr:
|
||||
AddQualType(TA.getNullPtrType());
|
||||
break;
|
||||
case TemplateArgument::Integral:
|
||||
TA.getAsIntegral().Profile(ID);
|
||||
AddQualType(TA.getIntegralType());
|
||||
break;
|
||||
case TemplateArgument::Template:
|
||||
case TemplateArgument::TemplateExpansion:
|
||||
AddTemplateName(TA.getAsTemplateOrTemplatePattern());
|
||||
break;
|
||||
case TemplateArgument::Expression:
|
||||
AddStmt(TA.getAsExpr());
|
||||
break;
|
||||
case TemplateArgument::Pack:
|
||||
ID.AddInteger(TA.pack_size());
|
||||
for (auto SubTA : TA.pack_elements()) {
|
||||
AddTemplateArgument(SubTA);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ODRHash::AddTemplateParameterList(const TemplateParameterList *TPL) {
|
||||
assert(TPL && "Expecting non-null pointer.");
|
||||
ID.AddInteger(TPL->size());
|
||||
for (auto *ND : TPL->asArray()) {
|
||||
AddDecl(ND);
|
||||
}
|
||||
}
|
||||
|
||||
void ODRHash::clear() {
|
||||
DeclMap.clear();
|
||||
TypeMap.clear();
|
||||
Bools.clear();
|
||||
ID.clear();
|
||||
}
|
||||
|
||||
unsigned ODRHash::CalculateHash() {
|
||||
// Append the bools to the end of the data segment backwards. This allows
|
||||
// for the bools data to be compressed 32 times smaller compared to using
|
||||
// ID.AddBoolean
|
||||
const unsigned unsigned_bits = sizeof(unsigned) * CHAR_BIT;
|
||||
const unsigned size = Bools.size();
|
||||
const unsigned remainder = size % unsigned_bits;
|
||||
const unsigned loops = size / unsigned_bits;
|
||||
auto I = Bools.rbegin();
|
||||
unsigned value = 0;
|
||||
for (unsigned i = 0; i < remainder; ++i) {
|
||||
value <<= 1;
|
||||
value |= *I;
|
||||
++I;
|
||||
}
|
||||
ID.AddInteger(value);
|
||||
|
||||
for (unsigned i = 0; i < loops; ++i) {
|
||||
value = 0;
|
||||
for (unsigned j = 0; j < unsigned_bits; ++j) {
|
||||
value <<= 1;
|
||||
value |= *I;
|
||||
++I;
|
||||
}
|
||||
ID.AddInteger(value);
|
||||
}
|
||||
|
||||
assert(I == Bools.rend());
|
||||
return ID.ComputeHash();
|
||||
}
|
||||
|
||||
// Process a Decl pointer. Add* methods call back into ODRHash while Visit*
|
||||
// methods process the relevant parts of the Decl.
|
||||
class ODRDeclVisitor : public ConstDeclVisitor<ODRDeclVisitor> {
|
||||
typedef ConstDeclVisitor<ODRDeclVisitor> Inherited;
|
||||
llvm::FoldingSetNodeID &ID;
|
||||
ODRHash &Hash;
|
||||
|
||||
public:
|
||||
ODRDeclVisitor(llvm::FoldingSetNodeID &ID, ODRHash &Hash)
|
||||
: ID(ID), Hash(Hash) {}
|
||||
|
||||
void AddDecl(const Decl *D) {
|
||||
Hash.AddBoolean(D);
|
||||
if (D) {
|
||||
Hash.AddDecl(D);
|
||||
}
|
||||
}
|
||||
|
||||
void AddStmt(const Stmt *S) {
|
||||
Hash.AddBoolean(S);
|
||||
if (S) {
|
||||
Hash.AddStmt(S);
|
||||
}
|
||||
}
|
||||
|
||||
void AddQualType(QualType T) {
|
||||
Hash.AddQualType(T);
|
||||
}
|
||||
|
||||
void AddIdentifierInfo(const IdentifierInfo *II) {
|
||||
Hash.AddBoolean(II);
|
||||
if (II) {
|
||||
Hash.AddIdentifierInfo(II);
|
||||
}
|
||||
}
|
||||
|
||||
void AddTemplateParameterList(TemplateParameterList *TPL) {
|
||||
Hash.AddBoolean(TPL);
|
||||
if (TPL) {
|
||||
Hash.AddTemplateParameterList(TPL);
|
||||
}
|
||||
}
|
||||
|
||||
void AddTemplateArgument(TemplateArgument TA) {
|
||||
Hash.AddTemplateArgument(TA);
|
||||
}
|
||||
|
||||
void VisitDecl(const Decl *D) {
|
||||
if (!D) {
|
||||
return;
|
||||
}
|
||||
if (D->isImplicit()) {
|
||||
return;
|
||||
}
|
||||
if (D->isInvalidDecl()) {
|
||||
return;
|
||||
}
|
||||
ID.AddInteger(D->getKind());
|
||||
Hash.AddBoolean(D->hasAttrs());
|
||||
|
||||
if (auto *DC = dyn_cast<DeclContext>(D)) {
|
||||
llvm::SmallVector<const Decl *, 16> Decls;
|
||||
for (const Decl* D : DC->decls()) {
|
||||
if (!D->isImplicit()) {
|
||||
Decls.push_back(D);
|
||||
}
|
||||
}
|
||||
ID.AddInteger(Decls.size());
|
||||
for (auto SubDecl : Decls) {
|
||||
AddDecl(SubDecl);
|
||||
}
|
||||
}
|
||||
|
||||
Inherited::VisitDecl(D);
|
||||
}
|
||||
|
||||
void VisitLabelDecl(const LabelDecl *D) {
|
||||
Inherited::VisitLabelDecl(D);
|
||||
}
|
||||
|
||||
void VisitEnumDecl(const EnumDecl *D) {
|
||||
const bool isFixed = D->isFixed();
|
||||
Hash.AddBoolean(isFixed);
|
||||
if (isFixed) {
|
||||
AddQualType(D->getIntegerType());
|
||||
}
|
||||
Hash.AddBoolean(D->isScoped());
|
||||
Hash.AddBoolean(D->isScopedUsingClassTag());
|
||||
|
||||
Inherited::VisitEnumDecl(D);
|
||||
}
|
||||
|
||||
void VisitEnumConstantDecl(const EnumConstantDecl *D) {
|
||||
auto *E = D->getInitExpr();
|
||||
AddStmt(E);
|
||||
|
||||
Inherited::VisitEnumConstantDecl(D);
|
||||
}
|
||||
|
||||
void VisitNamedDecl(const NamedDecl *D) {
|
||||
AddIdentifierInfo(D->getIdentifier());
|
||||
Inherited::VisitNamedDecl(D);
|
||||
}
|
||||
|
||||
void VisitValueDecl(const ValueDecl *D) {
|
||||
AddQualType(D->getType());
|
||||
Inherited::VisitValueDecl(D);
|
||||
}
|
||||
|
||||
void VisitParmVarDecl(const ParmVarDecl *D) {
|
||||
AddStmt(D->getDefaultArg());
|
||||
Inherited::VisitParmVarDecl(D);
|
||||
}
|
||||
|
||||
void VisitAccessSpecDecl(const AccessSpecDecl *D) {
|
||||
ID.AddInteger(D->getAccess());
|
||||
Inherited::VisitAccessSpecDecl(D);
|
||||
}
|
||||
|
||||
void VisitFriendDecl(const FriendDecl *D) {
|
||||
TypeSourceInfo *TSI = D->getFriendType();
|
||||
Hash.AddBoolean(TSI);
|
||||
if (TSI) {
|
||||
AddQualType(TSI->getType());
|
||||
} else {
|
||||
AddDecl(D->getFriendDecl());
|
||||
}
|
||||
|
||||
unsigned NumLists = D->getFriendTypeNumTemplateParameterLists();
|
||||
ID.AddInteger(NumLists);
|
||||
for (unsigned i = 0; i < NumLists; ++i) {
|
||||
AddTemplateParameterList(D->getFriendTypeTemplateParameterList(i));
|
||||
}
|
||||
|
||||
Inherited::VisitFriendDecl(D);
|
||||
}
|
||||
|
||||
void VisitStaticAssertDecl(const StaticAssertDecl *D) {
|
||||
AddStmt(D->getAssertExpr());
|
||||
AddStmt(D->getMessage());
|
||||
|
||||
Inherited::VisitStaticAssertDecl(D);
|
||||
}
|
||||
|
||||
void VisitTypedefNameDecl(const TypedefNameDecl *D) {
|
||||
AddQualType(D->getUnderlyingType());
|
||||
|
||||
Inherited::VisitTypedefNameDecl(D);
|
||||
}
|
||||
|
||||
void VisitFunctionDecl(const FunctionDecl *D) {
|
||||
AddStmt(D->getBody());
|
||||
|
||||
ID.AddInteger(D->getStorageClass());
|
||||
Hash.AddBoolean(D->isInlineSpecified());
|
||||
Hash.AddBoolean(D->isVirtualAsWritten());
|
||||
Hash.AddBoolean(D->isPure());
|
||||
Hash.AddBoolean(D->isDeletedAsWritten());
|
||||
ID.AddInteger(D->getOverloadedOperator());
|
||||
Inherited::VisitFunctionDecl(D);
|
||||
}
|
||||
|
||||
void VisitCXXMethodDecl(const CXXMethodDecl *D) {
|
||||
Hash.AddBoolean(D->isStatic());
|
||||
Hash.AddBoolean(D->isInstance());
|
||||
Hash.AddBoolean(D->isConst());
|
||||
Hash.AddBoolean(D->isVolatile());
|
||||
Inherited::VisitCXXMethodDecl(D);
|
||||
}
|
||||
|
||||
void VisitCXXConstructorDecl(const CXXConstructorDecl *D) {
|
||||
Hash.AddBoolean(D->isExplicitSpecified());
|
||||
unsigned NumCtorInits = 0;
|
||||
llvm::SmallVector<CXXCtorInitializer *, 4> Initializers;
|
||||
ID.AddInteger(D->getNumCtorInitializers());
|
||||
for (auto Initializer : D->inits()) {
|
||||
if (Initializer->isWritten()) {
|
||||
++NumCtorInits;
|
||||
Initializers.push_back(Initializer);
|
||||
}
|
||||
}
|
||||
for (auto Initializer : Initializers) {
|
||||
AddStmt(Initializer->getInit());
|
||||
}
|
||||
|
||||
Inherited::VisitCXXConstructorDecl(D);
|
||||
}
|
||||
|
||||
void VisitCXXConversionDecl(const CXXConversionDecl *D) {
|
||||
AddQualType(D->getConversionType());
|
||||
Hash.AddBoolean(D->isExplicitSpecified());
|
||||
Inherited::VisitCXXConversionDecl(D);
|
||||
}
|
||||
|
||||
void VisitFieldDecl(const FieldDecl *D) {
|
||||
Hash.AddBoolean(D->isMutable());
|
||||
|
||||
const bool isBitField = D->isBitField();
|
||||
Hash.AddBoolean(isBitField);
|
||||
if (isBitField) {
|
||||
AddStmt(D->getBitWidth());
|
||||
}
|
||||
|
||||
AddStmt(D->getInClassInitializer());
|
||||
|
||||
Inherited::VisitFieldDecl(D);
|
||||
}
|
||||
|
||||
void VisitTemplateDecl(const TemplateDecl *D) {
|
||||
AddDecl(D->getTemplatedDecl());
|
||||
|
||||
auto *Parameters = D->getTemplateParameters();
|
||||
ID.AddInteger(Parameters->size());
|
||||
for (auto *ND : *Parameters) {
|
||||
AddDecl(ND);
|
||||
}
|
||||
|
||||
Inherited::VisitTemplateDecl(D);
|
||||
}
|
||||
|
||||
void VisitFunctionTemplateDecl(const FunctionTemplateDecl *D) {
|
||||
Inherited::VisitFunctionTemplateDecl(D);
|
||||
}
|
||||
|
||||
void VisitTemplateTypeParmDecl(const TemplateTypeParmDecl *D) {
|
||||
const bool hasDefaultArgument = D->hasDefaultArgument();
|
||||
Hash.AddBoolean(hasDefaultArgument);
|
||||
if (hasDefaultArgument) {
|
||||
AddTemplateArgument(D->getDefaultArgument());
|
||||
}
|
||||
|
||||
Inherited::VisitTemplateTypeParmDecl(D);
|
||||
}
|
||||
|
||||
void VisitNonTypeTemplateParmDecl(const NonTypeTemplateParmDecl *D) {
|
||||
AddStmt(D->hasDefaultArgument() ? D->getDefaultArgument() : nullptr);
|
||||
|
||||
Inherited::VisitNonTypeTemplateParmDecl(D);
|
||||
}
|
||||
|
||||
void VisitTemplateTemplateParmDecl(const TemplateTemplateParmDecl *D) {
|
||||
const bool hasDefaultArgument = D->hasDefaultArgument();
|
||||
Hash.AddBoolean(hasDefaultArgument);
|
||||
if (hasDefaultArgument) {
|
||||
AddTemplateArgument(D->getDefaultArgument().getArgument());
|
||||
}
|
||||
|
||||
Inherited::VisitTemplateTemplateParmDecl(D);
|
||||
}
|
||||
};
|
||||
|
||||
void ODRHash::AddDecl(const Decl *D) {
|
||||
assert(D && "Expecting non-null pointer.");
|
||||
auto Result = DeclMap.insert(std::make_pair(D, DeclMap.size()));
|
||||
ID.AddInteger(Result.first->second);
|
||||
// On first encounter of a Decl pointer, process it. Every time afterwards,
|
||||
// only the index value is needed.
|
||||
if (Result.second) {
|
||||
ODRDeclVisitor(ID, *this).Visit(D);
|
||||
}
|
||||
}
|
||||
|
||||
// Process a Type pointer. Add* methods call back into ODRHash while Visit*
|
||||
// methods process the relevant parts of the Type.
|
||||
class ODRTypeVisitor : public TypeVisitor<ODRTypeVisitor> {
|
||||
typedef TypeVisitor<ODRTypeVisitor> Inherited;
|
||||
llvm::FoldingSetNodeID &ID;
|
||||
ODRHash &Hash;
|
||||
|
||||
public:
|
||||
ODRTypeVisitor(llvm::FoldingSetNodeID &ID, ODRHash &Hash)
|
||||
: ID(ID), Hash(Hash) {}
|
||||
|
||||
void AddQualType(QualType T) {
|
||||
Hash.AddQualType(T);
|
||||
}
|
||||
|
||||
void AddDecl(Decl *D) {
|
||||
Hash.AddBoolean(D);
|
||||
if (D) {
|
||||
Hash.AddDecl(D);
|
||||
}
|
||||
}
|
||||
|
||||
void AddTemplateArgument(TemplateArgument TA) {
|
||||
Hash.AddTemplateArgument(TA);
|
||||
}
|
||||
|
||||
void AddStmt(Stmt *S) {
|
||||
Hash.AddBoolean(S);
|
||||
if (S) {
|
||||
Hash.AddStmt(S);
|
||||
}
|
||||
}
|
||||
|
||||
void AddNestedNameSpecifier(NestedNameSpecifier *NNS) {
|
||||
Hash.AddBoolean(NNS);
|
||||
if (NNS) {
|
||||
Hash.AddNestedNameSpecifier(NNS);
|
||||
}
|
||||
}
|
||||
void AddIdentiferInfo(const IdentifierInfo *II) {
|
||||
Hash.AddBoolean(II);
|
||||
if (II) {
|
||||
Hash.AddIdentifierInfo(II);
|
||||
}
|
||||
}
|
||||
|
||||
void AddTemplateName(TemplateName TN) {
|
||||
Hash.AddTemplateName(TN);
|
||||
}
|
||||
|
||||
void VisitQualifiers(Qualifiers Quals) {
|
||||
ID.AddInteger(Quals.getAsOpaqueValue());
|
||||
}
|
||||
|
||||
void VisitType(const Type *T) { ID.AddInteger(T->getTypeClass()); }
|
||||
|
||||
void VisitAdjustedType(const AdjustedType *T) {
|
||||
AddQualType(T->getOriginalType());
|
||||
AddQualType(T->getAdjustedType());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitDecayedType(const DecayedType *T) {
|
||||
AddQualType(T->getDecayedType());
|
||||
AddQualType(T->getPointeeType());
|
||||
VisitAdjustedType(T);
|
||||
}
|
||||
|
||||
void VisitArrayType(const ArrayType *T) {
|
||||
AddQualType(T->getElementType());
|
||||
ID.AddInteger(T->getSizeModifier());
|
||||
VisitQualifiers(T->getIndexTypeQualifiers());
|
||||
VisitType(T);
|
||||
}
|
||||
void VisitConstantArrayType(const ConstantArrayType *T) {
|
||||
T->getSize().Profile(ID);
|
||||
VisitArrayType(T);
|
||||
}
|
||||
|
||||
void VisitDependentSizedArrayType(const DependentSizedArrayType *T) {
|
||||
AddStmt(T->getSizeExpr());
|
||||
VisitArrayType(T);
|
||||
}
|
||||
|
||||
void VisitIncompleteArrayType(const IncompleteArrayType *T) {
|
||||
VisitArrayType(T);
|
||||
}
|
||||
|
||||
void VisitVariableArrayType(const VariableArrayType *T) {
|
||||
AddStmt(T->getSizeExpr());
|
||||
VisitArrayType(T);
|
||||
}
|
||||
|
||||
void VisitAtomicType(const AtomicType *T) {
|
||||
AddQualType(T->getValueType());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitAttributedType(const AttributedType *T) {
|
||||
ID.AddInteger(T->getAttrKind());
|
||||
AddQualType(T->getModifiedType());
|
||||
AddQualType(T->getEquivalentType());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitBlockPointerType(const BlockPointerType *T) {
|
||||
AddQualType(T->getPointeeType());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitBuiltinType(const BuiltinType *T) {
|
||||
ID.AddInteger(T->getKind());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitComplexType(const ComplexType *T) {
|
||||
AddQualType(T->getElementType());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitDecltypeType(const DecltypeType *T) {
|
||||
AddQualType(T->getUnderlyingType());
|
||||
AddStmt(T->getUnderlyingExpr());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitDependentSizedExtVectorType(const DependentSizedExtVectorType *T) {
|
||||
AddQualType(T->getElementType());
|
||||
AddStmt(T->getSizeExpr());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitFunctionType(const FunctionType *T) {
|
||||
AddQualType(T->getReturnType());
|
||||
T->getExtInfo().Profile(ID);
|
||||
Hash.AddBoolean(T->isConst());
|
||||
Hash.AddBoolean(T->isVolatile());
|
||||
Hash.AddBoolean(T->isRestrict());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitFunctionNoProtoType(const FunctionNoProtoType *T) {
|
||||
VisitFunctionType(T);
|
||||
}
|
||||
|
||||
void VisitFunctionProtoType(const FunctionProtoType *T) {
|
||||
ID.AddInteger(T->getNumParams());
|
||||
for (auto ParamType : T->getParamTypes()) {
|
||||
AddQualType(ParamType);
|
||||
}
|
||||
|
||||
const auto &epi = T->getExtProtoInfo();
|
||||
ID.AddInteger(epi.Variadic);
|
||||
ID.AddInteger(epi.TypeQuals);
|
||||
ID.AddInteger(epi.RefQualifier);
|
||||
ID.AddInteger(epi.ExceptionSpec.Type);
|
||||
|
||||
if (epi.ExceptionSpec.Type == EST_Dynamic) {
|
||||
for (QualType Ex : epi.ExceptionSpec.Exceptions) {
|
||||
AddQualType(Ex);
|
||||
}
|
||||
} else if (epi.ExceptionSpec.Type == EST_ComputedNoexcept &&
|
||||
epi.ExceptionSpec.NoexceptExpr) {
|
||||
AddStmt(epi.ExceptionSpec.NoexceptExpr);
|
||||
} else if (epi.ExceptionSpec.Type == EST_Uninstantiated ||
|
||||
epi.ExceptionSpec.Type == EST_Unevaluated) {
|
||||
AddDecl(epi.ExceptionSpec.SourceDecl->getCanonicalDecl());
|
||||
}
|
||||
if (epi.ExtParameterInfos) {
|
||||
for (unsigned i = 0; i != T->getNumParams(); ++i) {
|
||||
ID.AddInteger(epi.ExtParameterInfos[i].getOpaqueValue());
|
||||
}
|
||||
}
|
||||
epi.ExtInfo.Profile(ID);
|
||||
Hash.AddBoolean(epi.HasTrailingReturn);
|
||||
|
||||
VisitFunctionType(T);
|
||||
}
|
||||
|
||||
void VisitInjectedClassNameType(const InjectedClassNameType *T) {
|
||||
AddQualType(T->getInjectedSpecializationType());
|
||||
AddDecl(T->getDecl());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitMemberPointerType(const MemberPointerType *T) {
|
||||
AddQualType(T->getPointeeType());
|
||||
Visit(T->getClass());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitObjCObjectPointerType(const ObjCObjectPointerType *T) {
|
||||
AddQualType(T->getPointeeType());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitObjCObjectType(const ObjCObjectType *T) {
|
||||
QualType Base = T->getBaseType();
|
||||
const bool SameType = Base.getTypePtr() == T;
|
||||
Hash.AddBoolean(SameType);
|
||||
if (!SameType) {
|
||||
AddQualType(Base);
|
||||
}
|
||||
auto TypeArgs = T->getTypeArgsAsWritten();
|
||||
ID.AddInteger(TypeArgs.size());
|
||||
for (auto TypeArg : TypeArgs) {
|
||||
AddQualType(TypeArg);
|
||||
}
|
||||
ID.AddInteger(T->getNumProtocols());
|
||||
for (auto proto : T->quals()) {
|
||||
AddDecl(proto);
|
||||
}
|
||||
ID.AddInteger(T->isKindOfTypeAsWritten());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitObjCInterfaceType(const ObjCInterfaceType *T) {
|
||||
VisitObjCObjectType(T);
|
||||
}
|
||||
|
||||
void VisitObjCObjectTypeImpl(const ObjCObjectTypeImpl *T) {
|
||||
VisitObjCObjectType(T);
|
||||
}
|
||||
|
||||
void VisitPackExpansionType(const PackExpansionType *T) {
|
||||
AddQualType(T->getPattern());
|
||||
auto NumExpansions = T->getNumExpansions();
|
||||
Hash.AddBoolean(NumExpansions.hasValue());
|
||||
if (NumExpansions) {
|
||||
ID.AddInteger(*NumExpansions);
|
||||
}
|
||||
VisitType(T);
|
||||
};
|
||||
|
||||
void VisitPointerType(const PointerType *T) {
|
||||
AddQualType(T->getPointeeType());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitReferenceType(const ReferenceType *T) {
|
||||
AddQualType(T->getPointeeTypeAsWritten());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitLValueReferenceType(const LValueReferenceType *T) {
|
||||
VisitReferenceType(T);
|
||||
}
|
||||
|
||||
void VisitRValueReferenceType(const RValueReferenceType *T) {
|
||||
VisitReferenceType(T);
|
||||
}
|
||||
|
||||
void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *T) {
|
||||
AddQualType(T->getReplacementType());
|
||||
AddQualType(QualType(T->getReplacedParameter(), 0));
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void
|
||||
VisitSubstTemplateTypeParmPackType(const SubstTemplateTypeParmPackType *T) {
|
||||
AddQualType(QualType(T->getReplacedParameter(), 0));
|
||||
AddTemplateArgument(T->getArgumentPack());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitTagType(const TagType *T) {
|
||||
AddDecl(T->getDecl());
|
||||
Hash.AddBoolean(T->isBeingDefined());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitEnumType(const EnumType *T) {
|
||||
AddDecl(T->getDecl());
|
||||
VisitTagType(T);
|
||||
}
|
||||
|
||||
void VisitRecordType(const RecordType *T) {
|
||||
AddDecl(T->getDecl());
|
||||
VisitTagType(T);
|
||||
}
|
||||
|
||||
void VisitTemplateSpecializationType(const TemplateSpecializationType *T) {
|
||||
AddTemplateName(T->getTemplateName());
|
||||
ID.AddInteger(T->getNumArgs());
|
||||
for (auto I = T->begin(), E = T->end(); I != E; ++I) {
|
||||
AddTemplateArgument(*I);
|
||||
}
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitTemplateTypeParmType(const TemplateTypeParmType *T) {
|
||||
ID.AddInteger(T->getDepth());
|
||||
ID.AddInteger(T->getIndex());
|
||||
Hash.AddBoolean(T->isParameterPack());
|
||||
AddDecl(T->getDecl());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitTypedefType(const TypedefType *T) {
|
||||
AddDecl(T->getDecl());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitTypeOfExprType(const TypeOfExprType *T) {
|
||||
AddStmt(T->getUnderlyingExpr());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitDependentTypeOfExprType(const DependentTypeOfExprType *T) {
|
||||
VisitTypeOfExprType(T);
|
||||
}
|
||||
|
||||
void VisitTypeWithKeyword(const TypeWithKeyword *T) { VisitType(T); }
|
||||
|
||||
void VisitElaboratedType(const ElaboratedType *T) {
|
||||
ID.AddInteger(T->getKeyword());
|
||||
AddNestedNameSpecifier(T->getQualifier());
|
||||
AddQualType(T->getNamedType());
|
||||
VisitTypeWithKeyword(T);
|
||||
}
|
||||
|
||||
void VisitUnaryTransformType(const UnaryTransformType *T) {
|
||||
AddQualType(T->getBaseType());
|
||||
ID.AddInteger(T->getUTTKind());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitDependentUnaryTransformType(const DependentUnaryTransformType *T) {
|
||||
VisitUnaryTransformType(T);
|
||||
}
|
||||
|
||||
void VisitUnresolvedUsingType(const UnresolvedUsingType *T) {
|
||||
AddDecl(T->getDecl());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitVectorType(const VectorType *T) {
|
||||
AddQualType(T->getElementType());
|
||||
ID.AddInteger(T->getNumElements());
|
||||
ID.AddInteger(T->getVectorKind());
|
||||
VisitType(T);
|
||||
}
|
||||
|
||||
void VisitExtVectorType(const ExtVectorType *T) { VisitVectorType(T); }
|
||||
};
|
||||
|
||||
void ODRHash::AddType(const Type *T) {
|
||||
assert(T && "Expecting non-null pointer.");
|
||||
auto Result = TypeMap.insert(std::make_pair(T, TypeMap.size()));
|
||||
ID.AddInteger(Result.first->second);
|
||||
// On first encounter of a Type pointer, process it. Every time afterwards,
|
||||
// only the index value is needed.
|
||||
if (Result.second) {
|
||||
ODRTypeVisitor(ID, *this).Visit(T);
|
||||
}
|
||||
}
|
||||
|
||||
void ODRHash::AddQualType(QualType T) {
|
||||
AddBoolean(T.isNull());
|
||||
if (T.isNull()) {
|
||||
return;
|
||||
}
|
||||
SplitQualType split = T.split();
|
||||
ID.AddInteger(split.Quals.getAsOpaqueValue());
|
||||
AddType(split.Ty);
|
||||
}
|
||||
|
||||
void ODRHash::AddBoolean(bool Value) {
|
||||
Bools.push_back(Value);
|
||||
}
|
|
@ -19,20 +19,22 @@
|
|||
#include "clang/AST/ExprCXX.h"
|
||||
#include "clang/AST/ExprObjC.h"
|
||||
#include "clang/AST/ExprOpenMP.h"
|
||||
#include "clang/AST/ODRHash.h"
|
||||
#include "clang/AST/StmtVisitor.h"
|
||||
#include "llvm/ADT/FoldingSet.h"
|
||||
using namespace clang;
|
||||
|
||||
namespace {
|
||||
class StmtProfiler : public ConstStmtVisitor<StmtProfiler> {
|
||||
protected:
|
||||
llvm::FoldingSetNodeID &ID;
|
||||
const ASTContext &Context;
|
||||
bool Canonical;
|
||||
|
||||
public:
|
||||
StmtProfiler(llvm::FoldingSetNodeID &ID, const ASTContext &Context,
|
||||
bool Canonical)
|
||||
: ID(ID), Context(Context), Canonical(Canonical) { }
|
||||
StmtProfiler(llvm::FoldingSetNodeID &ID, bool Canonical)
|
||||
: ID(ID), Canonical(Canonical) {}
|
||||
|
||||
virtual ~StmtProfiler() {}
|
||||
|
||||
void VisitStmt(const Stmt *S);
|
||||
|
||||
|
@ -41,22 +43,25 @@ namespace {
|
|||
|
||||
/// \brief Visit a declaration that is referenced within an expression
|
||||
/// or statement.
|
||||
void VisitDecl(const Decl *D);
|
||||
virtual void VisitDecl(const Decl *D) = 0;
|
||||
|
||||
/// \brief Visit a type that is referenced within an expression or
|
||||
/// statement.
|
||||
void VisitType(QualType T);
|
||||
virtual void VisitType(QualType T) = 0;
|
||||
|
||||
/// \brief Visit a name that occurs within an expression or statement.
|
||||
void VisitName(DeclarationName Name);
|
||||
virtual void VisitName(DeclarationName Name) = 0;
|
||||
|
||||
/// \brief Visit identifiers that are not in Decl's or Type's.
|
||||
virtual void VisitIdentifierInfo(IdentifierInfo *II) = 0;
|
||||
|
||||
/// \brief Visit a nested-name-specifier that occurs within an expression
|
||||
/// or statement.
|
||||
void VisitNestedNameSpecifier(NestedNameSpecifier *NNS);
|
||||
virtual void VisitNestedNameSpecifier(NestedNameSpecifier *NNS) = 0;
|
||||
|
||||
/// \brief Visit a template name that occurs within an expression or
|
||||
/// statement.
|
||||
void VisitTemplateName(TemplateName Name);
|
||||
virtual void VisitTemplateName(TemplateName Name) = 0;
|
||||
|
||||
/// \brief Visit template arguments that occur within an expression or
|
||||
/// statement.
|
||||
|
@ -66,6 +71,127 @@ namespace {
|
|||
/// \brief Visit a single template argument.
|
||||
void VisitTemplateArgument(const TemplateArgument &Arg);
|
||||
};
|
||||
|
||||
class StmtProfilerWithPointers : public StmtProfiler {
|
||||
const ASTContext &Context;
|
||||
|
||||
public:
|
||||
StmtProfilerWithPointers(llvm::FoldingSetNodeID &ID,
|
||||
const ASTContext &Context, bool Canonical)
|
||||
: StmtProfiler(ID, Canonical), Context(Context) {}
|
||||
private:
|
||||
void VisitDecl(const Decl *D) override {
|
||||
ID.AddInteger(D ? D->getKind() : 0);
|
||||
|
||||
if (Canonical && D) {
|
||||
if (const NonTypeTemplateParmDecl *NTTP =
|
||||
dyn_cast<NonTypeTemplateParmDecl>(D)) {
|
||||
ID.AddInteger(NTTP->getDepth());
|
||||
ID.AddInteger(NTTP->getIndex());
|
||||
ID.AddBoolean(NTTP->isParameterPack());
|
||||
VisitType(NTTP->getType());
|
||||
return;
|
||||
}
|
||||
|
||||
if (const ParmVarDecl *Parm = dyn_cast<ParmVarDecl>(D)) {
|
||||
// The Itanium C++ ABI uses the type, scope depth, and scope
|
||||
// index of a parameter when mangling expressions that involve
|
||||
// function parameters, so we will use the parameter's type for
|
||||
// establishing function parameter identity. That way, our
|
||||
// definition of "equivalent" (per C++ [temp.over.link]) is at
|
||||
// least as strong as the definition of "equivalent" used for
|
||||
// name mangling.
|
||||
VisitType(Parm->getType());
|
||||
ID.AddInteger(Parm->getFunctionScopeDepth());
|
||||
ID.AddInteger(Parm->getFunctionScopeIndex());
|
||||
return;
|
||||
}
|
||||
|
||||
if (const TemplateTypeParmDecl *TTP =
|
||||
dyn_cast<TemplateTypeParmDecl>(D)) {
|
||||
ID.AddInteger(TTP->getDepth());
|
||||
ID.AddInteger(TTP->getIndex());
|
||||
ID.AddBoolean(TTP->isParameterPack());
|
||||
return;
|
||||
}
|
||||
|
||||
if (const TemplateTemplateParmDecl *TTP =
|
||||
dyn_cast<TemplateTemplateParmDecl>(D)) {
|
||||
ID.AddInteger(TTP->getDepth());
|
||||
ID.AddInteger(TTP->getIndex());
|
||||
ID.AddBoolean(TTP->isParameterPack());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ID.AddPointer(D ? D->getCanonicalDecl() : nullptr);
|
||||
}
|
||||
|
||||
void VisitType(QualType T) override {
|
||||
if (Canonical)
|
||||
T = Context.getCanonicalType(T);
|
||||
|
||||
ID.AddPointer(T.getAsOpaquePtr());
|
||||
}
|
||||
|
||||
void VisitName(DeclarationName Name) override {
|
||||
ID.AddPointer(Name.getAsOpaquePtr());
|
||||
}
|
||||
|
||||
void VisitIdentifierInfo(IdentifierInfo *II) override {
|
||||
ID.AddPointer(II);
|
||||
}
|
||||
|
||||
void VisitNestedNameSpecifier(NestedNameSpecifier *NNS) override {
|
||||
if (Canonical)
|
||||
NNS = Context.getCanonicalNestedNameSpecifier(NNS);
|
||||
ID.AddPointer(NNS);
|
||||
}
|
||||
|
||||
void VisitTemplateName(TemplateName Name) override {
|
||||
if (Canonical)
|
||||
Name = Context.getCanonicalTemplateName(Name);
|
||||
|
||||
Name.Profile(ID);
|
||||
}
|
||||
};
|
||||
|
||||
class StmtProfilerWithoutPointers : public StmtProfiler {
|
||||
ODRHash &Hash;
|
||||
public:
|
||||
StmtProfilerWithoutPointers(llvm::FoldingSetNodeID &ID, ODRHash &Hash)
|
||||
: StmtProfiler(ID, false), Hash(Hash) {}
|
||||
|
||||
private:
|
||||
void VisitType(QualType T) override {
|
||||
Hash.AddQualType(T);
|
||||
}
|
||||
|
||||
void VisitName(DeclarationName Name) override {
|
||||
Hash.AddDeclarationName(Name);
|
||||
}
|
||||
void VisitIdentifierInfo(IdentifierInfo *II) override {
|
||||
ID.AddBoolean(II);
|
||||
if (II) {
|
||||
Hash.AddIdentifierInfo(II);
|
||||
}
|
||||
}
|
||||
void VisitDecl(const Decl *D) override {
|
||||
ID.AddBoolean(D);
|
||||
if (D) {
|
||||
Hash.AddDecl(D);
|
||||
}
|
||||
}
|
||||
void VisitTemplateName(TemplateName Name) override {
|
||||
Hash.AddTemplateName(Name);
|
||||
}
|
||||
void VisitNestedNameSpecifier(NestedNameSpecifier *NNS) override {
|
||||
ID.AddBoolean(NNS);
|
||||
if (NNS) {
|
||||
Hash.AddNestedNameSpecifier(NNS);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void StmtProfiler::VisitStmt(const Stmt *S) {
|
||||
|
@ -853,7 +979,7 @@ void StmtProfiler::VisitOffsetOfExpr(const OffsetOfExpr *S) {
|
|||
break;
|
||||
|
||||
case OffsetOfNode::Identifier:
|
||||
ID.AddPointer(ON.getFieldName());
|
||||
VisitIdentifierInfo(ON.getFieldName());
|
||||
break;
|
||||
|
||||
case OffsetOfNode::Base:
|
||||
|
@ -861,7 +987,7 @@ void StmtProfiler::VisitOffsetOfExpr(const OffsetOfExpr *S) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
VisitExpr(S);
|
||||
}
|
||||
|
||||
|
@ -1451,7 +1577,7 @@ StmtProfiler::VisitCXXPseudoDestructorExpr(const CXXPseudoDestructorExpr *S) {
|
|||
if (S->getDestroyedTypeInfo())
|
||||
VisitType(S->getDestroyedType());
|
||||
else
|
||||
ID.AddPointer(S->getDestroyedTypeIdentifier());
|
||||
VisitIdentifierInfo(S->getDestroyedTypeIdentifier());
|
||||
}
|
||||
|
||||
void StmtProfiler::VisitOverloadExpr(const OverloadExpr *S) {
|
||||
|
@ -1701,77 +1827,6 @@ void StmtProfiler::VisitObjCAvailabilityCheckExpr(
|
|||
VisitExpr(S);
|
||||
}
|
||||
|
||||
void StmtProfiler::VisitDecl(const Decl *D) {
|
||||
ID.AddInteger(D? D->getKind() : 0);
|
||||
|
||||
if (Canonical && D) {
|
||||
if (const NonTypeTemplateParmDecl *NTTP =
|
||||
dyn_cast<NonTypeTemplateParmDecl>(D)) {
|
||||
ID.AddInteger(NTTP->getDepth());
|
||||
ID.AddInteger(NTTP->getIndex());
|
||||
ID.AddBoolean(NTTP->isParameterPack());
|
||||
VisitType(NTTP->getType());
|
||||
return;
|
||||
}
|
||||
|
||||
if (const ParmVarDecl *Parm = dyn_cast<ParmVarDecl>(D)) {
|
||||
// The Itanium C++ ABI uses the type, scope depth, and scope
|
||||
// index of a parameter when mangling expressions that involve
|
||||
// function parameters, so we will use the parameter's type for
|
||||
// establishing function parameter identity. That way, our
|
||||
// definition of "equivalent" (per C++ [temp.over.link]) is at
|
||||
// least as strong as the definition of "equivalent" used for
|
||||
// name mangling.
|
||||
VisitType(Parm->getType());
|
||||
ID.AddInteger(Parm->getFunctionScopeDepth());
|
||||
ID.AddInteger(Parm->getFunctionScopeIndex());
|
||||
return;
|
||||
}
|
||||
|
||||
if (const TemplateTypeParmDecl *TTP =
|
||||
dyn_cast<TemplateTypeParmDecl>(D)) {
|
||||
ID.AddInteger(TTP->getDepth());
|
||||
ID.AddInteger(TTP->getIndex());
|
||||
ID.AddBoolean(TTP->isParameterPack());
|
||||
return;
|
||||
}
|
||||
|
||||
if (const TemplateTemplateParmDecl *TTP =
|
||||
dyn_cast<TemplateTemplateParmDecl>(D)) {
|
||||
ID.AddInteger(TTP->getDepth());
|
||||
ID.AddInteger(TTP->getIndex());
|
||||
ID.AddBoolean(TTP->isParameterPack());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ID.AddPointer(D? D->getCanonicalDecl() : nullptr);
|
||||
}
|
||||
|
||||
void StmtProfiler::VisitType(QualType T) {
|
||||
if (Canonical)
|
||||
T = Context.getCanonicalType(T);
|
||||
|
||||
ID.AddPointer(T.getAsOpaquePtr());
|
||||
}
|
||||
|
||||
void StmtProfiler::VisitName(DeclarationName Name) {
|
||||
ID.AddPointer(Name.getAsOpaquePtr());
|
||||
}
|
||||
|
||||
void StmtProfiler::VisitNestedNameSpecifier(NestedNameSpecifier *NNS) {
|
||||
if (Canonical)
|
||||
NNS = Context.getCanonicalNestedNameSpecifier(NNS);
|
||||
ID.AddPointer(NNS);
|
||||
}
|
||||
|
||||
void StmtProfiler::VisitTemplateName(TemplateName Name) {
|
||||
if (Canonical)
|
||||
Name = Context.getCanonicalTemplateName(Name);
|
||||
|
||||
Name.Profile(ID);
|
||||
}
|
||||
|
||||
void StmtProfiler::VisitTemplateArguments(const TemplateArgumentLoc *Args,
|
||||
unsigned NumArgs) {
|
||||
ID.AddInteger(NumArgs);
|
||||
|
@ -1821,6 +1876,12 @@ void StmtProfiler::VisitTemplateArgument(const TemplateArgument &Arg) {
|
|||
|
||||
void Stmt::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context,
|
||||
bool Canonical) const {
|
||||
StmtProfiler Profiler(ID, Context, Canonical);
|
||||
StmtProfilerWithPointers Profiler(ID, Context, Canonical);
|
||||
Profiler.Visit(this);
|
||||
}
|
||||
|
||||
void Stmt::ProcessODRHash(llvm::FoldingSetNodeID &ID,
|
||||
class ODRHash &Hash) const {
|
||||
StmtProfilerWithoutPointers Profiler(ID, Hash);
|
||||
Profiler.Visit(this);
|
||||
}
|
||||
|
|
|
@ -13705,8 +13705,11 @@ void Sema::ActOnTagFinishDefinition(Scope *S, Decl *TagD,
|
|||
RD->completeDefinition();
|
||||
}
|
||||
|
||||
if (isa<CXXRecordDecl>(Tag))
|
||||
if (auto *RD = dyn_cast<CXXRecordDecl>(Tag)) {
|
||||
FieldCollector->FinishClass();
|
||||
if (Context.getLangOpts().Modules)
|
||||
RD->computeODRHash();
|
||||
}
|
||||
|
||||
// Exit this scope of this tag's definition.
|
||||
PopDeclContext();
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "clang/AST/Expr.h"
|
||||
#include "clang/AST/ExprCXX.h"
|
||||
#include "clang/AST/NestedNameSpecifier.h"
|
||||
#include "clang/AST/ODRHash.h"
|
||||
#include "clang/AST/RawCommentList.h"
|
||||
#include "clang/AST/Type.h"
|
||||
#include "clang/AST/TypeLocVisitor.h"
|
||||
|
@ -8884,21 +8885,638 @@ void ASTReader::diagnoseOdrViolations() {
|
|||
for (auto *RD : Merge.second) {
|
||||
// Multiple different declarations got merged together; tell the user
|
||||
// where they came from.
|
||||
if (Merge.first != RD) {
|
||||
// FIXME: Walk the definition, figure out what's different,
|
||||
// and diagnose that.
|
||||
if (!Diagnosed) {
|
||||
std::string Module = getOwningModuleNameForDiagnostic(Merge.first);
|
||||
Diag(Merge.first->getLocation(),
|
||||
diag::err_module_odr_violation_different_definitions)
|
||||
<< Merge.first << Module.empty() << Module;
|
||||
Diagnosed = true;
|
||||
if (Merge.first == RD)
|
||||
continue;
|
||||
|
||||
llvm::SmallVector<std::pair<Decl *, unsigned>, 4> FirstHashes;
|
||||
llvm::SmallVector<std::pair<Decl *, unsigned>, 4> SecondHashes;
|
||||
ODRHash Hash;
|
||||
for (auto D : Merge.first->decls()) {
|
||||
if (D->isImplicit())
|
||||
continue;
|
||||
Hash.clear();
|
||||
Hash.AddDecl(D);
|
||||
FirstHashes.emplace_back(D, Hash.CalculateHash());
|
||||
}
|
||||
for (auto D : RD->decls()) {
|
||||
if (D->isImplicit())
|
||||
continue;
|
||||
Hash.clear();
|
||||
Hash.AddDecl(D);
|
||||
SecondHashes.emplace_back(D, Hash.CalculateHash());
|
||||
}
|
||||
|
||||
// Used with err_module_odr_violation_mismatch_decl and
|
||||
// note_module_odr_violation_mismatch_decl
|
||||
enum {
|
||||
EndOfClass,
|
||||
PublicSpecifer,
|
||||
PrivateSpecifer,
|
||||
ProtectedSpecifer,
|
||||
Friend,
|
||||
Enum,
|
||||
StaticAssert,
|
||||
Typedef,
|
||||
TypeAlias,
|
||||
CXXMethod,
|
||||
CXXConstructor,
|
||||
CXXDestructor,
|
||||
CXXConversion,
|
||||
Field,
|
||||
Other
|
||||
} FirstDiffType = Other,
|
||||
SecondDiffType = Other;
|
||||
|
||||
auto DifferenceSelector = [](Decl *D) {
|
||||
assert(D && "valid Decl required");
|
||||
switch (D->getKind()) {
|
||||
default:
|
||||
return Other;
|
||||
case Decl::AccessSpec:
|
||||
switch (D->getAccess()) {
|
||||
case AS_public:
|
||||
return PublicSpecifer;
|
||||
case AS_private:
|
||||
return PrivateSpecifer;
|
||||
case AS_protected:
|
||||
return ProtectedSpecifer;
|
||||
case AS_none:
|
||||
llvm_unreachable("Invalid access specifier");
|
||||
}
|
||||
case Decl::Friend:
|
||||
return Friend;
|
||||
case Decl::Enum:
|
||||
return Enum;
|
||||
case Decl::StaticAssert:
|
||||
return StaticAssert;
|
||||
case Decl::Typedef:
|
||||
return Typedef;
|
||||
case Decl::TypeAlias:
|
||||
return TypeAlias;
|
||||
case Decl::CXXMethod:
|
||||
return CXXMethod;
|
||||
case Decl::CXXConstructor:
|
||||
return CXXConstructor;
|
||||
case Decl::CXXDestructor:
|
||||
return CXXDestructor;
|
||||
case Decl::CXXConversion:
|
||||
return CXXConversion;
|
||||
case Decl::Field:
|
||||
return Field;
|
||||
}
|
||||
};
|
||||
Decl *FirstDecl = nullptr;
|
||||
Decl *SecondDecl = nullptr;
|
||||
auto FirstIt = FirstHashes.begin();
|
||||
auto SecondIt = SecondHashes.begin();
|
||||
|
||||
// If there is a diagnoseable difference, FirstDiffType and
|
||||
// SecondDiffType will not be Other and FirstDecl and SecondDecl will be
|
||||
// filled in if not EndOfClass.
|
||||
while (FirstIt != FirstHashes.end() || SecondIt != SecondHashes.end()) {
|
||||
if (FirstIt->second == SecondIt->second) {
|
||||
++FirstIt;
|
||||
++SecondIt;
|
||||
continue;
|
||||
}
|
||||
|
||||
FirstDecl = FirstIt == FirstHashes.end() ? nullptr : FirstIt->first;
|
||||
SecondDecl = SecondIt == SecondHashes.end() ? nullptr : SecondIt->first;
|
||||
|
||||
FirstDiffType = FirstDecl ? DifferenceSelector(FirstDecl) : EndOfClass;
|
||||
SecondDiffType =
|
||||
SecondDecl ? DifferenceSelector(SecondDecl) : EndOfClass;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (FirstDiffType == Other || SecondDiffType == Other) {
|
||||
// Reaching this point means an unexpected Decl was encountered
|
||||
// or no difference was detected. This causes a generic error
|
||||
// message to be emitted.
|
||||
std::string Module = getOwningModuleNameForDiagnostic(Merge.first);
|
||||
Diag(Merge.first->getLocation(),
|
||||
diag::err_module_odr_violation_different_definitions)
|
||||
<< Merge.first << Module.empty() << Module;
|
||||
|
||||
|
||||
|
||||
Diag(RD->getLocation(),
|
||||
diag::note_module_odr_violation_different_definitions)
|
||||
<< getOwningModuleNameForDiagnostic(RD);
|
||||
<< getOwningModuleNameForDiagnostic(RD);
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
std::string FirstModule = getOwningModuleNameForDiagnostic(Merge.first);
|
||||
std::string SecondModule = getOwningModuleNameForDiagnostic(RD);
|
||||
|
||||
if (FirstDiffType != SecondDiffType) {
|
||||
SourceLocation FirstLoc;
|
||||
SourceRange FirstRange;
|
||||
if (FirstDiffType == EndOfClass) {
|
||||
FirstLoc = Merge.first->getBraceRange().getEnd();
|
||||
} else {
|
||||
FirstLoc = FirstIt->first->getLocation();
|
||||
FirstRange = FirstIt->first->getSourceRange();
|
||||
}
|
||||
Diag(FirstLoc, diag::err_module_odr_violation_mismatch_decl)
|
||||
<< Merge.first << FirstModule.empty() << FirstModule << FirstRange
|
||||
<< FirstDiffType;
|
||||
|
||||
SourceLocation SecondLoc;
|
||||
SourceRange SecondRange;
|
||||
if (SecondDiffType == EndOfClass) {
|
||||
SecondLoc = RD->getBraceRange().getEnd();
|
||||
} else {
|
||||
SecondLoc = SecondDecl->getLocation();
|
||||
SecondRange = SecondDecl->getSourceRange();
|
||||
}
|
||||
Diag(SecondLoc, diag::note_module_odr_violation_mismatch_decl)
|
||||
<< SecondModule << SecondRange << SecondDiffType;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Used with err_module_odr_violation_mismatch_decl_diff and
|
||||
// note_module_odr_violation_mismatch_decl_diff
|
||||
enum ODRDeclDifference{
|
||||
FriendName,
|
||||
EnumName,
|
||||
EnumConstantName,
|
||||
EnumConstantInit,
|
||||
EnumConstantNoInit,
|
||||
EnumConstantDiffInit,
|
||||
EnumNumberOfConstants,
|
||||
StaticAssertCondition,
|
||||
StaticAssertMessage,
|
||||
StaticAssertOnlyMessage,
|
||||
TypedefName,
|
||||
MethodName,
|
||||
MethodStatic,
|
||||
MethodInline,
|
||||
MethodConst,
|
||||
MethodNumParams,
|
||||
MethodParamName,
|
||||
MethodParamType,
|
||||
MethodDefaultArg,
|
||||
MethodOnlyDefaultArg,
|
||||
MethodOnlyBody,
|
||||
MethodBody,
|
||||
FieldName,
|
||||
FieldSingleBitField,
|
||||
FieldMutable,
|
||||
};
|
||||
|
||||
// These lambdas have the common portions of the ODR diagnostics. This
|
||||
// has the same return as Diag(), so addition parameters can be passed
|
||||
// in with operator<<
|
||||
auto ODRDiagError = [&Merge, &FirstModule, this](
|
||||
SourceLocation Loc, SourceRange Range, ODRDeclDifference DiffType) {
|
||||
return Diag(Loc, diag::err_module_odr_violation_mismatch_decl_diff)
|
||||
<< Merge.first << FirstModule.empty() << FirstModule << Range
|
||||
<< DiffType;
|
||||
};
|
||||
auto ODRDiagNote = [&SecondModule, this](
|
||||
SourceLocation Loc, SourceRange Range, ODRDeclDifference DiffType) {
|
||||
return Diag(Loc, diag::note_module_odr_violation_mismatch_decl_diff)
|
||||
<< SecondModule << Range << DiffType;
|
||||
};
|
||||
|
||||
auto ComputeODRHash = [&Hash](const Stmt* S) {
|
||||
assert(S);
|
||||
Hash.clear();
|
||||
Hash.AddStmt(S);
|
||||
return Hash.CalculateHash();
|
||||
};
|
||||
|
||||
// At this point, both decls are of the same type. Dive down deeper into
|
||||
// the Decl to determine where the first difference is located.
|
||||
switch (FirstDiffType) {
|
||||
case Friend: {
|
||||
FriendDecl *FirstFriend = cast<FriendDecl>(FirstDecl);
|
||||
FriendDecl *SecondFriend = cast<FriendDecl>(SecondDecl);
|
||||
{
|
||||
auto D = ODRDiagError(FirstFriend->getFriendLoc(),
|
||||
FirstFriend->getSourceRange(), FriendName);
|
||||
if (TypeSourceInfo *FirstTSI = FirstFriend->getFriendType())
|
||||
D << FirstTSI->getType();
|
||||
else
|
||||
D << FirstFriend->getFriendDecl();
|
||||
}
|
||||
{
|
||||
auto D = ODRDiagNote(SecondFriend->getFriendLoc(),
|
||||
SecondFriend->getSourceRange(), FriendName);
|
||||
if (TypeSourceInfo *SecondTSI = SecondFriend->getFriendType())
|
||||
D << SecondTSI->getType();
|
||||
else
|
||||
D << SecondFriend->getFriendDecl();
|
||||
}
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
case Enum: {
|
||||
EnumDecl *FirstEnum = cast<EnumDecl>(FirstDecl);
|
||||
EnumDecl *SecondEnum = cast<EnumDecl>(SecondDecl);
|
||||
if (FirstEnum->getName() != SecondEnum->getName()) {
|
||||
ODRDiagError(FirstEnum->getLocStart(), FirstEnum->getSourceRange(),
|
||||
EnumName)
|
||||
<< FirstEnum;
|
||||
ODRDiagNote(SecondEnum->getLocStart(), SecondEnum->getSourceRange(),
|
||||
EnumName)
|
||||
<< SecondEnum;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Don't use EnumDecl::enumerator_{begin,end}. Decl merging can
|
||||
// cause the iterators from them to be the same for both Decl's.
|
||||
EnumDecl::enumerator_iterator FirstEnumIt(FirstEnum->decls_begin());
|
||||
EnumDecl::enumerator_iterator FirstEnumEnd(FirstEnum->decls_end());
|
||||
EnumDecl::enumerator_iterator SecondEnumIt(SecondEnum->decls_begin());
|
||||
EnumDecl::enumerator_iterator SecondEnumEnd(SecondEnum->decls_end());
|
||||
int NumElements = 0;
|
||||
for (; FirstEnumIt != FirstEnumEnd && SecondEnumIt != SecondEnumEnd;
|
||||
++FirstEnumIt, ++SecondEnumIt, ++NumElements) {
|
||||
if (FirstEnumIt->getName() != SecondEnumIt->getName()) {
|
||||
ODRDiagError(FirstEnumIt->getLocStart(),
|
||||
FirstEnumIt->getSourceRange(), EnumConstantName)
|
||||
<< *FirstEnumIt << FirstEnum;
|
||||
ODRDiagNote(SecondEnumIt->getLocStart(),
|
||||
SecondEnumIt->getSourceRange(), EnumConstantName)
|
||||
<< *SecondEnumIt << SecondEnum;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
Expr *FirstInit = FirstEnumIt->getInitExpr();
|
||||
Expr *SecondInit = SecondEnumIt->getInitExpr();
|
||||
|
||||
if (FirstInit && !SecondInit) {
|
||||
ODRDiagError(FirstEnumIt->getLocStart(),
|
||||
FirstEnumIt->getSourceRange(), EnumConstantInit)
|
||||
<< *FirstEnumIt << FirstEnum;
|
||||
|
||||
ODRDiagNote(SecondEnumIt->getLocStart(),
|
||||
SecondEnumIt->getSourceRange(), EnumConstantNoInit)
|
||||
<< *SecondEnumIt << SecondEnum;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!FirstInit && SecondInit) {
|
||||
ODRDiagError(FirstEnumIt->getLocStart(),
|
||||
FirstEnumIt->getSourceRange(), EnumConstantNoInit)
|
||||
<< *FirstEnumIt << FirstEnum;
|
||||
ODRDiagNote(SecondEnumIt->getLocStart(),
|
||||
SecondEnumIt->getSourceRange(), EnumConstantInit)
|
||||
<< *SecondEnumIt << SecondEnum;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (FirstInit == SecondInit)
|
||||
continue;
|
||||
|
||||
unsigned FirstODRHash = ComputeODRHash(FirstInit);
|
||||
unsigned SecondODRHash = ComputeODRHash(SecondInit);
|
||||
|
||||
if (FirstODRHash != SecondODRHash) {
|
||||
ODRDiagError(FirstEnumIt->getLocStart(),
|
||||
FirstEnumIt->getSourceRange(), EnumConstantDiffInit)
|
||||
<< *FirstEnumIt << FirstEnum;
|
||||
ODRDiagNote(SecondEnumIt->getLocStart(),
|
||||
SecondEnumIt->getSourceRange(), EnumConstantDiffInit)
|
||||
<< *SecondEnumIt << SecondEnum;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (FirstEnumIt == FirstEnumEnd && SecondEnumIt != SecondEnumEnd) {
|
||||
unsigned FirstEnumSize = NumElements;
|
||||
unsigned SecondEnumSize = NumElements;
|
||||
for (; SecondEnumIt != SecondEnumEnd; ++SecondEnumIt)
|
||||
++SecondEnumSize;
|
||||
ODRDiagError(FirstEnum->getLocStart(), FirstEnum->getSourceRange(),
|
||||
EnumNumberOfConstants)
|
||||
<< FirstEnum << FirstEnumSize;
|
||||
ODRDiagNote(SecondEnum->getLocStart(), SecondEnum->getSourceRange(),
|
||||
EnumNumberOfConstants)
|
||||
<< SecondEnum << SecondEnumSize;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (FirstEnumIt != FirstEnumEnd && SecondEnumIt == SecondEnumEnd) {
|
||||
unsigned FirstEnumSize = NumElements;
|
||||
unsigned SecondEnumSize = NumElements;
|
||||
for (; FirstEnumIt != FirstEnumEnd; ++FirstEnumIt)
|
||||
++FirstEnumSize;
|
||||
ODRDiagError(FirstEnum->getLocStart(), FirstEnum->getSourceRange(),
|
||||
EnumNumberOfConstants)
|
||||
<< FirstEnum << FirstEnumSize;
|
||||
ODRDiagNote(SecondEnum->getLocStart(), SecondEnum->getSourceRange(),
|
||||
EnumNumberOfConstants)
|
||||
<< SecondEnum << SecondEnumSize;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case StaticAssert: {
|
||||
StaticAssertDecl *FirstSA = cast<StaticAssertDecl>(FirstDecl);
|
||||
StaticAssertDecl *SecondSA = cast<StaticAssertDecl>(SecondDecl);
|
||||
|
||||
Expr *FirstExpr = FirstSA->getAssertExpr();
|
||||
Expr *SecondExpr = SecondSA->getAssertExpr();
|
||||
unsigned FirstODRHash = ComputeODRHash(FirstExpr);
|
||||
unsigned SecondODRHash = ComputeODRHash(SecondExpr);
|
||||
if (FirstODRHash != SecondODRHash) {
|
||||
ODRDiagError(FirstExpr->getLocStart(), FirstExpr->getSourceRange(),
|
||||
StaticAssertCondition);
|
||||
ODRDiagNote(SecondExpr->getLocStart(),
|
||||
SecondExpr->getSourceRange(), StaticAssertCondition);
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
StringLiteral *FirstStr = FirstSA->getMessage();
|
||||
StringLiteral *SecondStr = SecondSA->getMessage();
|
||||
if ((FirstStr && !SecondStr) || (!FirstStr && SecondStr)) {
|
||||
SourceLocation FirstLoc, SecondLoc;
|
||||
SourceRange FirstRange, SecondRange;
|
||||
if (FirstStr) {
|
||||
FirstLoc = FirstStr->getLocStart();
|
||||
FirstRange = FirstStr->getSourceRange();
|
||||
} else {
|
||||
FirstLoc = FirstSA->getLocStart();
|
||||
FirstRange = FirstSA->getSourceRange();
|
||||
}
|
||||
if (SecondStr) {
|
||||
SecondLoc = SecondStr->getLocStart();
|
||||
SecondRange = SecondStr->getSourceRange();
|
||||
} else {
|
||||
SecondLoc = SecondSA->getLocStart();
|
||||
SecondRange = SecondSA->getSourceRange();
|
||||
}
|
||||
ODRDiagError(FirstLoc, FirstRange, StaticAssertOnlyMessage)
|
||||
<< (FirstStr == nullptr);
|
||||
ODRDiagNote(SecondLoc, SecondRange, StaticAssertOnlyMessage)
|
||||
<< (SecondStr == nullptr);
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (FirstStr && SecondStr &&
|
||||
FirstStr->getString() != SecondStr->getString()) {
|
||||
ODRDiagError(FirstStr->getLocStart(), FirstStr->getSourceRange(),
|
||||
StaticAssertMessage);
|
||||
ODRDiagNote(SecondStr->getLocStart(), SecondStr->getSourceRange(),
|
||||
StaticAssertMessage);
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Typedef:
|
||||
case TypeAlias: {
|
||||
TypedefNameDecl *FirstTD = cast<TypedefNameDecl>(FirstDecl);
|
||||
TypedefNameDecl *SecondTD = cast<TypedefNameDecl>(SecondDecl);
|
||||
IdentifierInfo *FirstII = FirstTD->getIdentifier();
|
||||
IdentifierInfo *SecondII = SecondTD->getIdentifier();
|
||||
if (FirstII && SecondII && FirstII->getName() != SecondII->getName()) {
|
||||
ODRDiagError(FirstTD->getLocation(), FirstTD->getSourceRange(),
|
||||
TypedefName)
|
||||
<< (FirstDiffType == TypeAlias) << FirstII;
|
||||
ODRDiagNote(SecondTD->getLocation(), SecondTD->getSourceRange(),
|
||||
TypedefName)
|
||||
<< (FirstDiffType == TypeAlias) << SecondII;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CXXMethod:
|
||||
case CXXConstructor:
|
||||
case CXXConversion:
|
||||
case CXXDestructor: {
|
||||
// TODO: Merge with existing method diff logic.
|
||||
CXXMethodDecl *FirstMD = cast<CXXMethodDecl>(FirstDecl);
|
||||
CXXMethodDecl *SecondMD = cast<CXXMethodDecl>(SecondDecl);
|
||||
IdentifierInfo *FirstII = FirstMD->getIdentifier();
|
||||
IdentifierInfo *SecondII = SecondMD->getIdentifier();
|
||||
if (FirstII && SecondII && FirstII->getName() != SecondII->getName()) {
|
||||
ODRDiagError(FirstMD->getLocation(), FirstMD->getSourceRange(),
|
||||
MethodName)
|
||||
<< FirstII;
|
||||
ODRDiagNote(SecondMD->getLocation(), SecondMD->getSourceRange(),
|
||||
MethodName)
|
||||
<< SecondII;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bool FirstStatic = FirstMD->getStorageClass() == SC_Static;
|
||||
bool SecondStatic = SecondMD->getStorageClass() == SC_Static;
|
||||
if (FirstStatic != SecondStatic) {
|
||||
ODRDiagError(FirstMD->getLocation(), FirstMD->getSourceRange(),
|
||||
MethodStatic)
|
||||
<< FirstMD << FirstStatic;
|
||||
ODRDiagNote(SecondMD->getLocation(), SecondMD->getSourceRange(),
|
||||
MethodStatic)
|
||||
<< SecondMD << SecondStatic;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bool FirstInline = FirstMD->isInlineSpecified();
|
||||
bool SecondInline = SecondMD->isInlineSpecified();
|
||||
if (FirstInline != SecondInline) {
|
||||
ODRDiagError(FirstMD->getLocation(), FirstMD->getSourceRange(),
|
||||
MethodInline)
|
||||
<< FirstMD << FirstInline;
|
||||
ODRDiagNote(SecondMD->getLocation(), SecondMD->getSourceRange(),
|
||||
MethodInline)
|
||||
<< SecondMD << SecondInline;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bool FirstConst = FirstMD->isConst();
|
||||
bool SecondConst = SecondMD->isConst();
|
||||
if (FirstConst != SecondConst) {
|
||||
ODRDiagError(FirstMD->getLocation(), FirstMD->getSourceRange(),
|
||||
MethodConst)
|
||||
<< FirstMD << FirstInline;
|
||||
ODRDiagNote(SecondMD->getLocation(), SecondMD->getSourceRange(),
|
||||
MethodConst)
|
||||
<< SecondMD << SecondInline;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (FirstMD->getNumParams() != SecondMD->getNumParams()) {
|
||||
ODRDiagError(FirstMD->getLocation(), FirstMD->getSourceRange(),
|
||||
MethodNumParams)
|
||||
<< SecondMD << FirstMD->getNumParams();
|
||||
ODRDiagNote(SecondMD->getLocation(), SecondMD->getSourceRange(),
|
||||
MethodNumParams)
|
||||
<< SecondMD << SecondMD->getNumParams();
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
for (unsigned i = 0, e = FirstMD->getNumParams(); i < e; ++i) {
|
||||
ParmVarDecl *FirstParam = FirstMD->getParamDecl(i);
|
||||
ParmVarDecl *SecondParam = SecondMD->getParamDecl(i);
|
||||
IdentifierInfo *FirstII = FirstParam->getIdentifier();
|
||||
IdentifierInfo *SecondII = SecondParam->getIdentifier();
|
||||
if ((!FirstII && SecondII) || (FirstII && !SecondII) ||
|
||||
(FirstII && SecondII &&
|
||||
FirstII->getName() != SecondII->getName())) {
|
||||
ODRDiagError(FirstParam->getLocation(),
|
||||
FirstParam->getSourceRange(), MethodParamName)
|
||||
<< SecondMD << i + 1 << (FirstII == nullptr) << FirstII;
|
||||
ODRDiagNote(SecondParam->getLocation(),
|
||||
SecondParam->getSourceRange(), MethodParamName)
|
||||
<< SecondMD << i + 1 << (SecondII == nullptr) << SecondII;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (FirstParam->getType() != SecondParam->getType()) {
|
||||
ODRDiagError(FirstParam->getLocation(),
|
||||
FirstParam->getSourceRange(), MethodParamType)
|
||||
<< SecondMD << i + 1 << FirstParam->getType();
|
||||
ODRDiagNote(SecondParam->getLocation(),
|
||||
SecondParam->getSourceRange(), MethodParamType)
|
||||
<< SecondMD << i + 1 << SecondParam->getType();
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
Expr *FirstDefaultArg = FirstParam->getDefaultArg();
|
||||
Expr *SecondDefaultArg = SecondParam->getDefaultArg();
|
||||
if ((!FirstDefaultArg && SecondDefaultArg) ||
|
||||
(FirstDefaultArg && !SecondDefaultArg)) {
|
||||
ODRDiagError(FirstParam->getLocation(),
|
||||
FirstParam->getSourceRange(), MethodOnlyDefaultArg)
|
||||
<< SecondMD << i + 1 << (FirstDefaultArg != nullptr);
|
||||
ODRDiagNote(SecondParam->getLocation(),
|
||||
SecondParam->getSourceRange(), MethodOnlyDefaultArg)
|
||||
<< SecondMD << i + 1 << (SecondDefaultArg != nullptr);
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (FirstDefaultArg && SecondDefaultArg) {
|
||||
unsigned FirstODRHash = ComputeODRHash(FirstDefaultArg);
|
||||
unsigned SecondODRHash = ComputeODRHash(SecondDefaultArg);
|
||||
if (FirstODRHash != SecondODRHash) {
|
||||
ODRDiagError(FirstParam->getLocation(),
|
||||
FirstParam->getSourceRange(), MethodDefaultArg)
|
||||
<< SecondMD << i + 1;
|
||||
ODRDiagNote(SecondParam->getLocation(),
|
||||
SecondParam->getSourceRange(), MethodDefaultArg)
|
||||
<< SecondMD << i + 1;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Figure out how to diagnose different function bodies.
|
||||
// Deserialization does not import the second function body.
|
||||
|
||||
break;
|
||||
}
|
||||
case Field: {
|
||||
// TODO: Merge with exising field diff logic.
|
||||
FieldDecl *FirstField = cast<FieldDecl>(FirstDecl);
|
||||
FieldDecl *SecondField = cast<FieldDecl>(SecondDecl);
|
||||
IdentifierInfo *FirstII = FirstField->getIdentifier();
|
||||
IdentifierInfo *SecondII = SecondField->getIdentifier();
|
||||
if (FirstII->getName() != SecondII->getName()) {
|
||||
ODRDiagError(FirstField->getLocation(), FirstField->getSourceRange(),
|
||||
FieldName)
|
||||
<< FirstII;
|
||||
ODRDiagNote(SecondField->getLocation(), SecondField->getSourceRange(),
|
||||
FieldName)
|
||||
<< SecondII;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// This case is handled elsewhere.
|
||||
if (FirstField->getType() != SecondField->getType()) {
|
||||
break;
|
||||
}
|
||||
|
||||
bool FirstBitField = FirstField->isBitField();
|
||||
bool SecondBitField = SecondField->isBitField();
|
||||
if (FirstBitField != SecondBitField) {
|
||||
ODRDiagError(FirstField->getLocation(), FirstField->getSourceRange(),
|
||||
FieldSingleBitField)
|
||||
<< FirstII << FirstBitField;
|
||||
ODRDiagNote(SecondField->getLocation(), SecondField->getSourceRange(),
|
||||
FieldSingleBitField)
|
||||
<< SecondII << SecondBitField;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (FirstBitField && SecondBitField) {
|
||||
Expr* FirstWidth = FirstField->getBitWidth();
|
||||
Expr *SecondWidth = SecondField->getBitWidth();
|
||||
unsigned FirstODRHash = ComputeODRHash(FirstWidth);
|
||||
unsigned SecondODRHash = ComputeODRHash(SecondWidth);
|
||||
if (FirstODRHash != SecondODRHash) {
|
||||
ODRDiagError(FirstField->getLocation(),
|
||||
FirstField->getSourceRange(), FieldSingleBitField)
|
||||
<< FirstII << FirstBitField;
|
||||
ODRDiagNote(SecondField->getLocation(),
|
||||
SecondField->getSourceRange(), FieldSingleBitField)
|
||||
<< SecondII << SecondBitField;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool FirstMutable = FirstField->isMutable();
|
||||
bool SecondMutable = SecondField->isMutable();
|
||||
if (FirstMutable != SecondMutable) {
|
||||
ODRDiagError(FirstField->getLocation(), FirstField->getSourceRange(),
|
||||
FieldMutable)
|
||||
<< FirstII << FirstMutable;
|
||||
ODRDiagNote(SecondField->getLocation(), SecondField->getSourceRange(),
|
||||
FieldMutable)
|
||||
<< SecondII << SecondMutable;
|
||||
Diagnosed = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Other:
|
||||
case EndOfClass:
|
||||
case PublicSpecifer:
|
||||
case PrivateSpecifer:
|
||||
case ProtectedSpecifer:
|
||||
llvm_unreachable("Invalid diff type");
|
||||
}
|
||||
|
||||
if (Diagnosed == true)
|
||||
continue;
|
||||
|
||||
// Unable to find difference in Decl's, print simple different
|
||||
// definitions diagnostic.
|
||||
Diag(Merge.first->getLocation(),
|
||||
diag::err_module_odr_violation_different_definitions)
|
||||
<< Merge.first << FirstModule.empty() << FirstModule;
|
||||
Diag(RD->getLocation(),
|
||||
diag::note_module_odr_violation_different_definitions)
|
||||
<< SecondModule;
|
||||
Diagnosed = true;
|
||||
}
|
||||
|
||||
if (!Diagnosed) {
|
||||
|
|
|
@ -1521,6 +1521,7 @@ void ASTDeclReader::ReadCXXDefinitionData(
|
|||
Data.ImplicitCopyAssignmentHasConstParam = Record.readInt();
|
||||
Data.HasDeclaredCopyConstructorWithConstParam = Record.readInt();
|
||||
Data.HasDeclaredCopyAssignmentWithConstParam = Record.readInt();
|
||||
Data.ODRHash = Record.readInt();
|
||||
|
||||
Data.NumBases = Record.readInt();
|
||||
if (Data.NumBases)
|
||||
|
@ -1651,6 +1652,7 @@ void ASTDeclReader::MergeDefinitionData(
|
|||
OR_FIELD(HasDeclaredCopyConstructorWithConstParam)
|
||||
OR_FIELD(HasDeclaredCopyAssignmentWithConstParam)
|
||||
MATCH_FIELD(IsLambda)
|
||||
MATCH_FIELD(ODRHash)
|
||||
#undef OR_FIELD
|
||||
#undef MATCH_FIELD
|
||||
|
||||
|
|
|
@ -5699,6 +5699,7 @@ void ASTRecordWriter::AddCXXDefinitionData(const CXXRecordDecl *D) {
|
|||
Record->push_back(Data.ImplicitCopyAssignmentHasConstParam);
|
||||
Record->push_back(Data.HasDeclaredCopyConstructorWithConstParam);
|
||||
Record->push_back(Data.HasDeclaredCopyAssignmentWithConstParam);
|
||||
Record->push_back(Data.ODRHash);
|
||||
// IsLambda bit is already saved.
|
||||
|
||||
Record->push_back(Data.NumBases);
|
||||
|
|
|
@ -37,6 +37,10 @@ template int UseAll<Y>();
|
|||
// Here, we're instantiating the definition from 'A' and merging the definition
|
||||
// from 'B' into it.
|
||||
|
||||
// expected-error@b.h:* {{'D::type' from module 'B' is not present in definition of 'D<T>' in module 'A'}}
|
||||
// expected-error@b.h:* {{'D::value' from module 'B' is not present in definition of 'D<T>' in module 'A'}}
|
||||
|
||||
|
||||
// expected-error@b.h:* {{'E::value' from module 'B' is not present in definition of 'E<T>' in module 'A'}}
|
||||
// expected-error@b.h:* {{'E::v' from module 'B' is not present in definition of 'E<T>' in module 'A'}}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue