Skip to content

[Clang][Parse] Fix ambiguity with nested-name-specifiers that may declarative #96364

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 8 commits into from
Jul 29, 2024
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/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ Bug Fixes to C++ Support
- Fixed a crash when an expression with a dependent ``__typeof__`` type is used as the operand of a unary operator. (#GH97646)
- Fixed a failed assertion when checking invalid delete operator declaration. (#GH96191)
- Fix a crash when checking destructor reference with an invalid initializer. (#GH97230)
- Clang now correctly parses potentially declarative nested-name-specifiers in pointer-to-member declarators.

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
26 changes: 23 additions & 3 deletions clang/include/clang/Lex/Preprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,11 @@ class Preprocessor {
/// invoked (at which point the last position is popped).
std::vector<CachedTokensTy::size_type> BacktrackPositions;

/// Stack of cached tokens/initial number of cached tokens pairs, allowing
/// nested unannotated backtracks.
std::vector<std::pair<CachedTokensTy, CachedTokensTy::size_type>>
UnannotatedBacktrackTokens;

/// True if \p Preprocessor::SkipExcludedConditionalBlock() is running.
/// This is used to guard against calling this function recursively.
///
Expand Down Expand Up @@ -1722,8 +1727,16 @@ class Preprocessor {
/// at some point after EnableBacktrackAtThisPos. If you don't, caching of
/// tokens will continue indefinitely.
///
void EnableBacktrackAtThisPos();
/// \param Unannotated Whether token annotations are reverted upon calling
/// Backtrack().
void EnableBacktrackAtThisPos(bool Unannotated = false);

private:
std::pair<CachedTokensTy::size_type, bool> LastBacktrackPos();

CachedTokensTy PopUnannotatedBacktrackTokens();

public:
/// Disable the last EnableBacktrackAtThisPos call.
void CommitBacktrackedTokens();

Expand All @@ -1735,6 +1748,12 @@ class Preprocessor {
/// caching of tokens is on.
bool isBacktrackEnabled() const { return !BacktrackPositions.empty(); }

/// True if EnableBacktrackAtThisPos() was called and
/// caching of unannotated tokens is on.
bool isUnannotatedBacktrackEnabled() const {
return !UnannotatedBacktrackTokens.empty();
}

/// Lex the next token for this preprocessor.
void Lex(Token &Result);

Expand Down Expand Up @@ -1841,8 +1860,9 @@ class Preprocessor {
void RevertCachedTokens(unsigned N) {
assert(isBacktrackEnabled() &&
"Should only be called when tokens are cached for backtracking");
assert(signed(CachedLexPos) - signed(N) >= signed(BacktrackPositions.back())
&& "Should revert tokens up to the last backtrack position, not more");
assert(signed(CachedLexPos) - signed(N) >=
signed(LastBacktrackPos().first) &&
"Should revert tokens up to the last backtrack position, not more");
assert(signed(CachedLexPos) - signed(N) >= 0 &&
"Corrupted backtrack positions ?");
CachedLexPos -= N;
Expand Down
15 changes: 8 additions & 7 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,8 @@ class Parser : public CodeCompletionHandler {
/// ....
/// TPA.Revert();
///
/// If the Unannotated parameter is true, any token annotations created
/// during the tentative parse are reverted.
class TentativeParsingAction {
Parser &P;
PreferredTypeBuilder PrevPreferredType;
Expand All @@ -1034,15 +1036,15 @@ class Parser : public CodeCompletionHandler {
bool isActive;

public:
explicit TentativeParsingAction(Parser &p)
explicit TentativeParsingAction(Parser &p, bool Unannotated = false)
: P(p), PrevPreferredType(P.PreferredType) {
PrevTok = P.Tok;
PrevTentativelyDeclaredIdentifierCount =
P.TentativelyDeclaredIdentifiers.size();
PrevParenCount = P.ParenCount;
PrevBracketCount = P.BracketCount;
PrevBraceCount = P.BraceCount;
P.PP.EnableBacktrackAtThisPos();
P.PP.EnableBacktrackAtThisPos(Unannotated);
isActive = true;
}
void Commit() {
Expand Down Expand Up @@ -1073,13 +1075,11 @@ class Parser : public CodeCompletionHandler {
class RevertingTentativeParsingAction
: private Parser::TentativeParsingAction {
public:
RevertingTentativeParsingAction(Parser &P)
: Parser::TentativeParsingAction(P) {}
using TentativeParsingAction::TentativeParsingAction;

~RevertingTentativeParsingAction() { Revert(); }
};

class UnannotatedTentativeParsingAction;

/// ObjCDeclContextSwitch - An object used to switch context from
/// an objective-c decl context to its enclosing decl context and
/// back.
Expand Down Expand Up @@ -1984,7 +1984,8 @@ class Parser : public CodeCompletionHandler {
CXXScopeSpec &SS, ParsedType ObjectType, bool ObjectHasErrors,
bool EnteringContext, bool *MayBePseudoDestructor = nullptr,
bool IsTypename = false, const IdentifierInfo **LastII = nullptr,
bool OnlyNamespace = false, bool InUsingDeclaration = false);
bool OnlyNamespace = false, bool InUsingDeclaration = false,
bool Disambiguation = false);

//===--------------------------------------------------------------------===//
// C++11 5.1.2: Lambda expressions
Expand Down
48 changes: 40 additions & 8 deletions clang/lib/Lex/PPCaching.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@
#include "clang/Lex/Preprocessor.h"
using namespace clang;

std::pair<Preprocessor::CachedTokensTy::size_type, bool>
Preprocessor::LastBacktrackPos() {
assert(isBacktrackEnabled());
auto BacktrackPos = BacktrackPositions.back();
bool Unannotated =
static_cast<CachedTokensTy::difference_type>(BacktrackPos) < 0;
return {Unannotated ? ~BacktrackPos : BacktrackPos, Unannotated};
}

// EnableBacktrackAtThisPos - From the point that this method is called, and
// until CommitBacktrackedTokens() or Backtrack() is called, the Preprocessor
// keeps track of the lexed tokens so that a subsequent Backtrack() call will
Expand All @@ -22,26 +31,45 @@ using namespace clang;
// Nested backtracks are allowed, meaning that EnableBacktrackAtThisPos can
// be called multiple times and CommitBacktrackedTokens/Backtrack calls will
// be combined with the EnableBacktrackAtThisPos calls in reverse order.
void Preprocessor::EnableBacktrackAtThisPos() {
void Preprocessor::EnableBacktrackAtThisPos(bool Unannotated) {
assert(LexLevel == 0 && "cannot use lookahead while lexing");
BacktrackPositions.push_back(CachedLexPos);
BacktrackPositions.push_back(Unannotated ? ~CachedLexPos : CachedLexPos);
if (Unannotated)
UnannotatedBacktrackTokens.emplace_back(CachedTokens, CachedTokens.size());
EnterCachingLexMode();
}

Preprocessor::CachedTokensTy Preprocessor::PopUnannotatedBacktrackTokens() {
assert(isUnannotatedBacktrackEnabled() && "missing unannotated tokens?");
auto [UnannotatedTokens, NumCachedToks] =
std::move(UnannotatedBacktrackTokens.back());
UnannotatedBacktrackTokens.pop_back();
// If another unannotated backtrack is active, propagate any tokens that were
// lexed (not cached) since EnableBacktrackAtThisPos was last called.
if (isUnannotatedBacktrackEnabled())
UnannotatedBacktrackTokens.back().first.append(
UnannotatedTokens.begin() + NumCachedToks, UnannotatedTokens.end());
return std::move(UnannotatedTokens);
}

// Disable the last EnableBacktrackAtThisPos call.
void Preprocessor::CommitBacktrackedTokens() {
assert(!BacktrackPositions.empty()
&& "EnableBacktrackAtThisPos was not called!");
assert(isBacktrackEnabled() && "EnableBacktrackAtThisPos was not called!");
auto [BacktrackPos, Unannotated] = LastBacktrackPos();
BacktrackPositions.pop_back();
if (Unannotated)
PopUnannotatedBacktrackTokens();
}

// Make Preprocessor re-lex the tokens that were lexed since
// EnableBacktrackAtThisPos() was previously called.
void Preprocessor::Backtrack() {
assert(!BacktrackPositions.empty()
&& "EnableBacktrackAtThisPos was not called!");
CachedLexPos = BacktrackPositions.back();
assert(isBacktrackEnabled() && "EnableBacktrackAtThisPos was not called!");
auto [BacktrackPos, Unannotated] = LastBacktrackPos();
BacktrackPositions.pop_back();
CachedLexPos = BacktrackPos;
if (Unannotated)
CachedTokens = PopUnannotatedBacktrackTokens();
recomputeCurLexerKind();
}

Expand All @@ -67,6 +95,8 @@ void Preprocessor::CachingLex(Token &Result) {
EnterCachingLexModeUnchecked();
CachedTokens.push_back(Result);
++CachedLexPos;
if (isUnannotatedBacktrackEnabled())
UnannotatedBacktrackTokens.back().first.push_back(Result);
return;
}

Expand Down Expand Up @@ -108,6 +138,8 @@ const Token &Preprocessor::PeekAhead(unsigned N) {
for (size_t C = CachedLexPos + N - CachedTokens.size(); C > 0; --C) {
CachedTokens.push_back(Token());
Lex(CachedTokens.back());
if (isUnannotatedBacktrackEnabled())
UnannotatedBacktrackTokens.back().first.push_back(CachedTokens.back());
}
EnterCachingLexMode();
return CachedTokens.back();
Expand All @@ -124,7 +156,7 @@ void Preprocessor::AnnotatePreviousCachedTokens(const Token &Tok) {
for (CachedTokensTy::size_type i = CachedLexPos; i != 0; --i) {
CachedTokensTy::iterator AnnotBegin = CachedTokens.begin() + i-1;
if (AnnotBegin->getLocation() == Tok.getLocation()) {
assert((BacktrackPositions.empty() || BacktrackPositions.back() <= i) &&
assert((!isBacktrackEnabled() || LastBacktrackPos().first <= i) &&
"The backtrack pos points inside the annotated tokens!");
// Replace the cached tokens with the single annotation token.
if (i < CachedLexPos)
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Lex/Preprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ Preprocessor::Preprocessor(std::shared_ptr<PreprocessorOptions> PPOpts,
}

Preprocessor::~Preprocessor() {
assert(BacktrackPositions.empty() && "EnableBacktrack/Backtrack imbalance!");
assert(!isBacktrackEnabled() && "EnableBacktrack/Backtrack imbalance!");

IncludeMacroStack.clear();

Expand Down
41 changes: 2 additions & 39 deletions clang/lib/Parse/ParseCXXInlineMethods.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1205,41 +1205,6 @@ bool Parser::ConsumeAndStoreConditional(CachedTokens &Toks) {
return true;
}

/// A tentative parsing action that can also revert token annotations.
class Parser::UnannotatedTentativeParsingAction : public TentativeParsingAction {
public:
explicit UnannotatedTentativeParsingAction(Parser &Self,
tok::TokenKind EndKind)
: TentativeParsingAction(Self), Self(Self), EndKind(EndKind) {
// Stash away the old token stream, so we can restore it once the
// tentative parse is complete.
TentativeParsingAction Inner(Self);
Self.ConsumeAndStoreUntil(EndKind, Toks, true, /*ConsumeFinalToken*/false);
Inner.Revert();
}

void RevertAnnotations() {
Revert();

// Put back the original tokens.
Self.SkipUntil(EndKind, StopAtSemi | StopBeforeMatch);
if (Toks.size()) {
auto Buffer = std::make_unique<Token[]>(Toks.size());
std::copy(Toks.begin() + 1, Toks.end(), Buffer.get());
Buffer[Toks.size() - 1] = Self.Tok;
Self.PP.EnterTokenStream(std::move(Buffer), Toks.size(), true,
/*IsReinject*/ true);

Self.Tok = Toks.front();
}
}

private:
Parser &Self;
CachedTokens Toks;
tok::TokenKind EndKind;
};

/// ConsumeAndStoreInitializer - Consume and store the token at the passed token
/// container until the end of the current initializer expression (either a
/// default argument or an in-class initializer for a non-static data member).
Expand Down Expand Up @@ -1277,9 +1242,7 @@ bool Parser::ConsumeAndStoreInitializer(CachedTokens &Toks,
// syntactically-valid init-declarator-list, then this comma ends
// the default initializer.
{
UnannotatedTentativeParsingAction PA(*this,
CIK == CIK_DefaultInitializer
? tok::semi : tok::r_paren);
TentativeParsingAction TPA(*this, /*Unannotated=*/true);
Sema::TentativeAnalysisScope Scope(Actions);

TPResult Result = TPResult::Error;
Expand Down Expand Up @@ -1307,7 +1270,7 @@ bool Parser::ConsumeAndStoreInitializer(CachedTokens &Toks,
// Put the token stream back and undo any annotations we performed
// after the comma. They may reflect a different parse than the one
// we will actually perform at the end of the class.
PA.RevertAnnotations();
TPA.Revert();

// If what follows could be a declaration, it is a declaration.
if (Result != TPResult::False && Result != TPResult::Error)
Expand Down
82 changes: 50 additions & 32 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6650,48 +6650,66 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
(Tok.is(tok::identifier) &&
(NextToken().is(tok::coloncolon) || NextToken().is(tok::less))) ||
Tok.is(tok::annot_cxxscope))) {
TentativeParsingAction TPA(*this, /*Unannotated=*/true);
bool EnteringContext = D.getContext() == DeclaratorContext::File ||
D.getContext() == DeclaratorContext::Member;
CXXScopeSpec SS;
SS.setTemplateParamLists(D.getTemplateParameterLists());
ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
/*ObjectHasErrors=*/false, EnteringContext);

if (SS.isNotEmpty()) {
if (Tok.isNot(tok::star)) {
// The scope spec really belongs to the direct-declarator.
if (D.mayHaveIdentifier())
D.getCXXScopeSpec() = SS;
else
AnnotateScopeToken(SS, true);
if (ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
/*ObjectHasErrors=*/false,
/*EnteringContext=*/false,
/*MayBePseudoDestructor=*/nullptr,
/*IsTypename=*/false, /*LastII=*/nullptr,
/*OnlyNamespace=*/false,
/*InUsingDeclaration=*/false,
/*Disambiguation=*/EnteringContext) ||

SS.isEmpty() || SS.isInvalid() || !EnteringContext ||
Tok.is(tok::star)) {
TPA.Commit();
if (SS.isNotEmpty() && Tok.is(tok::star)) {
if (SS.isValid()) {
checkCompoundToken(SS.getEndLoc(), tok::coloncolon,
CompoundToken::MemberPtr);
}

if (DirectDeclParser)
(this->*DirectDeclParser)(D);
SourceLocation StarLoc = ConsumeToken();
D.SetRangeEnd(StarLoc);
DeclSpec DS(AttrFactory);
ParseTypeQualifierListOpt(DS);
D.ExtendWithDeclSpec(DS);

// Recurse to parse whatever is left.
Actions.runWithSufficientStackSpace(D.getBeginLoc(), [&] {
ParseDeclaratorInternal(D, DirectDeclParser);
});

// Sema will have to catch (syntactically invalid) pointers into global
// scope. It has to catch pointers into namespace scope anyway.
D.AddTypeInfo(DeclaratorChunk::getMemberPointer(
SS, DS.getTypeQualifiers(), StarLoc, DS.getEndLoc()),
std::move(DS.getAttributes()),
/*EndLoc=*/SourceLocation());
return;
}
} else {
TPA.Revert();
SS.clear();
ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
/*ObjectHasErrors=*/false,
/*EnteringContext=*/true);
}

if (SS.isValid()) {
checkCompoundToken(SS.getEndLoc(), tok::coloncolon,
CompoundToken::MemberPtr);
}
if (SS.isNotEmpty()) {
// The scope spec really belongs to the direct-declarator.
if (D.mayHaveIdentifier())
D.getCXXScopeSpec() = SS;
else
AnnotateScopeToken(SS, true);

SourceLocation StarLoc = ConsumeToken();
D.SetRangeEnd(StarLoc);
DeclSpec DS(AttrFactory);
ParseTypeQualifierListOpt(DS);
D.ExtendWithDeclSpec(DS);

// Recurse to parse whatever is left.
Actions.runWithSufficientStackSpace(D.getBeginLoc(), [&] {
ParseDeclaratorInternal(D, DirectDeclParser);
});

// Sema will have to catch (syntactically invalid) pointers into global
// scope. It has to catch pointers into namespace scope anyway.
D.AddTypeInfo(DeclaratorChunk::getMemberPointer(
SS, DS.getTypeQualifiers(), StarLoc, DS.getEndLoc()),
std::move(DS.getAttributes()),
/* Don't replace range end. */ SourceLocation());
if (DirectDeclParser)
(this->*DirectDeclParser)(D);
return;
}
}
Expand Down
Loading
Loading