OSDN Git Service

add AST for if-else statement which contains lock or unlock statement (#32)
[bytom/equity.git] / compiler / checks.go
1 package compiler
2
3 import "fmt"
4
5 func checkRecursive(contract *Contract) bool {
6         for _, clause := range contract.Clauses {
7                 for _, stmt := range clause.statements {
8                         if result := checkStatRecursive(stmt, contract.Name); result {
9                                 return true
10                         }
11                 }
12         }
13         return false
14 }
15
16 func checkStatRecursive(stmt statement, contractName string) bool {
17         switch s := stmt.(type) {
18         case *ifStatement:
19                 for _, trueStmt := range s.body.trueBody {
20                         if result := checkStatRecursive(trueStmt, contractName); result {
21                                 return true
22                         }
23                 }
24
25                 for _, falseStmt := range s.body.falseBody {
26                         if result := checkStatRecursive(falseStmt, contractName); result {
27                                 return true
28                         }
29                 }
30
31         case *lockStatement:
32                 if c, ok := s.program.(*callExpr); ok {
33                         if references(c.fn, contractName) {
34                                 return true
35                         }
36                 }
37         }
38
39         return false
40 }
41
42 func calClauseValues(contract *Contract, env *environ, stmt statement, conditions map[string]Condition, condValues map[string][]ValueInfo, index *int) (valueInfo *ValueInfo) {
43         switch s := stmt.(type) {
44         case *ifStatement:
45                 *index++
46                 strIndex := fmt.Sprintf("%d", *index)
47
48                 conditionCounts := make(map[string]int)
49                 s.condition.countVarRefs(conditionCounts)
50
51                 params := []*Param{}
52                 for v := range conditionCounts {
53                         if entry := env.lookup(v); entry != nil && (entry.r == roleContractParam || entry.r == roleContractValue || entry.r == roleClauseParam || entry.r == roleClauseVariable) {
54                                 params = append(params, &Param{Name: v, Type: entry.t})
55                         }
56                 }
57
58                 condition := Condition{Source: s.condition.String(), Params: params}
59                 conditions["condition_"+strIndex] = condition
60
61                 trueValues := []ValueInfo{}
62                 for _, trueStmt := range s.body.trueBody {
63                         trueValue := calClauseValues(contract, env, trueStmt, conditions, condValues, index)
64                         if trueValue != nil {
65                                 trueValues = append(trueValues, *trueValue)
66                         }
67                 }
68                 condValues["truebody_"+strIndex] = trueValues
69
70                 if len(s.body.falseBody) != 0 {
71                         falseValues := []ValueInfo{}
72                         for _, falseStmt := range s.body.falseBody {
73                                 falseValue := calClauseValues(contract, env, falseStmt, conditions, condValues, index)
74                                 if falseValue != nil {
75                                         falseValues = append(falseValues, *falseValue)
76                                 }
77                         }
78                         condValues["falsebody_"+strIndex] = falseValues
79                 }
80
81         case *lockStatement:
82                 valueInfo = &ValueInfo{
83                         Amount:  s.lockedAmount.String(),
84                         Asset:   s.lockedAsset.String(),
85                         Program: s.program.String(),
86                 }
87
88                 lockCounts := make(map[string]int)
89                 s.lockedAmount.countVarRefs(lockCounts)
90                 if _, ok := lockCounts[s.lockedAmount.String()]; !ok {
91                         params := []*Param{}
92                         for v := range lockCounts {
93                                 if entry := env.lookup(v); entry != nil && (entry.r == roleContractParam || entry.r == roleContractValue || entry.r == roleClauseParam || entry.r == roleClauseVariable) {
94                                         params = append(params, &Param{Name: v, Type: entry.t})
95                                 }
96                         }
97                         valueInfo.Params = params
98                 }
99
100         case *unlockStatement:
101                 valueInfo = &ValueInfo{
102                         Amount: contract.Value.Amount,
103                         Asset:  contract.Value.Asset,
104                 }
105         }
106
107         return valueInfo
108 }
109
110 func prohibitSigParams(contract *Contract) error {
111         for _, p := range contract.Params {
112                 if p.Type == sigType {
113                         return fmt.Errorf("contract parameter \"%s\" has type Signature, but contract parameters cannot have type Signature", p.Name)
114                 }
115         }
116         return nil
117 }
118
119 func requireAllParamsUsedInClauses(params []*Param, clauses []*Clause) error {
120         for _, p := range params {
121                 used := false
122                 for _, c := range clauses {
123                         err := requireAllParamsUsedInClause([]*Param{p}, c)
124                         if err == nil {
125                                 used = true
126                                 break
127                         }
128                 }
129
130                 if !used {
131                         return fmt.Errorf("parameter \"%s\" is unused", p.Name)
132                 }
133         }
134         return nil
135 }
136
137 func requireAllParamsUsedInClause(params []*Param, clause *Clause) error {
138         for _, p := range params {
139                 used := false
140                 for _, stmt := range clause.statements {
141                         if used = checkParamUsedInStatement(p, stmt); used {
142                                 break
143                         }
144                 }
145
146                 if !used {
147                         return fmt.Errorf("parameter \"%s\" is unused in clause \"%s\"", p.Name, clause.Name)
148                 }
149         }
150         return nil
151 }
152
153 func checkParamUsedInStatement(param *Param, stmt statement) (used bool) {
154         switch s := stmt.(type) {
155         case *ifStatement:
156                 if used = references(s.condition, param.Name); used {
157                         return used
158                 }
159
160                 for _, st := range s.body.trueBody {
161                         if used = checkParamUsedInStatement(param, st); used {
162                                 break
163                         }
164                 }
165
166                 if !used {
167                         for _, st := range s.body.falseBody {
168                                 if used = checkParamUsedInStatement(param, st); used {
169                                         break
170                                 }
171                         }
172                 }
173
174         case *defineStatement:
175                 used = references(s.expr, param.Name)
176         case *assignStatement:
177                 used = references(s.expr, param.Name)
178         case *verifyStatement:
179                 used = references(s.expr, param.Name)
180         case *lockStatement:
181                 used = references(s.lockedAmount, param.Name) || references(s.lockedAsset, param.Name) || references(s.program, param.Name)
182         case *unlockStatement:
183                 used = references(s.unlockedAmount, param.Name) || references(s.unlockedAsset, param.Name)
184         }
185
186         return used
187 }
188
189 func references(expr expression, name string) bool {
190         switch e := expr.(type) {
191         case *binaryExpr:
192                 return references(e.left, name) || references(e.right, name)
193         case *unaryExpr:
194                 return references(e.expr, name)
195         case *callExpr:
196                 if references(e.fn, name) {
197                         return true
198                 }
199                 for _, a := range e.args {
200                         if references(a, name) {
201                                 return true
202                         }
203                 }
204                 return false
205         case varRef:
206                 return string(e) == name
207         case listExpr:
208                 for _, elt := range []expression(e) {
209                         if references(elt, name) {
210                                 return true
211                         }
212                 }
213                 return false
214         }
215         return false
216 }
217
218 func referencedBuiltin(expr expression) *builtin {
219         if v, ok := expr.(varRef); ok {
220                 for _, b := range builtins {
221                         if string(v) == b.name {
222                                 return &b
223                         }
224                 }
225         }
226         return nil
227 }
228
229 func countsVarRef(stat statement, counts map[string]int) map[string]int {
230         if stmt, ok := stat.(*defineStatement); ok && stmt.expr == nil {
231                 return counts
232         }
233
234         if _, ok := stat.(*unlockStatement); ok {
235                 return counts
236         }
237
238         stat.countVarRefs(counts)
239         if stmt, ok := stat.(*ifStatement); ok {
240                 for _, trueStmt := range stmt.body.trueBody {
241                         counts = countsVarRef(trueStmt, counts)
242                 }
243
244                 for _, falseStmt := range stmt.body.falseBody {
245                         counts = countsVarRef(falseStmt, counts)
246                 }
247         }
248
249         return counts
250 }
251
252 func assignIndexes(clause *Clause) error {
253         var nextIndex int64
254         for i, stmt := range clause.statements {
255                 if nextIndex = assignStatIndexes(stmt, nextIndex, i != len(clause.statements)-1); nextIndex < 0 {
256                         return fmt.Errorf("Not support that the number of lock/unlock statement is not equal between ifbody and elsebody when the if-else is not the last statement in clause \"%s\"", clause.Name)
257                 }
258         }
259
260         return nil
261 }
262
263 func assignStatIndexes(stat statement, nextIndex int64, nonFinalFlag bool) int64 {
264         switch stmt := stat.(type) {
265         case *ifStatement:
266                 trueIndex := nextIndex
267                 falseIndex := nextIndex
268                 for _, trueStmt := range stmt.body.trueBody {
269                         trueIndex = assignStatIndexes(trueStmt, trueIndex, nonFinalFlag)
270                 }
271
272                 for _, falseStmt := range stmt.body.falseBody {
273                         falseIndex = assignStatIndexes(falseStmt, falseIndex, nonFinalFlag)
274                 }
275
276                 if trueIndex != falseIndex && nonFinalFlag {
277                         return -1
278                 } else if trueIndex == falseIndex {
279                         nextIndex = trueIndex
280                 }
281
282         case *lockStatement:
283                 stmt.index = nextIndex
284                 nextIndex++
285
286         case *unlockStatement:
287                 nextIndex++
288         }
289
290         return nextIndex
291 }
292
293 func typeCheckClause(contract *Contract, clause *Clause, env *environ) error {
294         for _, s := range clause.statements {
295                 if err := typeCheckStatement(s, contract.Value, clause.Name, env); err != nil {
296                         return err
297                 }
298         }
299         return nil
300 }
301
302 func typeCheckStatement(stat statement, contractValue ValueInfo, clauseName string, env *environ) error {
303         switch stmt := stat.(type) {
304         case *ifStatement:
305                 for _, trueStmt := range stmt.body.trueBody {
306                         if err := typeCheckStatement(trueStmt, contractValue, clauseName, env); err != nil {
307                                 return err
308                         }
309                 }
310
311                 for _, falseStmt := range stmt.body.falseBody {
312                         if err := typeCheckStatement(falseStmt, contractValue, clauseName, env); err != nil {
313                                 return err
314                         }
315                 }
316
317         case *defineStatement:
318                 if stmt.expr != nil && stmt.expr.typ(env) != stmt.variable.Type && !(stmt.variable.Type == hashType && isHashSubtype(stmt.expr.typ(env))) {
319                         return fmt.Errorf("expression in define statement in clause \"%s\" has type \"%s\", must be \"%s\"",
320                                 clauseName, stmt.expr.typ(env), stmt.variable.Type)
321                 }
322
323         case *assignStatement:
324                 if stmt.expr.typ(env) != stmt.variable.Type && !(stmt.variable.Type == hashType && isHashSubtype(stmt.expr.typ(env))) {
325                         return fmt.Errorf("expression in assign statement in clause \"%s\" has type \"%s\", must be \"%s\"",
326                                 clauseName, stmt.expr.typ(env), stmt.variable.Type)
327                 }
328
329         case *verifyStatement:
330                 if t := stmt.expr.typ(env); t != boolType {
331                         return fmt.Errorf("expression in verify statement in clause \"%s\" has type \"%s\", must be Boolean", clauseName, t)
332                 }
333
334         case *lockStatement:
335                 if t := stmt.lockedAmount.typ(env); !(t == intType || t == amountType) {
336                         return fmt.Errorf("lockedAmount expression \"%s\" in lock statement in clause \"%s\" has type \"%s\", must be Integer", stmt.lockedAmount, clauseName, t)
337                 }
338                 if t := stmt.lockedAsset.typ(env); t != assetType {
339                         return fmt.Errorf("lockedAsset expression \"%s\" in lock statement in clause \"%s\" has type \"%s\", must be Asset", stmt.lockedAsset, clauseName, t)
340                 }
341                 if t := stmt.program.typ(env); t != progType {
342                         return fmt.Errorf("program in lock statement in clause \"%s\" has type \"%s\", must be Program", clauseName, t)
343                 }
344
345         case *unlockStatement:
346                 if t := stmt.unlockedAmount.typ(env); !(t == intType || t == amountType) {
347                         return fmt.Errorf("unlockedAmount expression \"%s\" in unlock statement of clause \"%s\" has type \"%s\", must be Integer", stmt.unlockedAmount, clauseName, t)
348                 }
349                 if t := stmt.unlockedAsset.typ(env); t != assetType {
350                         return fmt.Errorf("unlockedAsset expression \"%s\" in unlock statement of clause \"%s\" has type \"%s\", must be Asset", stmt.unlockedAsset, clauseName, t)
351                 }
352                 if stmt.unlockedAmount.String() != contractValue.Amount || stmt.unlockedAsset.String() != contractValue.Asset {
353                         return fmt.Errorf("amount \"%s\" of asset \"%s\" expression in unlock statement of clause \"%s\" must be the contract valueAmount \"%s\" of valueAsset \"%s\"",
354                                 stmt.unlockedAmount.String(), stmt.unlockedAsset.String(), clauseName, contractValue.Amount, contractValue.Asset)
355                 }
356         }
357
358         return nil
359 }