Skip to content

Commit

Permalink
implemented more checks
Browse files Browse the repository at this point in the history
  • Loading branch information
ascandone committed Aug 7, 2024
1 parent ad44569 commit 0e825ae
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 32 deletions.
104 changes: 72 additions & 32 deletions analysis/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,15 @@ type Diagnostic struct {
}

type CheckResult struct {
emptiedAccount map[string]struct{}
unboundedSend bool
declaredVars map[string]parser.VarDeclaration
unusedVars map[string]parser.Range
varResolution map[*parser.VariableLiteral]parser.VarDeclaration
fnCallResolution map[*parser.FnCallIdentifier]FnCallResolution
Diagnostics []Diagnostic
Program parser.Program
unboundedAccountInSend parser.Literal
emptiedAccount map[string]struct{}
unboundedSend bool
declaredVars map[string]parser.VarDeclaration
unusedVars map[string]parser.Range
varResolution map[*parser.VariableLiteral]parser.VarDeclaration
fnCallResolution map[*parser.FnCallIdentifier]FnCallResolution
Diagnostics []Diagnostic
Program parser.Program
}

func (r CheckResult) GetErrorsCount() int {
Expand Down Expand Up @@ -154,6 +155,7 @@ func (res *CheckResult) check() {
}
}
for _, statement := range res.Program.Statements {
res.unboundedAccountInSend = nil
res.checkStatement(statement)
}

Expand Down Expand Up @@ -382,32 +384,30 @@ func (res *CheckResult) checkSentValue(sentValue parser.SentValue) {
}
}

func (res *CheckResult) withCloneEmptyAccount() func() {
bkEmptiedAccount := res.emptiedAccount
res.emptiedAccount = make(map[string]struct{})
for k, v := range bkEmptiedAccount {
res.emptiedAccount[k] = v
}
return func() {
res.emptiedAccount = bkEmptiedAccount
}
}

func (res *CheckResult) checkSource(source parser.Source) {
if source == nil {
return
}

if res.unboundedAccountInSend != nil {
res.Diagnostics = append(res.Diagnostics, Diagnostic{
Range: source.GetRange(),
Kind: &UnboundedAccountIsNotLast{},
})
}

switch source := source.(type) {
case *parser.AccountLiteral:
if source.Name == "world" && res.unboundedSend {
if source.IsWorld() && res.unboundedSend {
res.Diagnostics = append(res.Diagnostics, Diagnostic{
Range: source.GetRange(),
Kind: &InvalidUnboundedAccount{},
})
} else if source.IsWorld() {
res.unboundedAccountInSend = source
}

if _, emptied := res.emptiedAccount[source.Name]; emptied && source.Name != "world" {
if _, emptied := res.emptiedAccount[source.Name]; emptied && !source.IsWorld() {
res.Diagnostics = append(res.Diagnostics, Diagnostic{
Kind: &EmptiedAccount{Name: source.Name},
Range: source.Range,
Expand All @@ -420,13 +420,17 @@ func (res *CheckResult) checkSource(source parser.Source) {
res.checkLiteral(source, TypeAccount)

case *parser.SourceOverdraft:
if accountLiteral, ok := source.Address.(*parser.AccountLiteral); ok && accountLiteral.Name == "world" {
if accountLiteral, ok := source.Address.(*parser.AccountLiteral); ok && accountLiteral.IsWorld() {
res.Diagnostics = append(res.Diagnostics, Diagnostic{
Range: accountLiteral.Range,
Kind: &InvalidWorldOverdraft{},
})
}

if source.Bounded == nil {
res.unboundedAccountInSend = source.Address
}

if res.unboundedSend {
res.Diagnostics = append(res.Diagnostics, Diagnostic{
Range: source.Address.GetRange(),
Expand All @@ -445,18 +449,13 @@ func (res *CheckResult) checkSource(source parser.Source) {
}

case *parser.SourceCapped:
bkSendAll := res.unboundedSend
res.unboundedSend = false
defer func() {
res.unboundedSend = bkSendAll
}()

handler := res.withCloneEmptyAccount()
defer handler()
onExit := res.enterCappedSource()

res.checkLiteral(source.Cap, TypeMonetary)
res.checkSource(source.From)

onExit()

case *parser.SourceAllotment:
if res.unboundedSend {
res.Diagnostics = append(res.Diagnostics, Diagnostic{
Expand Down Expand Up @@ -489,9 +488,9 @@ func (res *CheckResult) checkSource(source parser.Source) {
}
}

handler := res.withCloneEmptyAccount()
onExit := res.enterCappedSource()
res.checkSource(allottedItem.From)
handler()
onExit()
}

res.checkHasBadAllotmentSum(*sum, source.Range, remainingAllotment, variableLiterals)
Expand Down Expand Up @@ -606,3 +605,44 @@ func (res *CheckResult) checkHasBadAllotmentSum(
}
}
}

func (res *CheckResult) withCloneEmptyAccount() func() {
initial := res.emptiedAccount
res.emptiedAccount = make(map[string]struct{})
for k, v := range initial {
res.emptiedAccount[k] = v
}
return func() {
res.emptiedAccount = initial
}
}

func (res *CheckResult) withCloneUnboundedAccountInSend() func() {
initial := res.unboundedAccountInSend
res.unboundedAccountInSend = nil

return func() {
res.unboundedAccountInSend = initial
}
}

func (res *CheckResult) withCloneUnboundedSend() func() {
initial := res.unboundedSend
res.unboundedSend = false

return func() {
res.unboundedSend = initial
}
}

func (res *CheckResult) enterCappedSource() func() {
exitCloneEmptyAccount := res.withCloneEmptyAccount()
exitCloneUnboundeAccountInSend := res.withCloneUnboundedAccountInSend()
exitCloneUnboundedSend := res.withCloneUnboundedSend()

return func() {
exitCloneEmptyAccount()
exitCloneUnboundeAccountInSend()
exitCloneUnboundedSend()
}
}
124 changes: 124 additions & 0 deletions analysis/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1232,3 +1232,127 @@ func TestDoNotEmitEmptiedAccountOnAllotment(t *testing.T) {
diagnostics := analysis.CheckSource(input).Diagnostics
require.Empty(t, diagnostics)
}

func TestDoNotAllowExprAfterWorld(t *testing.T) {
input := `
send [COIN 100] (
source = {
@world
@another
}
destination = @dest
)
`

diagnostics := analysis.CheckSource(input).Diagnostics
require.Len(t, diagnostics, 1)

require.Equal(t,
diagnostics[0].Kind,
&analysis.UnboundedAccountIsNotLast{},
)

require.Equal(t,
diagnostics[0].Range,
RangeOfIndexed(input, "@another", 0),
)
}

func TestAllowWorldInNextExpr(t *testing.T) {
input := `
send [COIN 1] (
source = @world
destination = @dest
)
send [COIN 1] (
source = @world
destination = @dest
)
`

diagnostics := analysis.CheckSource(input).Diagnostics
require.Empty(t, diagnostics)

}

func TestAllowWorldInMaxedExpr(t *testing.T) {
input := `
send [COIN 10] (
source = {
max [COIN 1] from @world
@x
}
destination = @dest
)
`

diagnostics := analysis.CheckSource(input).Diagnostics
require.Empty(t, diagnostics)

}

func TestDoNotAllowExprAfterWorldInsideMaxed(t *testing.T) {
input := `
send [COIN 10] (
source = max [COIN 1] from {
@world
@x
}
destination = @dest
)
`

diagnostics := analysis.CheckSource(input).Diagnostics
require.Len(t, diagnostics, 1)

require.Equal(t,
diagnostics[0].Kind,
&analysis.UnboundedAccountIsNotLast{},
)

require.Equal(t,
diagnostics[0].Range,
RangeOfIndexed(input, "@x", 0),
)
}

func TestDoNotAllowExprAfterUnbounded(t *testing.T) {
input := `
send [COIN 100] (
source = {
@unbounded allowing unbounded overdraft
@another
}
destination = @dest
)
`

diagnostics := analysis.CheckSource(input).Diagnostics
require.Len(t, diagnostics, 1)

require.Equal(t,
diagnostics[0].Kind,
&analysis.UnboundedAccountIsNotLast{},
)

require.Equal(t,
diagnostics[0].Range,
RangeOfIndexed(input, "@another", 0),
)
}

func TestAllowExprAfterBoundedOverdraft(t *testing.T) {
input := `
send [COIN 100] (
source = {
@unbounded allowing overdraft up to [COIN 10]
@another
}
destination = @dest
)
`

diagnostics := analysis.CheckSource(input).Diagnostics
require.Empty(t, diagnostics)
}
10 changes: 10 additions & 0 deletions analysis/diagnostic_kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,13 @@ func (e *EmptiedAccount) Message() string {
func (*EmptiedAccount) Severity() Severity {
return WarningSeverity
}

type UnboundedAccountIsNotLast struct{}

func (e *UnboundedAccountIsNotLast) Message() string {
return "Inorder sources after an unbounded overdraft are never reached"
}

func (*UnboundedAccountIsNotLast) Severity() Severity {
return WarningSeverity
}
4 changes: 4 additions & 0 deletions parser/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ type RemainingAllotment struct {
Range Range
}

func (a *AccountLiteral) IsWorld() bool {
return a.Name == "world"
}

// Source exprs

type Source interface {
Expand Down

0 comments on commit 0e825ae

Please sign in to comment.