Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 103 additions & 1 deletion clang/lib/AST/ItaniumMangle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "clang/AST/ExprObjC.h"
#include "clang/AST/LocInfoType.h"
#include "clang/AST/Mangle.h"
#include "clang/AST/ODRHash.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/ABI.h"
#include "clang/Basic/Module.h"
Expand Down Expand Up @@ -5001,6 +5002,33 @@ void CXXNameMangler::mangleReflection(const APValue &R) {
} else if (auto *DD = dyn_cast<CXXDestructorDecl>(D)) {
GlobalDecl GD(DD, Dtor_Complete);
mangle(GD);
} else if (auto *DG = dyn_cast<CXXDeductionGuideDecl>(D)) {
// A deduction-guide SPECIALIZATION reflection (e.g. obtained via
// substitute on the guide template) is Declaration-kind: mangle() would
// route it through mangleFunctionEncoding -> mangleUnqualifiedName,
// whose CXXDeductionGuideName case is unreachable. Encode it like the
// Template-kind guides below: "dg" + the deduced template + an ODR-hash
// discriminator, with the specialization's own function type folded in
// (AddFunctionDecl no-ops in specialization context, so the type is
// what separates Box<int>'s guide from Box<float>'s).
Out << "dg";
if (TemplateDecl *Deduced = DG->getDeducedTemplate())
mangleTemplateName(Deduced, /*Args=*/{});
ODRHash Hash;
Hash.AddBoolean(DG->isImplicit());
DeductionCandidate DCK = DG->getDeductionCandidateKind();
Hash.AddBoolean(DCK == DeductionCandidate::Copy);
Hash.AddBoolean(DCK == DeductionCandidate::Aggregate);
if (FunctionTemplateDecl *Primary = DG->getPrimaryTemplate()) {
Hash.AddTemplateParameterList(Primary->getTemplateParameters());
Hash.AddFunctionDecl(
cast<CXXDeductionGuideDecl>(Primary->getTemplatedDecl()),
/*SkipBody=*/true);
} else {
Hash.AddFunctionDecl(DG, /*SkipBody=*/true);
}
Hash.AddQualType(DG->getType());
Out << '$' << Hash.CalculateHash() << '$';
} else {
mangle(cast<NamedDecl>(D));
}
Expand All @@ -5021,8 +5049,82 @@ void CXXNameMangler::mangleReflection(const APValue &R) {
case ReflectionKind::Template: {
Out << 't';

TemplateDecl *TD = R.getReflectedTemplate().getAsTemplateDecl();

// A deduction guide's DeclarationName (CXXDeductionGuideName) has no
// <unqualified-name> encoding: mangleTemplateName would reach the
// llvm_unreachable in mangleUnqualifiedName. members_of over a
// namespace enumerates guides like any other member, and lifting that
// list into define_static_array makes each one a reflection template
// argument, so they MUST mangle. Encode "dg" + the deduced template's
// name + the same '$'-bracketed ODR-hash discriminator used for
// overloaded function templates below: every guide for one template
// shares a single DeclarationName (and implicit/copy guides can be
// enumerated alongside explicit ones once CTAD has been used in the TU),
// so the hash of the template head + declaration pattern is what keeps
// two different guides for the same template distinct. Cross-TU-stable by
// design, preserving legitimate linkonce_odr merging.
if (auto *FTD = dyn_cast<FunctionTemplateDecl>(TD)) {
if (auto *DG = dyn_cast<CXXDeductionGuideDecl>(FTD->getTemplatedDecl())) {
Out << "dg";
if (TemplateDecl *Deduced = DG->getDeducedTemplate())
mangleTemplateName(Deduced, /*Args=*/{});
ODRHash Hash;
// The structural hash alone cannot separate an EXPLICIT guide from
// the IMPLICIT guide Sema declares for the same-signature
// constructor, nor a per-constructor guide from the copy guide when
// their signatures coincide (X(X<E>)); fold implicitness and the
// deduction-candidate kind in as well -- all of these enumerate
// side by side and are distinct reflections.
Hash.AddBoolean(DG->isImplicit());
DeductionCandidate DCK = DG->getDeductionCandidateKind();
Hash.AddBoolean(DCK == DeductionCandidate::Copy);
Hash.AddBoolean(DCK == DeductionCandidate::Aggregate);
Hash.AddTemplateParameterList(FTD->getTemplateParameters());
Hash.AddFunctionDecl(DG, /*SkipBody=*/true);
Out << '$' << Hash.CalculateHash() << '$';
break;
}
}

ArrayRef<TemplateArgument> Args;
mangleTemplateName(R.getReflectedTemplate().getAsTemplateDecl(), Args);
mangleTemplateName(TD, Args);
// The name alone identifies class/variable/alias templates, but function
// templates OVERLOAD: two same-named siblings (e.g. absl raw_hash_map's
// lifetimebound operator[] pair) mangled identically here, so a template
// taking the reflection as an NTTP got ONE mangled name for its two
// specializations -- CodeGen then silently folds the linkonce_odr
// definitions and a single body serves both call sites (the AST-level
// specializations are correct and distinct; no diagnostic anywhere). Append a structural
// digest of the template head + declaration pattern to discriminate the
// overloads. An ODR hash (cross-TU-stable by design; it is how modules
// compare decls between TUs) rather than a structural mangling of the
// pattern's function type: dependent pattern types from real code embed
// parameter-referencing expressions (lifetimebound SFINAE, noexcept(...))
// that the mangler cannot encode outside a function-declaration context
// (mangleFunctionParam asserts).
if (auto *FTD = dyn_cast<FunctionTemplateDecl>(TD)) {
ODRHash Hash;
Hash.AddTemplateParameterList(FTD->getTemplateParameters());
const FunctionDecl *Pattern = FTD->getTemplatedDecl();
Hash.AddFunctionDecl(Pattern, /*SkipBody=*/true);
// AddFunctionDecl silently NO-OPS for a declaration in "specialization
// context" (a member template of a class template specialization, the
// common members_of shape), so siblings sharing one template head
// hashed identically there -- tl::expected<T,E>'s four value()
// overloads (const&/&/const&&/&&, identical heads) all folded. Hash
// the pattern's function type (return type, parameter
// types, cv-quals) and its ref-qualifier (which even
// VisitFunctionProtoType omits) as well; the ODR type hash handles the
// dependent pattern types that a structural MANGLING of the type
// cannot (see above).
Hash.AddQualType(Pattern->getType());
if (const auto *FPT = Pattern->getType()->getAs<FunctionProtoType>()) {
Hash.AddBoolean(FPT->getRefQualifier() == RQ_LValue);
Hash.AddBoolean(FPT->getRefQualifier() == RQ_RValue);
}
Out << '$' << Hash.CalculateHash() << '$';
}
break;
}
case ReflectionKind::Namespace: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//===----------------------------------------------------------------------===//
//
// Copyright 2024 Bloomberg Finance L.P.
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03 || c++11 || c++14 || c++17 || c++20
// ADDITIONAL_COMPILE_FLAGS: -freflection-latest

// <experimental/reflection>
//
// [reflection]
//
// Regression test: a reflection of a DEDUCTION GUIDE used as a template
// argument (a define_static_array element / reflection NTTP) must mangle
// rather than hitting mangleUnqualifiedName's
// llvm_unreachable("Can't mangle a deduction guide name!")
// (a CXXDeductionGuideName has no <unqualified-name> encoding), and two
// different guides for the same template must mangle DISTINCTLY -- they share
// one DeclarationName, so a name-only encoding would fold their
// specializations at codegen (the same silent failure mode as overloaded
// function templates, different declaration-name kind). members_of over a namespace enumerates guides like
// any other member, so any namespace-walking reflection consumer that lifts
// the member list into static storage hits this. -fsyntax-only does NOT
// reproduce; the assertions below require codegen and a runtime observation.

#include <meta>

#include <cassert>

namespace demo {
template <class E> struct unexpected { unexpected(E); };
template <class E> unexpected(E) -> unexpected<E>; // guide #1
template <class E> unexpected(E*) -> unexpected<E*>; // guide #2 (same DeclarationName)
} // namespace demo

template <std::meta::info R> int probe() { return 1; }

int main() {
// The lift itself is the ICE shape: the array backing define_static_array
// has every element reflection mangled into its linkage name. Taking
// &probe<m> additionally pins each guide reflection as a function-template
// NTTP, whose mangled names must be pairwise distinct. The enumeration
// contains FOUR guides: the two explicit ones plus the two Sema-declared
// implicit ones (the per-constructor guide -- whose signature is
// structurally IDENTICAL to explicit guide #1 -- and the copy guide), so
// distinctness needs more than a structural hash of the declaration.
int (*addrs[8])() = {};
int n = 0;
template for (constexpr auto m : std::define_static_array(
std::meta::members_of(^^demo, std::meta::access_context::unchecked()))) {
if constexpr (std::meta::is_function_template(m)) { // only the guides
addrs[n++] = &probe<m>;
}
}
assert(n == 4);
for (int i = 0; i < n; ++i) {
assert(addrs[i]() == 1);
// Distinct manglings: without the per-guide discriminator the
// linkonce_odr specializations silently fold into one symbol.
for (int j = i + 1; j < n; ++j)
assert(addrs[i] != addrs[j]);
// A reflection of a guide stays distinct from a reflection of the
// deduced class template itself.
assert((void *)&probe<^^demo::unexpected> != (void *)addrs[i]);
}
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//===----------------------------------------------------------------------===//
//
// Copyright 2024 Bloomberg Finance L.P.
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03 || c++11 || c++14 || c++17 || c++20
// ADDITIONAL_COMPILE_FLAGS: -freflection-latest

// <experimental/reflection>
//
// [reflection]
//
// Regression test: a deduction-guide SPECIALIZATION reflection (Declaration
// kind, e.g. obtained via substitute on the guide template) used as a
// non-type template argument must mangle, not crash. The Template-kind guide
// encoding ("dg" + deduced template + ODR hash) already existed; the
// Declaration-kind path routed through mangleFunctionEncoding ->
// mangleUnqualifiedName and hit "Can't mangle a deduction guide name!".
// Distinct specializations must also mangle DISTINCTLY (no linker folding).

#include <experimental/meta>

namespace demo {
template <class T> struct Box { Box(T); };
Box(int) -> Box<int>;
Box(double) -> Box<double>;
} // namespace demo

template <std::meta::info R> struct Holder { static int x; };
template <std::meta::info R> int Holder<R>::x = 0;

consteval bool is_guide(std::meta::info m) {
return std::meta::is_function_template(m) &&
!std::meta::has_identifier(m) &&
!std::meta::is_operator_function_template(m) &&
!std::meta::is_conversion_function_template(m) &&
!std::meta::is_literal_operator_template(m) &&
!std::meta::is_constructor_template(m);
}

template <class A>
consteval std::meta::info guide_spec() {
for (auto m : std::meta::members_of(^^demo,
std::meta::access_context::unchecked()))
if (is_guide(m) && std::meta::can_substitute(m, {^^A}))
return std::meta::substitute(m, {^^A});
return ^^void;
}

int main() {
static_assert(guide_spec<int>() != ^^void);
static_assert(guide_spec<double>() != ^^void);
static_assert(guide_spec<int>() != guide_spec<double>());

// Each specialization's reflection must mangle (this is what crashed) and
// the two must NOT fold into one symbol at link time.
int *a = &Holder<guide_spec<int>()>::x;
int *b = &Holder<guide_spec<double>()>::x;
if (a == b)
return 1;
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//===----------------------------------------------------------------------===//
//
// Copyright 2024 Bloomberg Finance L.P.
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03 || c++11 || c++14 || c++17 || c++20
// ADDITIONAL_COMPILE_FLAGS: -freflection-latest

// <experimental/reflection>
//
// [reflection]
//
// Regression test: reflections of same-named member function templates of a
// CLASS TEMPLATE SPECIALIZATION used as non-type template arguments must
// mangle distinctly even when the siblings share an identical template head.
// The NTTP discriminator introduced for overloaded function templates hashed
// the template head plus the declaration via ODRHash::AddFunctionDecl -- but
// AddFunctionDecl silently NO-OPS for any declaration in "specialization
// context" (a member of a ClassTemplateSpecializationDecl, the common
// members_of shape), so an overload set like tl::expected<T,E>'s four value()
// member templates (const& / & / const&& / &&, identical heads) hashed
// identically: CodeGen silently folded the linkonce_odr specializations of a
// dispatcher taking the reflection as an NTTP, and one body served all four
// call sites. The AST-level specializations are correct, so only a RUNTIME
// observation catches this (same silent-fold mode as the overloaded
// function-template discriminator's original motivation).

#include <meta>

#include <cassert>
#include <string_view>

namespace meta = std::meta;
constexpr auto ctx = meta::access_context::unchecked();

template <class T> struct trait { static constexpr bool value = true; };

// The tl::expected<T,E>::value() field shape: four same-named member function
// templates with IDENTICAL template heads, differing only in cv/ref qualifiers
// and (correspondingly) return type.
template <class T> struct Exp {
template <class U = T, bool = trait<U>::value>
const U &value() const & { return v; }
template <class U = T, bool = trait<U>::value>
U &value() & { return v; }
template <class U = T, bool = trait<U>::value>
const U &&value() const && { return static_cast<const U &&>(v); }
template <class U = T, bool = trait<U>::value>
U &&value() && { return static_cast<U &&>(v); }
T v;
};

// Two same-headed siblings differing ONLY in ref-qualifier (the return types
// coincide): the ref-qualifier is not part of what VisitFunctionProtoType
// hashes, so it needs its own discrimination.
template <class T> struct RQ {
template <class U = T> int get() & { return 1; }
template <class U = T> int get() && { return 2; }
};

template <meta::info R> int probe() { return 1; }

int main() {
// All four value() siblings of a specialization must instantiate probe<m>
// DISTINCTLY (pre-fix: one mangled name, "definition with same mangled
// name" at best, a silent linkonce_odr fold at worst).
int (*addrs[8])() = {};
int n = 0;
template for (constexpr auto m : std::define_static_array(
meta::members_of(^^Exp<int>, ctx))) {
if constexpr (meta::is_function_template(m) && meta::has_identifier(m)) {
if constexpr (meta::identifier_of(m) == std::string_view("value")) {
addrs[n++] = &probe<m>;
}
}
}
assert(n == 4);
for (int i = 0; i < n; ++i)
for (int j = i + 1; j < n; ++j)
assert(addrs[i] != addrs[j]);

// Ref-qualifier-only siblings stay distinct too.
int (*raddrs[8])() = {};
int rn = 0;
template for (constexpr auto m : std::define_static_array(
meta::members_of(^^RQ<int>, ctx))) {
if constexpr (meta::is_function_template(m) && meta::has_identifier(m)) {
if constexpr (meta::identifier_of(m) == std::string_view("get")) {
raddrs[rn++] = &probe<m>;
}
}
}
assert(rn == 2);
assert(raddrs[0] != raddrs[1]);
return 0;
}
Loading