Skip to content

[Clang] Improve infrastructure for libstdc++ workarounds (Reland) #142592

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
Jun 3, 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
15 changes: 15 additions & 0 deletions clang/include/clang/Lex/Preprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ enum class EmbedResult {
Empty = 2, // Corresponds to __STDC_EMBED_EMPTY__
};

struct CXXStandardLibraryVersionInfo {
enum Library { Unknown, LibStdCXX };
Library Lib;
unsigned Version;
};

/// Engages in a tight little dance with the lexer to efficiently
/// preprocess tokens.
///
Expand Down Expand Up @@ -2706,6 +2712,15 @@ class Preprocessor {
return IsFileLexer(CurLexer.get(), CurPPLexer);
}

//===--------------------------------------------------------------------===//
// Standard Library Identification
std::optional<CXXStandardLibraryVersionInfo> CXXStandardLibraryVersion;

public:
std::optional<unsigned> getStdLibCxxVersion();
bool NeedsStdLibCxxWorkaroundBefore(unsigned FixedVersion);

private:
//===--------------------------------------------------------------------===//
// Caching stuff.
void CachingLex(Token &Result);
Expand Down
53 changes: 53 additions & 0 deletions clang/lib/Lex/PPExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -979,3 +979,56 @@ Preprocessor::EvaluateDirectiveExpression(IdentifierInfo *&IfNDefMacro,
return EvaluateDirectiveExpression(IfNDefMacro, Tok, EvaluatedDefined,
CheckForEoD);
}

static std::optional<CXXStandardLibraryVersionInfo>
getCXXStandardLibraryVersion(Preprocessor &PP, StringRef MacroName,
CXXStandardLibraryVersionInfo::Library Lib) {
MacroInfo *Macro = PP.getMacroInfo(PP.getIdentifierInfo(MacroName));
if (!Macro)
return std::nullopt;

if (Macro->getNumTokens() != 1 || !Macro->isObjectLike()) {
Macro->dump();
return std::nullopt;
}

const Token &RevisionDateTok = Macro->getReplacementToken(0);

bool Invalid = false;
llvm::SmallVector<char, 10> Buffer;
llvm::StringRef RevisionDate =
PP.getSpelling(RevisionDateTok, Buffer, &Invalid);
if (!Invalid) {
llvm::errs() << RevisionDate << "\n";
unsigned Value;
// We don't use NumericParser to avoid diagnostics
if (!RevisionDate.consumeInteger(10, Value)) {
llvm::errs() << "Value:" << Value << "\n";
return CXXStandardLibraryVersionInfo{Lib, Value};
}
}
return CXXStandardLibraryVersionInfo{CXXStandardLibraryVersionInfo::Unknown,
0};
}

std::optional<unsigned> Preprocessor::getStdLibCxxVersion() {
if (!CXXStandardLibraryVersion)
CXXStandardLibraryVersion = getCXXStandardLibraryVersion(
*this, "__GLIBCXX__", CXXStandardLibraryVersionInfo::LibStdCXX);
if (!CXXStandardLibraryVersion)
return std::nullopt;

if (CXXStandardLibraryVersion->Lib ==
CXXStandardLibraryVersionInfo::LibStdCXX)
return CXXStandardLibraryVersion->Version;
return std::nullopt;
}

bool Preprocessor::NeedsStdLibCxxWorkaroundBefore(unsigned FixedVersion) {
assert(FixedVersion >= 2000'00'00 && FixedVersion <= 2100'00'00 &&
"invalid value for __GLIBCXX__");
std::optional<unsigned> Ver = getStdLibCxxVersion();
if (!Ver)
return false;
return *Ver < FixedVersion;
}
1 change: 1 addition & 0 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13198,6 +13198,7 @@ NamedDecl *Sema::BuildUsingDeclaration(
if (getLangOpts().CPlusPlus14 && II && II->isStr("gets") &&
CurContext->isStdNamespace() &&
isa<TranslationUnitDecl>(LookupContext) &&
PP.NeedsStdLibCxxWorkaroundBefore(2016'12'21) &&
getSourceManager().isInSystemHeader(UsingLoc))
return nullptr;
UsingValidatorCCC CCC(HasTypenameKeyword, IsInstantiation, SS.getScopeRep(),
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Sema/SemaExceptionSpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/SemaInternal.h"
#include "llvm/ADT/SmallPtrSet.h"
#include <optional>
Expand All @@ -44,6 +45,8 @@ static const FunctionProtoType *GetUnderlyingFunction(QualType T)
bool Sema::isLibstdcxxEagerExceptionSpecHack(const Declarator &D) {
auto *RD = dyn_cast<CXXRecordDecl>(CurContext);

if (!getPreprocessor().NeedsStdLibCxxWorkaroundBefore(2016'04'27))
return false;
// All the problem cases are member functions named "swap" within class
// templates declared directly within namespace std or std::__debug or
// std::__profile.
Expand Down
7 changes: 5 additions & 2 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/Designator.h"
#include "clang/Sema/EnterExpressionEvaluationContext.h"
#include "clang/Sema/Initialization.h"
Expand Down Expand Up @@ -641,8 +642,10 @@ ExprResult InitListChecker::PerformEmptyInit(SourceLocation Loc,
// in that case. stlport does so too.
// Look for std::__debug for libstdc++, and for std:: for stlport.
// This is effectively a compiler-side implementation of LWG2193.
if (!InitSeq && EmptyInitList && InitSeq.getFailureKind() ==
InitializationSequence::FK_ExplicitConstructor) {
if (!InitSeq && EmptyInitList &&
InitSeq.getFailureKind() ==
InitializationSequence::FK_ExplicitConstructor &&
SemaRef.getPreprocessor().NeedsStdLibCxxWorkaroundBefore(2014'04'22)) {
OverloadCandidateSet::iterator Best;
OverloadingResult O =
InitSeq.getFailedCandidateSet()
Expand Down
25 changes: 3 additions & 22 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4444,35 +4444,16 @@ static bool IsLibstdcxxStdFormatKind(Preprocessor &PP, VarDecl *Var) {
!Var->getDeclContext()->isStdNamespace())
return false;

MacroInfo *MacroGLIBCXX =
PP.getMacroInfo(PP.getIdentifierInfo("__GLIBCXX__"));

if (!MacroGLIBCXX || MacroGLIBCXX->getNumTokens() != 1)
return false;

const Token &RevisionDateTok = MacroGLIBCXX->getReplacementToken(0);
bool Invalid = false;
std::string RevisionDate = PP.getSpelling(RevisionDateTok, &Invalid);

// Checking old versions of libstdc++ is not needed because 15.1 is the first
// release in which users can access std::format_kind.
// We can use 20250520 as the final date, see the following commits.
// GCC releases/gcc-15 branch:
// https://gcc.gnu.org/g:fedf81ef7b98e5c9ac899b8641bb670746c51205
// https://gcc.gnu.org/g:53680c1aa92d9f78e8255fbf696c0ed36f160650
// GCC master branch:
// https://gcc.gnu.org/g:9361966d80f625c5accc25cbb439f0278dd8b278
// https://gcc.gnu.org/g:c65725eccbabf3b9b5965f27fff2d3b9f6c75930
StringRef FixDate = "20250520";

if (Invalid)
return false;

// The format of the revision date is in compressed ISO date format.
// See https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_macros.html
// So we can use string comparison.
//
// Checking old versions of libstdc++ is not needed because 15.1 is the first
// release in which users can access std::format_kind.
return RevisionDate.size() == 8 && RevisionDate < FixDate;
return PP.NeedsStdLibCxxWorkaroundBefore(2025'05'20);
}
} // end anonymous namespace

Expand Down
24 changes: 13 additions & 11 deletions clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1449,17 +1449,19 @@ Decl *TemplateDeclInstantiator::InstantiateTypedefNameDecl(TypedefNameDecl *D,
// happen to be processing that implementation, fake up the g++ ?:
// semantics. See LWG issue 2141 for more information on the bug. The bugs
// are fixed in g++ and libstdc++ 4.9.0 (2014-04-22).
const DecltypeType *DT = DI->getType()->getAs<DecltypeType>();
CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(D->getDeclContext());
if (DT && RD && isa<ConditionalOperator>(DT->getUnderlyingExpr()) &&
DT->isReferenceType() &&
RD->getEnclosingNamespaceContext() == SemaRef.getStdNamespace() &&
RD->getIdentifier() && RD->getIdentifier()->isStr("common_type") &&
D->getIdentifier() && D->getIdentifier()->isStr("type") &&
SemaRef.getSourceManager().isInSystemHeader(D->getBeginLoc()))
// Fold it to the (non-reference) type which g++ would have produced.
DI = SemaRef.Context.getTrivialTypeSourceInfo(
DI->getType().getNonReferenceType());
if (SemaRef.getPreprocessor().NeedsStdLibCxxWorkaroundBefore(2014'04'22)) {
const DecltypeType *DT = DI->getType()->getAs<DecltypeType>();
CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(D->getDeclContext());
if (DT && RD && isa<ConditionalOperator>(DT->getUnderlyingExpr()) &&
DT->isReferenceType() &&
RD->getEnclosingNamespaceContext() == SemaRef.getStdNamespace() &&
RD->getIdentifier() && RD->getIdentifier()->isStr("common_type") &&
D->getIdentifier() && D->getIdentifier()->isStr("type") &&
SemaRef.getSourceManager().isInSystemHeader(D->getBeginLoc()))
// Fold it to the (non-reference) type which g++ would have produced.
DI = SemaRef.Context.getTrivialTypeSourceInfo(
DI->getType().getNonReferenceType());
}

// Create the new typedef
TypedefNameDecl *Typedef;
Expand Down
2 changes: 1 addition & 1 deletion clang/test/SemaCXX/libstdcxx_common_type_hack.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -D__GLIBCXX__=20100000L

// This is a test for an egregious hack in Clang that works around
// an issue with GCC's <type_traits> implementation. std::common_type
Expand Down
2 changes: 1 addition & 1 deletion clang/test/SemaCXX/libstdcxx_explicit_init_list_hack.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify -Wsystem-headers %s
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify -Wsystem-headers -D__GLIBCXX__=20100000L %s

// libstdc++4.6 in debug mode has explicit default constructors.
// stlport has this for all containers.
Expand Down
18 changes: 9 additions & 9 deletions clang/test/SemaCXX/libstdcxx_pair_swap_hack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@
// The same problem afflicts a bunch of other class templates. Those
// affected are array, pair, priority_queue, stack, and queue.

// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -DCLASS=array
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -DCLASS=array -DPR28423
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -DCLASS=pair
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -DCLASS=priority_queue
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -DCLASS=stack
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -DCLASS=queue
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -Wextra -D__GLIBCXX__=20100000L -DCLASS=array
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -Wextra -D__GLIBCXX__=20100000L -DCLASS=array -DPR28423
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -Wextra -D__GLIBCXX__=20100000L -DCLASS=pair
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -Wextra -D__GLIBCXX__=20100000L -DCLASS=priority_queue
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -Wextra -D__GLIBCXX__=20100000L -DCLASS=stack
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -Wextra -D__GLIBCXX__=20100000L -DCLASS=queue
//
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -DCLASS=array -DNAMESPACE=__debug
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -DCLASS=array -DNAMESPACE=__profile
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -Wextra -D__GLIBCXX__=20100000L -DCLASS=array -DNAMESPACE=__debug
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -Wextra -D__GLIBCXX__=20100000L -DCLASS=array -DNAMESPACE=__profile

// MSVC's standard library uses a very similar pattern that relies on delayed
// parsing of exception specifications.
//
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -DCLASS=array -DMSVC
// RUN: %clang_cc1 -fsyntax-only %s -std=c++11 -verify -fexceptions -fcxx-exceptions -D__GLIBCXX__=20100000L -DCLASS=array -DMSVC

#ifdef BE_THE_HEADER

Expand Down
Loading