Skip to content

Enable -Wunique-object-duplication inside templated code #125902

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 14, 2025
Merged
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
1 change: 1 addition & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -3669,6 +3669,7 @@ class Sema final : public SemaBase {
/// cause problems if the variable is mutable, its initialization is
/// effectful, or its address is taken.
bool GloballyUniqueObjectMightBeAccidentallyDuplicated(const VarDecl *Dcl);
void DiagnoseUniqueObjectDuplication(const VarDecl *Dcl);

/// AddInitializerToDecl - Adds the initializer Init to the
/// declaration dcl. If DirectInit is true, this is C++ direct
Expand Down
110 changes: 60 additions & 50 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13399,8 +13399,11 @@ bool Sema::GloballyUniqueObjectMightBeAccidentallyDuplicated(
// about the properties of the function containing it.
const ValueDecl *Target = Dcl;
// VarDecls and FunctionDecls have different functions for checking
// inline-ness, so we have to do it manually.
// inline-ness, and whether they were originally templated, so we have to
// call the appropriate functions manually.
bool TargetIsInline = Dcl->isInline();
bool TargetWasTemplated =
Dcl->getTemplateSpecializationKind() != TSK_Undeclared;

// Update the Target and TargetIsInline property if necessary
if (Dcl->isStaticLocal()) {
Expand All @@ -13416,12 +13419,13 @@ bool Sema::GloballyUniqueObjectMightBeAccidentallyDuplicated(
Target = FunDcl;
// IsInlined() checks for the C++ inline property
TargetIsInline = FunDcl->isInlined();
TargetWasTemplated =
FunDcl->getTemplateSpecializationKind() != TSK_Undeclared;
}

// Non-inline variables can only legally appear in one TU
// FIXME: This also applies to templated variables, but that can rarely lead
// to false positives so templates are disabled for now.
if (!TargetIsInline)
// Non-inline functions/variables can only legally appear in one TU,
// unless they were part of a template.
if (!TargetIsInline && !TargetWasTemplated)
return false;

// If the object isn't hidden, the dynamic linker will prevent duplication.
Expand All @@ -13436,6 +13440,55 @@ bool Sema::GloballyUniqueObjectMightBeAccidentallyDuplicated(
return true;
}

void Sema::DiagnoseUniqueObjectDuplication(const VarDecl *VD) {
// If this object has external linkage and hidden visibility, it might be
// duplicated when built into a shared library, which causes problems if it's
// mutable (since the copies won't be in sync) or its initialization has side
// effects (since it will run once per copy instead of once globally).
// FIXME: Windows uses dllexport/dllimport instead of visibility, and we don't
// handle that yet. Disable the warning on Windows for now.

// Don't diagnose if we're inside a template;
// we'll diagnose during instantiation instead.
if (!Context.getTargetInfo().shouldDLLImportComdatSymbols() &&
!VD->isTemplated() &&
GloballyUniqueObjectMightBeAccidentallyDuplicated(VD)) {

// Check mutability. For pointers, ensure that both the pointer and the
// pointee are (recursively) const.
QualType Type = VD->getType().getNonReferenceType();
if (!Type.isConstant(VD->getASTContext())) {
Diag(VD->getLocation(), diag::warn_possible_object_duplication_mutable)
<< VD;
} else {
while (Type->isPointerType()) {
Type = Type->getPointeeType();
if (Type->isFunctionType())
break;
if (!Type.isConstant(VD->getASTContext())) {
Diag(VD->getLocation(),
diag::warn_possible_object_duplication_mutable)
<< VD;
break;
}
}
}

// To keep false positives low, only warn if we're certain that the
// initializer has side effects. Don't warn on operator new, since a mutable
// pointer will trigger the previous warning, and an immutable pointer
// getting duplicated just results in a little extra memory usage.
const Expr *Init = VD->getAnyInitializer();
if (Init &&
Init->HasSideEffects(VD->getASTContext(),
/*IncludePossibleEffects=*/false) &&
!isa<CXXNewExpr>(Init->IgnoreParenImpCasts())) {
Diag(Init->getExprLoc(), diag::warn_possible_object_duplication_init)
<< VD;
}
}
}

void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) {
// If there is no declaration, there was an error parsing it. Just ignore
// the initializer.
Expand Down Expand Up @@ -14655,6 +14708,8 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
return;
}

DiagnoseUniqueObjectDuplication(var);

// Require the destructor.
if (!type->isDependentType())
if (const RecordType *recordType = baseType->getAs<RecordType>())
Expand Down Expand Up @@ -14842,51 +14897,6 @@ void Sema::FinalizeDeclaration(Decl *ThisDecl) {
if (DC->getRedeclContext()->isFileContext() && VD->isExternallyVisible())
AddPushedVisibilityAttribute(VD);

// If this object has external linkage and hidden visibility, it might be
// duplicated when built into a shared library, which causes problems if it's
// mutable (since the copies won't be in sync) or its initialization has side
// effects (since it will run once per copy instead of once globally)
// FIXME: Windows uses dllexport/dllimport instead of visibility, and we don't
// handle that yet. Disable the warning on Windows for now.
// FIXME: Checking templates can cause false positives if the template in
// question is never instantiated (e.g. only specialized templates are used).
if (!Context.getTargetInfo().shouldDLLImportComdatSymbols() &&
!VD->isTemplated() &&
GloballyUniqueObjectMightBeAccidentallyDuplicated(VD)) {
// Check mutability. For pointers, ensure that both the pointer and the
// pointee are (recursively) const.
QualType Type = VD->getType().getNonReferenceType();
if (!Type.isConstant(VD->getASTContext())) {
Diag(VD->getLocation(), diag::warn_possible_object_duplication_mutable)
<< VD;
} else {
while (Type->isPointerType()) {
Type = Type->getPointeeType();
if (Type->isFunctionType())
break;
if (!Type.isConstant(VD->getASTContext())) {
Diag(VD->getLocation(),
diag::warn_possible_object_duplication_mutable)
<< VD;
break;
}
}
}

// To keep false positives low, only warn if we're certain that the
// initializer has side effects. Don't warn on operator new, since a mutable
// pointer will trigger the previous warning, and an immutable pointer
// getting duplicated just results in a little extra memory usage.
const Expr *Init = VD->getAnyInitializer();
if (Init &&
Init->HasSideEffects(VD->getASTContext(),
/*IncludePossibleEffects=*/false) &&
!isa<CXXNewExpr>(Init->IgnoreParenImpCasts())) {
Diag(Init->getExprLoc(), diag::warn_possible_object_duplication_init)
<< VD;
}
}

// FIXME: Warn on unused var template partial specializations.
if (VD->isFileVarDecl() && !isa<VarTemplatePartialSpecializationDecl>(VD))
MarkUnusedFileScopedDecl(VD);
Expand Down
87 changes: 86 additions & 1 deletion clang/test/SemaCXX/unique_object_duplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,89 @@ namespace GlobalTest {
};

inline float Test::disallowedStaticMember2 = 2.3; // hidden-warning {{'disallowedStaticMember2' may be duplicated when built into a shared library: it is mutable, has hidden visibility, and external linkage}}
} // namespace GlobalTest
} // namespace GlobalTest

/******************************************************************************
* Case three: Inside templates
******************************************************************************/

namespace TemplateTest {

template <typename T>
int disallowedTemplate1 = 0; // hidden-warning {{'disallowedTemplate1<int>' may be duplicated when built into a shared library: it is mutable, has hidden visibility, and external linkage}}

template int disallowedTemplate1<int>; // hidden-note {{in instantiation of}}


// Should work for implicit instantiation as well
template <typename T>
int disallowedTemplate2 = 0; // hidden-warning {{'disallowedTemplate2<int>' may be duplicated when built into a shared library: it is mutable, has hidden visibility, and external linkage}}

int implicit_instantiate() {
return disallowedTemplate2<int>; // hidden-note {{in instantiation of}}
}


// Ensure we only get warnings for templates that are actually instantiated
template <typename T>
int maybeAllowedTemplate = 0; // Not instantiated, so no warning here

template <typename T>
int maybeAllowedTemplate<T*> = 1; // hidden-warning {{'maybeAllowedTemplate<int *>' may be duplicated when built into a shared library: it is mutable, has hidden visibility, and external linkage}}

template <>
int maybeAllowedTemplate<bool> = 2; // hidden-warning {{'maybeAllowedTemplate<bool>' may be duplicated when built into a shared library: it is mutable, has hidden visibility, and external linkage}}

template int maybeAllowedTemplate<int*>; // hidden-note {{in instantiation of}}



// Should work the same for static class members
template <typename T>
struct S {
static int staticMember;
};

template <typename T>
int S<T>::staticMember = 0; // Never instantiated

// T* specialization
template <typename T>
struct S<T*> {
static int staticMember;
};

template <typename T>
int S<T*>::staticMember = 1; // hidden-warning {{'staticMember' may be duplicated when built into a shared library: it is mutable, has hidden visibility, and external linkage}}

template class S<int*>; // hidden-note {{in instantiation of}}

// T& specialization, implicitly instantiated
template <typename T>
struct S<T&> {
static int staticMember;
};

template <typename T>
int S<T&>::staticMember = 2; // hidden-warning {{'staticMember' may be duplicated when built into a shared library: it is mutable, has hidden visibility, and external linkage}}

int implicit_instantiate2() {
return S<bool&>::staticMember; // hidden-note {{in instantiation of}}
}


// Should work for static locals as well
template <typename T>
int* wrapper() {
static int staticLocal; // hidden-warning {{'staticLocal' may be duplicated when built into a shared library: it is mutable, has hidden visibility, and external linkage}}
return &staticLocal;
}

template <>
int* wrapper<int*>() {
static int staticLocal; // hidden-warning {{'staticLocal' may be duplicated when built into a shared library: it is mutable, has hidden visibility, and external linkage}}
return &staticLocal;
}

auto dummy = wrapper<bool>(); // hidden-note {{in instantiation of}}
} // namespace TemplateTest