Skip to content

Commit c5503ce

Browse files
authored
Merge pull request #31685 from akyrtzi/libsyntax-incremental-parse-unreachable-fix
[Parser/libSyntax] Avoid doing lookup for a previous parsed node when we are in backtracking mode
2 parents 528479e + 6bfde00 commit c5503ce

File tree

7 files changed

+71
-8
lines changed

7 files changed

+71
-8
lines changed

include/swift/Parse/Parser.h

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -494,13 +494,7 @@ class Parser {
494494
~BacktrackingScope();
495495
bool willBacktrack() const { return Backtrack; }
496496

497-
void cancelBacktrack() {
498-
Backtrack = false;
499-
SynContext->setTransparent();
500-
SynContext.reset();
501-
DT.commit();
502-
TempReceiver.shouldTransfer = true;
503-
}
497+
void cancelBacktrack();
504498
};
505499

506500
/// RAII object that, when it is destructed, restores the parser and lexer to

include/swift/Parse/SyntaxParsingContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@ class alignas(1 << SyntaxAlignInBits) SyntaxParsingContext {
326326
IsBacktracking = true;
327327
}
328328

329+
/// Cancels backtracking state from the top of the context stack until `this` context.
330+
void cancelBacktrack();
331+
329332
bool isBacktracking() const { return IsBacktracking; }
330333

331334
void setShouldDefer(bool Value = true) { ShouldDefer = Value; }

lib/Parse/ParseDecl.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5531,7 +5531,14 @@ ParserStatus Parser::parseGetSet(ParseDeclOptions Flags,
55315531
parseImplicitGetter();
55325532
return makeParserSuccess();
55335533
}
5534-
IsFirstAccessor = false;
5534+
if (IsFirstAccessor) {
5535+
// Continue parsing without backtracking so we can re-use previously
5536+
// parsed nodes for incremental re-parsing, but avoid destructing
5537+
// `backtrack` because its syntax context isn't at the top of the stack at
5538+
// this point.
5539+
backtrack->cancelBacktrack();
5540+
IsFirstAccessor = false;
5541+
}
55355542

55365543
// For now, immediately reject illegal accessors in protocols just to
55375544
// avoid having to deal with them everywhere.

lib/Parse/Parser.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,19 @@ swift::Parser::BacktrackingScope::~BacktrackingScope() {
222222
}
223223
}
224224

225+
void swift::Parser::BacktrackingScope::cancelBacktrack() {
226+
if (!Backtrack)
227+
return;
228+
229+
Backtrack = false;
230+
SynContext->cancelBacktrack();
231+
SynContext->setTransparent();
232+
if (SynContext->isTopOfContextStack())
233+
SynContext.reset();
234+
DT.commit();
235+
TempReceiver.shouldTransfer = true;
236+
}
237+
225238
/// Tokenizes a string literal, taking into account string interpolation.
226239
static void getStringPartTokens(const Token &Tok, const LangOptions &LangOpts,
227240
const SourceManager &SM,

lib/Parse/SyntaxParsingContext.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,39 @@ SyntaxParsingContext::SyntaxParsingContext(SyntaxParsingContext *&CtxtHolder,
4646
getStorage().reserve(128);
4747
}
4848

49+
void SyntaxParsingContext::cancelBacktrack() {
50+
SyntaxParsingContext *curr = CtxtHolder;
51+
while (true) {
52+
curr->IsBacktracking = false;
53+
if (curr == this) {
54+
break;
55+
}
56+
curr = curr->getParent();
57+
}
58+
}
59+
4960
size_t SyntaxParsingContext::lookupNode(size_t LexerOffset, SourceLoc Loc) {
5061
if (!Enabled)
5162
return 0;
5263

64+
// Avoid doing lookup for a previous parsed node when we are in backtracking
65+
// mode. This is because if the parser library client give us a node pointer
66+
// and we discard it due to backtracking then we are violating this invariant:
67+
//
68+
// The parser guarantees that any \c swiftparse_client_node_t, given to the
69+
// parser by \c swiftparse_node_handler_t or \c swiftparse_node_lookup_t,
70+
// will be returned back to the client.
71+
//
72+
// which will end up likely creating a memory leak for the client because
73+
// the semantics is that the parser accepts ownership of the object that the
74+
// node pointer represents.
75+
//
76+
// Note that the fact that backtracking mode is disabling incremental parse
77+
// node re-use is another reason that we should keep backtracking state as
78+
// minimal as possible.
79+
if (isBacktracking())
80+
return 0;
81+
5382
assert(getStorage().size() == Offset &&
5483
"Cannot do lookup if nodes have already been gathered");
5584
assert(Mode == AccumulationMode::CreateSyntax &&
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %validate-incrparse %s --test-case REPLACE
3+
4+
5+
var value: Int {
6+
get { fatalError() }
7+
<<REPLACE<|||}>>>
8+
9+
let x = 10

test/incrParse/reuse.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
// RUN: %empty-directory(%t)
2+
// RUN: %validate-incrparse %s --test-case MODIFY_ACCESSOR
23
// RUN: %validate-incrparse %s --test-case ADD_PROPERTY
34
// RUN: %validate-incrparse %s --test-case WRAP_IN_CLASS
45
// RUN: %validate-incrparse %s --test-case UNWRAP_CLASS
56
// RUN: %validate-incrparse %s --test-case NEXT_TOKEN_CALCULATION
67

78
func start() {}
89

10+
<reparse MODIFY_ACCESSOR>var someprop: Int {</reparse MODIFY_ACCESSOR>
11+
<reparse MODIFY_ACCESSOR>get {</reparse MODIFY_ACCESSOR>
12+
return 0
13+
<reparse MODIFY_ACCESSOR>}</reparse MODIFY_ACCESSOR>
14+
<reparse MODIFY_ACCESSOR>set { print(<<MODIFY_ACCESSOR<|||0>>>) }</reparse MODIFY_ACCESSOR>
15+
<reparse MODIFY_ACCESSOR>}</reparse MODIFY_ACCESSOR>
16+
917
<reparse ADD_PROPERTY>struct Foo {</reparse ADD_PROPERTY>
1018
let a: Int
1119
let b: Int

0 commit comments

Comments
 (0)