From 287fd924ff18c14a91a151eb7b2f14d89322bb9d Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 10 May 2023 12:01:22 +0200 Subject: [PATCH 1/2] Add testcases to reproduce crash --- decoder/expr_keyword_completion_test.go | 11 +++++++++++ decoder/expr_literal_type_completion_test.go | 14 ++++++++++++++ decoder/expr_literal_value_completion_test.go | 14 ++++++++++++++ decoder/expr_type_declaration_completion_test.go | 12 ++++++++++++ 4 files changed, 51 insertions(+) diff --git a/decoder/expr_keyword_completion_test.go b/decoder/expr_keyword_completion_test.go index f7881eee..e97936d8 100644 --- a/decoder/expr_keyword_completion_test.go +++ b/decoder/expr_keyword_completion_test.go @@ -75,6 +75,17 @@ func TestCompletionAtPos_exprKeyword(t *testing.T) { }, }), }, + { + "matching prefix with dot", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Keyword{Keyword: "foobar"}, + }, + }, + `attr = f.`, + hcl.Pos{Line: 1, Column: 10, Byte: 9}, + lang.CompleteCandidates([]lang.Candidate{}), + }, { "matching prefix in the middle", map[string]*schema.AttributeSchema{ diff --git a/decoder/expr_literal_type_completion_test.go b/decoder/expr_literal_type_completion_test.go index 3f3db302..e38f49ae 100644 --- a/decoder/expr_literal_type_completion_test.go +++ b/decoder/expr_literal_type_completion_test.go @@ -96,6 +96,20 @@ func TestCompletionAtPos_exprLiteralType(t *testing.T) { }, }), }, + { + "bool by prefix with dot", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.LiteralType{ + Type: cty.Bool, + }, + }, + }, + `attr = f. +`, + hcl.Pos{Line: 1, Column: 10, Byte: 9}, + lang.CompleteCandidates([]lang.Candidate{}), + }, { "string", map[string]*schema.AttributeSchema{ diff --git a/decoder/expr_literal_value_completion_test.go b/decoder/expr_literal_value_completion_test.go index a634d2b1..a1c265a5 100644 --- a/decoder/expr_literal_value_completion_test.go +++ b/decoder/expr_literal_value_completion_test.go @@ -117,6 +117,20 @@ func TestCompletionAtPos_exprLiteralValue(t *testing.T) { }, }), }, + { + "bool partial with dot", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.LiteralValue{ + Value: cty.BoolVal(true), + }, + }, + }, + `attr = tr. +`, + hcl.Pos{Line: 1, Column: 11, Byte: 10}, + lang.CompleteCandidates([]lang.Candidate{}), + }, { "bool partial middle", map[string]*schema.AttributeSchema{ diff --git a/decoder/expr_type_declaration_completion_test.go b/decoder/expr_type_declaration_completion_test.go index be5a9460..e7e741f2 100644 --- a/decoder/expr_type_declaration_completion_test.go +++ b/decoder/expr_type_declaration_completion_test.go @@ -124,6 +124,18 @@ func TestCompletionAtPos_exprTypeDeclaration(t *testing.T) { }, }), }, + { + "partial name with dot", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.TypeDeclaration{}, + }, + }, + `attr = st. +`, + hcl.Pos{Line: 1, Column: 11, Byte: 10}, + lang.CompleteCandidates([]lang.Candidate{}), + }, { "partial list name", map[string]*schema.AttributeSchema{ From 8722e35d0660c01c8cee6f3958a22171ef6786ca Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 10 May 2023 12:29:55 +0200 Subject: [PATCH 2/2] Fix crash by checking expression length --- decoder/expr_keyword_completion.go | 6 ++++++ decoder/expr_literal_type_completion.go | 6 ++++++ decoder/expr_literal_value_completion.go | 6 ++++++ decoder/expr_type_declaration_completion.go | 6 ++++++ 4 files changed, 24 insertions(+) diff --git a/decoder/expr_keyword_completion.go b/decoder/expr_keyword_completion.go index 3766af29..8e5ea4e6 100644 --- a/decoder/expr_keyword_completion.go +++ b/decoder/expr_keyword_completion.go @@ -43,6 +43,12 @@ func (kw Keyword) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candi } prefixLen := pos.Byte - eType.Traversal.SourceRange().Start.Byte + if prefixLen > len(eType.Traversal.RootName()) { + // The user has probably typed an extra character, such as a + // period, that is not (yet) part of the expression. This prefix + // won't match anything, so we'll return early. + return []lang.Candidate{} + } prefix := eType.Traversal.RootName()[0:prefixLen] if strings.HasPrefix(kw.cons.Keyword, prefix) { diff --git a/decoder/expr_literal_type_completion.go b/decoder/expr_literal_type_completion.go index 0b806bfc..d54fd949 100644 --- a/decoder/expr_literal_type_completion.go +++ b/decoder/expr_literal_type_completion.go @@ -140,6 +140,12 @@ func (lt LiteralType) completeBoolAtPos(ctx context.Context, pos hcl.Pos) []lang case *hclsyntax.ScopeTraversalExpr: prefixLen := pos.Byte - eType.Range().Start.Byte + if prefixLen > len(eType.Traversal.RootName()) { + // The user has probably typed an extra character, such as a + // period, that is not (yet) part of the expression. This prefix + // won't match anything, so we'll return early. + return []lang.Candidate{} + } prefix := eType.Traversal.RootName()[0:prefixLen] return boolLiteralTypeCandidates(prefix, eType.Range()) diff --git a/decoder/expr_literal_value_completion.go b/decoder/expr_literal_value_completion.go index 6a4cbe69..94141af8 100644 --- a/decoder/expr_literal_value_completion.go +++ b/decoder/expr_literal_value_completion.go @@ -87,6 +87,12 @@ func (lv LiteralValue) completeBoolAtPos(ctx context.Context, pos hcl.Pos) []lan case *hclsyntax.ScopeTraversalExpr: prefixLen := pos.Byte - eType.Range().Start.Byte + if prefixLen > len(eType.Traversal.RootName()) { + // The user has probably typed an extra character, such as a + // period, that is not (yet) part of the expression. This prefix + // won't match anything, so we'll return early. + return []lang.Candidate{} + } prefix := eType.Traversal.RootName()[0:prefixLen] return lv.boolLiteralValueCandidates(prefix, eType.Range()) diff --git a/decoder/expr_type_declaration_completion.go b/decoder/expr_type_declaration_completion.go index 2ce8bceb..c9f3fc07 100644 --- a/decoder/expr_type_declaration_completion.go +++ b/decoder/expr_type_declaration_completion.go @@ -32,6 +32,12 @@ func (td TypeDeclaration) CompletionAtPos(ctx context.Context, pos hcl.Pos) []la } prefixLen := pos.Byte - eType.Range().Start.Byte + if prefixLen > len(eType.Traversal.RootName()) { + // The user has probably typed an extra character, such as a + // period, that is not (yet) part of the expression. This prefix + // won't match anything, so we'll return early. + return []lang.Candidate{} + } prefix := eType.Traversal.RootName()[0:prefixLen] editRange := hcl.Range{