Skip to content

Commit f71107f

Browse files
committed
Rework attribute handling based on review feedback
If either tag type has an attribute or has a member with an attribute, including keywords that are implemented via an attribute like _Alignas, then the types are treated as incompatible. A warning which defaults to an error is emitted; if the user downgrades the error back to a warning or disables the warning entirely, then it's undefined behavior and the user gets what they get. Eventually we will implement tablegen logic so attributes can be handled generically via information encoded in Attr.td.
1 parent e2867ee commit f71107f

File tree

3 files changed

+67
-259
lines changed

3 files changed

+67
-259
lines changed

clang/include/clang/Basic/DiagnosticASTKinds.td

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,8 +478,11 @@ def warn_odr_function_type_inconsistent : Warning<
478478
"external function %0 declared with incompatible types in different "
479479
"translation units (%1 vs. %2)">,
480480
InGroup<ODR>;
481-
def note_odr_attr : Note<"attribute %0 here">;
482-
def note_odr_attr_missing : Note<"no corresponding attribute here">;
481+
def warn_odr_tag_type_with_attributes : Warning<
482+
"type %0 has %select{an attribute|a member with an attribute}1 which "
483+
"currently causes the types to be treated as though they are incompatible">,
484+
InGroup<ODR>, DefaultError;
485+
def note_odr_attr_here : Note<"attribute %0 here">;
483486
def err_odr_tag_type_inconsistent
484487
: Error<"type %0 has incompatible definitions%select{| in different "
485488
"translation units}1">;

clang/lib/AST/ASTStructuralEquivalence.cpp

Lines changed: 28 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -453,109 +453,38 @@ class StmtComparer {
453453
};
454454
} // namespace
455455

456-
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
457-
const Attr *Attr1, const Attr *Attr2) {
458-
// Two attributes are structurally equivalent if they are the same kind
459-
// of attribute, spelled with the same spelling kind, and have the same
460-
// arguments. This means that [[noreturn]] and __attribute__((noreturn)) are
461-
// not structurally equivalent, nor are [[nodiscard("foo")]] and
462-
// [[nodiscard("bar")]].
463-
if (Attr1->getKind() != Attr2->getKind())
464-
return false;
465-
466-
if (Attr1->getSyntax() != Attr2->getSyntax())
467-
return false;
468-
469-
if (Attr1->getSpellingListIndex() != Attr2->getSpellingListIndex())
470-
return false;
471-
472-
auto GetAttrName = [](const Attr *A) {
473-
if (const IdentifierInfo *II = A->getAttrName())
474-
return II->getName();
475-
return StringRef{};
476-
};
477-
478-
if (GetAttrName(Attr1) != GetAttrName(Attr2))
479-
return false;
480-
481-
// FIXME: check the attribute arguments. Attr does not track the arguments on
482-
// the base class, which makes this awkward. We may want to tablegen a
483-
// comparison function for attributes? In the meantime, we're doing this the
484-
// cheap way by pretty printing the attributes and checking they produce
485-
// equivalent string representations.
486-
std::string AttrStr1, AttrStr2;
487-
PrintingPolicy DefaultPolicy(Context.LangOpts);
488-
llvm::raw_string_ostream SS1(AttrStr1), SS2(AttrStr2);
489-
Attr1->printPretty(SS1, DefaultPolicy);
490-
Attr2->printPretty(SS2, DefaultPolicy);
491-
492-
return SS1.str() == SS2.str();
493-
}
494-
495456
static bool
496457
CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
497458
const Decl *D1, const Decl *D2,
498459
const Decl *PrimaryDecl = nullptr) {
499-
// Gather the attributes and sort them by name so that they're in equivalent
500-
// orders. This means that __attribute__((foo, bar)) is equivalent to
501-
// __attribute__((bar, foo)).
502-
llvm::SmallVector<const Attr *, 2> Attrs1, Attrs2;
503-
llvm::copy(D1->attrs(), std::back_inserter(Attrs1));
504-
llvm::copy(D2->attrs(), std::back_inserter(Attrs2));
505-
506-
auto Sorter = [](const Attr *LHS, const Attr *RHS) {
507-
const IdentifierInfo *II1 = LHS->getAttrName(), *II2 = RHS->getAttrName();
508-
if (!II1 || !II2)
509-
return II1 == II2;
510-
return *II1 < *II2;
511-
};
512-
llvm::sort(Attrs1, Sorter);
513-
llvm::sort(Attrs2, Sorter);
514-
515-
auto A2 = Attrs2.begin(), A2End = Attrs2.end();
516-
const auto *DiagnoseDecl = cast<TypeDecl>(PrimaryDecl ? PrimaryDecl : D2);
517-
for (auto A1 = Attrs1.begin(), A1End = Attrs1.end(); A1 != A1End;
518-
++A1, ++A2) {
519-
if (A2 == A2End) {
520-
if (Context.Complain) {
521-
Context.Diag2(DiagnoseDecl->getLocation(),
522-
Context.getApplicableDiagnostic(
523-
diag::err_odr_tag_type_inconsistent))
524-
<< Context.ToCtx.getTypeDeclType(DiagnoseDecl)
525-
<< (&Context.FromCtx != &Context.ToCtx);
526-
Context.Diag1((*A1)->getLocation(), diag::note_odr_attr) << *A1;
527-
Context.Diag2(D2->getLocation(), diag::note_odr_attr_missing);
528-
}
529-
return false;
530-
}
531-
532-
if (!IsStructurallyEquivalent(Context, *A1, *A2)) {
533-
if (Context.Complain) {
534-
Context.Diag2(DiagnoseDecl->getLocation(),
535-
Context.getApplicableDiagnostic(
536-
diag::err_odr_tag_type_inconsistent))
537-
<< Context.ToCtx.getTypeDeclType(DiagnoseDecl)
538-
<< (&Context.FromCtx != &Context.ToCtx);
539-
Context.Diag2((*A2)->getLocation(), diag::note_odr_attr) << *A2;
540-
Context.Diag1((*A1)->getLocation(), diag::note_odr_attr) << *A1;
541-
}
542-
return false;
543-
}
544-
}
545-
546-
if (A2 != A2End) {
547-
if (Context.Complain) {
548-
Context.Diag2(
549-
DiagnoseDecl->getLocation(),
550-
Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
551-
<< Context.ToCtx.getTypeDeclType(DiagnoseDecl)
552-
<< (&Context.FromCtx != &Context.ToCtx);
553-
Context.Diag1(D1->getLocation(), diag::note_odr_attr_missing);
554-
Context.Diag1((*A2)->getLocation(), diag::note_odr_attr) << *A2;
555-
}
556-
return false;
557-
}
558-
460+
// If either declaration has an attribute on it, we treat the declarations
461+
// as not being structurally equivalent.
462+
// FIXME: this should be handled on a case-by-case basis via tablegen in
463+
// Attr.td. There are multiple cases to consider: one declation with the
464+
// attribute, another without it; different attribute syntax|spellings for
465+
// the same semantic attribute, differences in attribute arguments, order
466+
// in which attributes are applied, how to merge attributes if the types are
467+
// structurally equivalent, etc.
468+
const Attr *D1Attr = nullptr, *D2Attr = nullptr;
469+
if (D1->hasAttrs())
470+
D1Attr = *D1->getAttrs().begin();
471+
if (D2->hasAttrs())
472+
D2Attr = *D2->getAttrs().begin();
473+
if (D1Attr || D2Attr) {
474+
const auto *DiagnoseDecl = cast<TypeDecl>(PrimaryDecl ? PrimaryDecl : D2);
475+
Context.Diag2(DiagnoseDecl->getLocation(),
476+
diag::warn_odr_tag_type_with_attributes)
477+
<< Context.ToCtx.getTypeDeclType(DiagnoseDecl)
478+
<< (PrimaryDecl != nullptr);
479+
if (D1Attr)
480+
Context.Diag1(D1Attr->getLoc(), diag::note_odr_attr_here) << D1Attr;
481+
if (D2Attr)
482+
Context.Diag1(D2Attr->getLoc(), diag::note_odr_attr_here) << D2Attr;
483+
}
484+
485+
// The above diagnostic is a warning which defaults to an error. If treated
486+
// as a warning, we'll go ahead and allow any attribute differences to be
487+
// undefined behavior and the user gets what they get in terms of behavior.
559488
return true;
560489
}
561490

0 commit comments

Comments
 (0)