Skip to content

Commit 004a5fe

Browse files
authored
Enable -Wunique-object-duplication inside templated code (#125902)
Followup to #125526. This allows the unique object duplication warning to fire on code inside of templates. Previously, it was disabled there to prevent false positives if the template was never instantiated. The patch has two parts: first, we move the check from `FinalizeDeclaration` (which is only called during parsing) to `CheckCompleteVariableDeclaration` (which is also called during template instantiation). Since the code we're moving is fairly bulky, we abstract it into a separate function for convenience. Second, we disable the warning during template parsing, and add a check later to see if the variable we're acting on on originated from a template. If so, it has the potential to be duplicated just like an inline variable. ## Testing Unit tests for template have been added to the existing test suite. To evaluate the patch on real code, I ran it on chromium and on clang itself. As expected, in both cases we got strictly more warnings than before. I manually looked through each new warning, and they all seemed legitimate. In chromium, we found [79 new warnings across 55 files](https://github.com/user-attachments/files/18676635/new_warnings_chromium.txt), mostly in third-party code (for a total of 234 warnings across 137 files). In clang, we found [8 new warnings across 6 files](https://github.com/user-attachments/files/18676658/new_warnings_clang.txt), for a total of 17 warnings across 11 files.
1 parent afa3c10 commit 004a5fe

File tree

3 files changed

+147
-51
lines changed

3 files changed

+147
-51
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3669,6 +3669,7 @@ class Sema final : public SemaBase {
36693669
/// cause problems if the variable is mutable, its initialization is
36703670
/// effectful, or its address is taken.
36713671
bool GloballyUniqueObjectMightBeAccidentallyDuplicated(const VarDecl *Dcl);
3672+
void DiagnoseUniqueObjectDuplication(const VarDecl *Dcl);
36723673

36733674
/// AddInitializerToDecl - Adds the initializer Init to the
36743675
/// declaration dcl. If DirectInit is true, this is C++ direct

clang/lib/Sema/SemaDecl.cpp

Lines changed: 60 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13399,8 +13399,11 @@ bool Sema::GloballyUniqueObjectMightBeAccidentallyDuplicated(
1339913399
// about the properties of the function containing it.
1340013400
const ValueDecl *Target = Dcl;
1340113401
// VarDecls and FunctionDecls have different functions for checking
13402-
// inline-ness, so we have to do it manually.
13402+
// inline-ness, and whether they were originally templated, so we have to
13403+
// call the appropriate functions manually.
1340313404
bool TargetIsInline = Dcl->isInline();
13405+
bool TargetWasTemplated =
13406+
Dcl->getTemplateSpecializationKind() != TSK_Undeclared;
1340413407

1340513408
// Update the Target and TargetIsInline property if necessary
1340613409
if (Dcl->isStaticLocal()) {
@@ -13416,12 +13419,13 @@ bool Sema::GloballyUniqueObjectMightBeAccidentallyDuplicated(
1341613419
Target = FunDcl;
1341713420
// IsInlined() checks for the C++ inline property
1341813421
TargetIsInline = FunDcl->isInlined();
13422+
TargetWasTemplated =
13423+
FunDcl->getTemplateSpecializationKind() != TSK_Undeclared;
1341913424
}
1342013425

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

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

13443+
void Sema::DiagnoseUniqueObjectDuplication(const VarDecl *VD) {
13444+
// If this object has external linkage and hidden visibility, it might be
13445+
// duplicated when built into a shared library, which causes problems if it's
13446+
// mutable (since the copies won't be in sync) or its initialization has side
13447+
// effects (since it will run once per copy instead of once globally).
13448+
// FIXME: Windows uses dllexport/dllimport instead of visibility, and we don't
13449+
// handle that yet. Disable the warning on Windows for now.
13450+
13451+
// Don't diagnose if we're inside a template;
13452+
// we'll diagnose during instantiation instead.
13453+
if (!Context.getTargetInfo().shouldDLLImportComdatSymbols() &&
13454+
!VD->isTemplated() &&
13455+
GloballyUniqueObjectMightBeAccidentallyDuplicated(VD)) {
13456+
13457+
// Check mutability. For pointers, ensure that both the pointer and the
13458+
// pointee are (recursively) const.
13459+
QualType Type = VD->getType().getNonReferenceType();
13460+
if (!Type.isConstant(VD->getASTContext())) {
13461+
Diag(VD->getLocation(), diag::warn_possible_object_duplication_mutable)
13462+
<< VD;
13463+
} else {
13464+
while (Type->isPointerType()) {
13465+
Type = Type->getPointeeType();
13466+
if (Type->isFunctionType())
13467+
break;
13468+
if (!Type.isConstant(VD->getASTContext())) {
13469+
Diag(VD->getLocation(),
13470+
diag::warn_possible_object_duplication_mutable)
13471+
<< VD;
13472+
break;
13473+
}
13474+
}
13475+
}
13476+
13477+
// To keep false positives low, only warn if we're certain that the
13478+
// initializer has side effects. Don't warn on operator new, since a mutable
13479+
// pointer will trigger the previous warning, and an immutable pointer
13480+
// getting duplicated just results in a little extra memory usage.
13481+
const Expr *Init = VD->getAnyInitializer();
13482+
if (Init &&
13483+
Init->HasSideEffects(VD->getASTContext(),
13484+
/*IncludePossibleEffects=*/false) &&
13485+
!isa<CXXNewExpr>(Init->IgnoreParenImpCasts())) {
13486+
Diag(Init->getExprLoc(), diag::warn_possible_object_duplication_init)
13487+
<< VD;
13488+
}
13489+
}
13490+
}
13491+
1343913492
void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) {
1344013493
// If there is no declaration, there was an error parsing it. Just ignore
1344113494
// the initializer.
@@ -14655,6 +14708,8 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
1465514708
return;
1465614709
}
1465714710

14711+
DiagnoseUniqueObjectDuplication(var);
14712+
1465814713
// Require the destructor.
1465914714
if (!type->isDependentType())
1466014715
if (const RecordType *recordType = baseType->getAs<RecordType>())
@@ -14842,51 +14897,6 @@ void Sema::FinalizeDeclaration(Decl *ThisDecl) {
1484214897
if (DC->getRedeclContext()->isFileContext() && VD->isExternallyVisible())
1484314898
AddPushedVisibilityAttribute(VD);
1484414899

14845-
// If this object has external linkage and hidden visibility, it might be
14846-
// duplicated when built into a shared library, which causes problems if it's
14847-
// mutable (since the copies won't be in sync) or its initialization has side
14848-
// effects (since it will run once per copy instead of once globally)
14849-
// FIXME: Windows uses dllexport/dllimport instead of visibility, and we don't
14850-
// handle that yet. Disable the warning on Windows for now.
14851-
// FIXME: Checking templates can cause false positives if the template in
14852-
// question is never instantiated (e.g. only specialized templates are used).
14853-
if (!Context.getTargetInfo().shouldDLLImportComdatSymbols() &&
14854-
!VD->isTemplated() &&
14855-
GloballyUniqueObjectMightBeAccidentallyDuplicated(VD)) {
14856-
// Check mutability. For pointers, ensure that both the pointer and the
14857-
// pointee are (recursively) const.
14858-
QualType Type = VD->getType().getNonReferenceType();
14859-
if (!Type.isConstant(VD->getASTContext())) {
14860-
Diag(VD->getLocation(), diag::warn_possible_object_duplication_mutable)
14861-
<< VD;
14862-
} else {
14863-
while (Type->isPointerType()) {
14864-
Type = Type->getPointeeType();
14865-
if (Type->isFunctionType())
14866-
break;
14867-
if (!Type.isConstant(VD->getASTContext())) {
14868-
Diag(VD->getLocation(),
14869-
diag::warn_possible_object_duplication_mutable)
14870-
<< VD;
14871-
break;
14872-
}
14873-
}
14874-
}
14875-
14876-
// To keep false positives low, only warn if we're certain that the
14877-
// initializer has side effects. Don't warn on operator new, since a mutable
14878-
// pointer will trigger the previous warning, and an immutable pointer
14879-
// getting duplicated just results in a little extra memory usage.
14880-
const Expr *Init = VD->getAnyInitializer();
14881-
if (Init &&
14882-
Init->HasSideEffects(VD->getASTContext(),
14883-
/*IncludePossibleEffects=*/false) &&
14884-
!isa<CXXNewExpr>(Init->IgnoreParenImpCasts())) {
14885-
Diag(Init->getExprLoc(), diag::warn_possible_object_duplication_init)
14886-
<< VD;
14887-
}
14888-
}
14889-
1489014900
// FIXME: Warn on unused var template partial specializations.
1489114901
if (VD->isFileVarDecl() && !isa<VarTemplatePartialSpecializationDecl>(VD))
1489214902
MarkUnusedFileScopedDecl(VD);

clang/test/SemaCXX/unique_object_duplication.h

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,89 @@ namespace GlobalTest {
154154
};
155155

156156
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}}
157-
} // namespace GlobalTest
157+
} // namespace GlobalTest
158+
159+
/******************************************************************************
160+
* Case three: Inside templates
161+
******************************************************************************/
162+
163+
namespace TemplateTest {
164+
165+
template <typename T>
166+
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}}
167+
168+
template int disallowedTemplate1<int>; // hidden-note {{in instantiation of}}
169+
170+
171+
// Should work for implicit instantiation as well
172+
template <typename T>
173+
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}}
174+
175+
int implicit_instantiate() {
176+
return disallowedTemplate2<int>; // hidden-note {{in instantiation of}}
177+
}
178+
179+
180+
// Ensure we only get warnings for templates that are actually instantiated
181+
template <typename T>
182+
int maybeAllowedTemplate = 0; // Not instantiated, so no warning here
183+
184+
template <typename T>
185+
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}}
186+
187+
template <>
188+
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}}
189+
190+
template int maybeAllowedTemplate<int*>; // hidden-note {{in instantiation of}}
191+
192+
193+
194+
// Should work the same for static class members
195+
template <typename T>
196+
struct S {
197+
static int staticMember;
198+
};
199+
200+
template <typename T>
201+
int S<T>::staticMember = 0; // Never instantiated
202+
203+
// T* specialization
204+
template <typename T>
205+
struct S<T*> {
206+
static int staticMember;
207+
};
208+
209+
template <typename T>
210+
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}}
211+
212+
template class S<int*>; // hidden-note {{in instantiation of}}
213+
214+
// T& specialization, implicitly instantiated
215+
template <typename T>
216+
struct S<T&> {
217+
static int staticMember;
218+
};
219+
220+
template <typename T>
221+
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}}
222+
223+
int implicit_instantiate2() {
224+
return S<bool&>::staticMember; // hidden-note {{in instantiation of}}
225+
}
226+
227+
228+
// Should work for static locals as well
229+
template <typename T>
230+
int* wrapper() {
231+
static int staticLocal; // hidden-warning {{'staticLocal' may be duplicated when built into a shared library: it is mutable, has hidden visibility, and external linkage}}
232+
return &staticLocal;
233+
}
234+
235+
template <>
236+
int* wrapper<int*>() {
237+
static int staticLocal; // hidden-warning {{'staticLocal' may be duplicated when built into a shared library: it is mutable, has hidden visibility, and external linkage}}
238+
return &staticLocal;
239+
}
240+
241+
auto dummy = wrapper<bool>(); // hidden-note {{in instantiation of}}
242+
} // namespace TemplateTest

0 commit comments

Comments
 (0)