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
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
@@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^
26 changes: 23 additions & 3 deletions clang/include/clang/Lex/Preprocessor.h
Original file line number Diff line number Diff line change
@@ -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.
///
@@ -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();

@@ -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);

@@ -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;
15 changes: 8 additions & 7 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
@@ -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;
@@ -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() {
@@ -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.
@@ -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
48 changes: 40 additions & 8 deletions clang/lib/Lex/PPCaching.cpp
Original file line number Diff line number Diff line change
@@ -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
@@ -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();
}

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

@@ -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();
@@ -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)
2 changes: 1 addition & 1 deletion clang/lib/Lex/Preprocessor.cpp
Original file line number Diff line number Diff line change
@@ -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();

41 changes: 2 additions & 39 deletions clang/lib/Parse/ParseCXXInlineMethods.cpp
Original file line number Diff line number Diff line change
@@ -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).
@@ -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;
@@ -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)
82 changes: 50 additions & 32 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading
Loading