Skip to content

Commit 891d7ff

Browse files
authored
Merge pull request #3974 from onflow/bastian/check-parser-progress
Ensure unbounded loops in parser eventually make progress
2 parents e291a6d + c5ac940 commit 891d7ff

File tree

12 files changed

+346
-47
lines changed

12 files changed

+346
-47
lines changed

cmd/lex/main.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Cadence - The resource-oriented smart contract programming language
3+
*
4+
* Copyright Flow Foundation
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package main
20+
21+
import (
22+
"bufio"
23+
"fmt"
24+
"io"
25+
"os"
26+
"runtime/debug"
27+
28+
"github.com/onflow/cadence/parser/lexer"
29+
)
30+
31+
func main() {
32+
33+
var path string
34+
if len(os.Args) > 1 {
35+
path = os.Args[1]
36+
}
37+
38+
var (
39+
data []byte
40+
err error
41+
)
42+
if len(path) == 0 {
43+
data, err = io.ReadAll(bufio.NewReader(os.Stdin))
44+
} else {
45+
data, err = os.ReadFile(path)
46+
}
47+
if err != nil {
48+
_, _ = fmt.Fprintf(os.Stderr, "error reading file %s: %v\n", path, err)
49+
os.Exit(1)
50+
}
51+
52+
defer func() {
53+
if r := recover(); r != nil {
54+
err = fmt.Errorf("%s", debug.Stack())
55+
}
56+
if err != nil {
57+
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
58+
os.Exit(1)
59+
}
60+
}()
61+
62+
_, _ = fmt.Fprintf(os.Stderr, "lexing %s\n", path)
63+
64+
tokens, err := lexer.Lex(data, nil)
65+
if err != nil {
66+
return
67+
}
68+
defer tokens.Reclaim()
69+
70+
for {
71+
token := tokens.Next()
72+
if token.Type == lexer.TokenEOF {
73+
break
74+
}
75+
76+
fmt.Printf("%s-%s: %s\n", token.StartPos, token.EndPos, token.Type)
77+
}
78+
}

parser/comment.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@
1919
package parser
2020

2121
import (
22+
"github.com/onflow/cadence/errors"
2223
"github.com/onflow/cadence/parser/lexer"
2324
)
2425

2526
func (p *parser) parseBlockComment() (endToken lexer.Token, ok bool) {
2627
var depth int
2728

28-
for {
29+
progress := p.newProgress()
30+
31+
for p.checkProgress(&progress) {
32+
2933
switch p.current.Type {
3034
case lexer.TokenBlockCommentStart:
3135
p.next()
@@ -61,4 +65,6 @@ func (p *parser) parseBlockComment() (endToken lexer.Token, ok bool) {
6165
return
6266
}
6367
}
68+
69+
panic(errors.NewUnreachableError())
6470
}

parser/comment_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Cadence - The resource-oriented smart contract programming language
3+
*
4+
* Copyright Flow Foundation
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package parser
20+
21+
import (
22+
"testing"
23+
24+
"github.com/stretchr/testify/require"
25+
)
26+
27+
func TestParseBlockCommentEmpty(t *testing.T) {
28+
29+
t.Parallel()
30+
31+
_, err := testParseProgram(`/**/`)
32+
require.NoError(t, err)
33+
}

parser/declaration.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ import (
3131
)
3232

3333
func parseDeclarations(p *parser, endTokenType lexer.TokenType) (declarations []ast.Declaration, err error) {
34-
for {
34+
progress := p.newProgress()
35+
36+
for p.checkProgress(&progress) {
37+
3538
_, docString := p.parseTrivia(triviaOptions{
3639
skipNewlines: true,
3740
parseDocStrings: true,
@@ -60,6 +63,8 @@ func parseDeclarations(p *parser, endTokenType lexer.TokenType) (declarations []
6063
declarations = append(declarations, declaration)
6164
}
6265
}
66+
67+
panic(errors.NewUnreachableError())
6368
}
6469

6570
func parseDeclaration(p *parser, docString string) (ast.Declaration, error) {
@@ -76,7 +81,10 @@ func parseDeclaration(p *parser, docString string) (ast.Declaration, error) {
7681
staticModifierEnabled := p.config.StaticModifierEnabled
7782
nativeModifierEnabled := p.config.NativeModifierEnabled
7883

79-
for {
84+
progress := p.newProgress()
85+
86+
for p.checkProgress(&progress) {
87+
8088
p.skipSpaceAndComments()
8189

8290
switch p.current.Type {
@@ -278,6 +286,8 @@ func parseDeclaration(p *parser, docString string) (ast.Declaration, error) {
278286

279287
return nil, nil
280288
}
289+
290+
panic(errors.NewUnreachableError())
281291
}
282292

283293
func handlePriv(p *parser) {
@@ -707,8 +717,11 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) {
707717
parseMoreIdentifiers := func() error {
708718
expectCommaOrFrom := false
709719

710-
atEnd := false
711-
for !atEnd {
720+
var atEnd bool
721+
progress := p.newProgress()
722+
723+
for !atEnd && p.checkProgress(&progress) {
724+
712725
p.nextSemanticToken()
713726

714727
switch p.current.Type {
@@ -1131,7 +1144,10 @@ func parseEntitlementMapping(p *parser, docString string) (*ast.EntitlementMapRe
11311144
func parseEntitlementMappingsAndInclusions(p *parser, endTokenType lexer.TokenType) ([]ast.EntitlementMapElement, error) {
11321145
var elements []ast.EntitlementMapElement
11331146

1134-
for {
1147+
progress := p.newProgress()
1148+
1149+
for p.checkProgress(&progress) {
1150+
11351151
_, docString := p.parseTrivia(triviaOptions{
11361152
skipNewlines: true,
11371153
parseDocStrings: true,
@@ -1171,6 +1187,8 @@ func parseEntitlementMappingsAndInclusions(p *parser, endTokenType lexer.TokenTy
11711187
}
11721188
}
11731189
}
1190+
1191+
panic(errors.NewUnreachableError())
11741192
}
11751193

11761194
// parseEntitlementOrMappingDeclaration parses an entitlement declaration,
@@ -1321,8 +1339,12 @@ func parseCompositeOrInterfaceDeclaration(
13211339
var isInterface bool
13221340
var identifier ast.Identifier
13231341

1324-
for {
1342+
progress := p.newProgress()
1343+
1344+
for p.checkProgress(&progress) {
1345+
13251346
p.skipSpaceAndComments()
1347+
13261348
if !p.current.Is(lexer.TokenIdentifier) {
13271349
return nil, p.syntaxError(
13281350
"expected %s, got %s",
@@ -1516,7 +1538,10 @@ func parseMembersAndNestedDeclarations(p *parser, endTokenType lexer.TokenType)
15161538

15171539
var declarations []ast.Declaration
15181540

1519-
for {
1541+
progress := p.newProgress()
1542+
1543+
for p.checkProgress(&progress) {
1544+
15201545
_, docString := p.parseTrivia(triviaOptions{
15211546
skipNewlines: true,
15221547
parseDocStrings: true,
@@ -1544,6 +1569,8 @@ func parseMembersAndNestedDeclarations(p *parser, endTokenType lexer.TokenType)
15441569
declarations = append(declarations, memberOrNestedDeclaration)
15451570
}
15461571
}
1572+
1573+
panic(errors.NewUnreachableError())
15471574
}
15481575

15491576
// parseMemberOrNestedDeclaration parses a composite or interface member,
@@ -1575,7 +1602,10 @@ func parseMemberOrNestedDeclaration(p *parser, docString string) (ast.Declaratio
15751602
staticModifierEnabled := p.config.StaticModifierEnabled
15761603
nativeModifierEnabled := p.config.NativeModifierEnabled
15771604

1578-
for {
1605+
progress := p.newProgress()
1606+
1607+
for p.checkProgress(&progress) {
1608+
15791609
p.skipSpaceAndComments()
15801610

15811611
switch p.current.Type {
@@ -1826,6 +1856,8 @@ func parseMemberOrNestedDeclaration(p *parser, docString string) (ast.Declaratio
18261856

18271857
return nil, nil
18281858
}
1859+
1860+
panic(errors.NewUnreachableError())
18291861
}
18301862

18311863
func rejectAllModifiers(

parser/expression.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,9 +1002,14 @@ func defineInvocationExpression() {
10021002
}
10031003

10041004
func parseArgumentListRemainder(p *parser) (arguments []*ast.Argument, endPos ast.Position, err error) {
1005-
atEnd := false
10061005
expectArgument := true
1007-
for !atEnd {
1006+
1007+
var atEnd bool
1008+
1009+
progress := p.newProgress()
1010+
1011+
for !atEnd && p.checkProgress(&progress) {
1012+
10081013
p.skipSpaceAndComments()
10091014

10101015
switch p.current.Type {
@@ -1163,7 +1168,11 @@ func defineStringExpression() {
11631168
// flag for ending " check
11641169
missingEnd := true
11651170

1166-
for curToken.Is(lexer.TokenString) {
1171+
progress := p.newProgress()
1172+
1173+
for curToken.Is(lexer.TokenString) &&
1174+
p.checkProgress(&progress) {
1175+
11671176
literal = p.tokenSource(curToken)
11681177

11691178
// remove quotation marks if they exist
@@ -1251,8 +1260,14 @@ func defineArrayExpression() {
12511260
p.skipSpaceAndComments()
12521261

12531262
var values []ast.Expression
1254-
for !p.current.Is(lexer.TokenBracketClose) {
1263+
1264+
progress := p.newProgress()
1265+
1266+
for !p.current.Is(lexer.TokenBracketClose) &&
1267+
p.checkProgress(&progress) {
1268+
12551269
p.skipSpaceAndComments()
1270+
12561271
if len(values) > 0 {
12571272
if !p.current.Is(lexer.TokenComma) {
12581273
break
@@ -1292,8 +1307,14 @@ func defineDictionaryExpression() {
12921307
p.skipSpaceAndComments()
12931308

12941309
var entries []ast.DictionaryEntry
1295-
for !p.current.Is(lexer.TokenBraceClose) {
1310+
1311+
progress := p.newProgress()
1312+
1313+
for !p.current.Is(lexer.TokenBraceClose) &&
1314+
p.checkProgress(&progress) {
1315+
12961316
p.skipSpaceAndComments()
1317+
12971318
if len(entries) > 0 {
12981319
if !p.current.Is(lexer.TokenComma) {
12991320
break
@@ -1547,7 +1568,10 @@ func parseExpression(p *parser, rightBindingPower int) (ast.Expression, error) {
15471568
return nil, err
15481569
}
15491570

1550-
for {
1571+
progress := p.newProgress()
1572+
1573+
for p.checkProgress(&progress) {
1574+
15511575
// Automatically skip any trivia between the left and right expression.
15521576
// However, do not automatically skip newlines:
15531577
// Some left denotations do not support newlines before them,

parser/function.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,13 @@ func parseParameterList(p *parser, expectDefaultArguments bool) (*ast.ParameterL
5353

5454
expectParameter := true
5555

56-
atEnd := false
57-
for !atEnd {
56+
var atEnd bool
57+
progress := p.newProgress()
58+
59+
for !atEnd && p.checkProgress(&progress) {
60+
5861
p.skipSpaceAndComments()
62+
5963
switch p.current.Type {
6064
case lexer.TokenIdentifier:
6165
if !expectParameter {
@@ -218,9 +222,13 @@ func parseTypeParameterList(p *parser) (*ast.TypeParameterList, error) {
218222

219223
expectTypeParameter := true
220224

221-
atEnd := false
222-
for !atEnd {
225+
var atEnd bool
226+
progress := p.newProgress()
227+
228+
for !atEnd && p.checkProgress(&progress) {
229+
223230
p.skipSpaceAndComments()
231+
224232
switch p.current.Type {
225233
case lexer.TokenIdentifier:
226234
if !expectTypeParameter {

0 commit comments

Comments
 (0)