forked from ethereum/remix
-
Notifications
You must be signed in to change notification settings - Fork 0
/
staticAnalysisCommon.ts
1249 lines (1128 loc) · 44.9 KB
/
staticAnalysisCommon.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'use strict'
import { FunctionDefinitionAstNode, ModifierDefinitionAstNode, ParameterListAstNode, ForStatementAstNode,
WhileStatementAstNode, VariableDeclarationAstNode, ContractDefinitionAstNode, InheritanceSpecifierAstNode,
MemberAccessAstNode, BinaryOperationAstNode, FunctionCallAstNode, ExpressionStatementAstNode, UnaryOperationAstNode,
IdentifierAstNode, IndexAccessAstNode, BlockAstNode, AssignmentAstNode, InlineAssemblyAstNode, IfStatementAstNode, CompiledContractObj, ABIParameter, CompilationResult, CompiledContract } from "types"
import { util } from 'remix-lib'
type SpecialObjDetail = {
obj: string
member: string
type: string
}
const nodeTypes: Record<string, string> = {
SOURCEUNIT: 'SourceUnit',
PRAGMADIRECTIVE: 'PragmaDirective',
IMPORTDIRECTIVE: 'ImportDirective',
CONTRACTDEFINITION: 'ContractDefinition',
INHERITANCESPECIFIER: 'InheritanceSpecifier',
USINGFORDIRECTIVE: 'UsingForDirective',
STRUCTDEFINITION: 'StructDefinition',
ENUMDEFINITION: 'EnumDefinition',
ENUMVALUE: 'EnumValue',
PARAMETERLIST: 'ParameterList',
OVERRIDESPECIFIER: 'OverrideSpecifier',
FUNCTIONDEFINITION: 'FunctionDefinition',
VARIABLEDECLARATION: 'VariableDeclaration',
MODIFIERDEFINITION: 'ModifierDefinition',
MODIFIERINVOCATION: 'ModifierInvocation',
EVENTDEFINITION: 'EventDefinition',
ELEMENTARYTYPENAME: 'ElementaryTypeName',
USERDEFINEDTYPENAME: 'UserDefinedTypeName',
FUNCTIONTYPENAME: 'FunctionTypeName',
MAPPING: 'Mapping',
ARRAYTYPENAME: 'ArrayTypeName',
INLINEASSEMBLY: 'InlineAssembly',
BLOCK: 'Block',
PLACEHOLDERSTATEMENT: 'PlaceholderStatement',
IFSTATEMENT: 'IfStatement',
TRYCATCHCLAUSE: 'TryCatchClause',
TRYSTATEMENT: 'TryStatement',
WHILESTATEMENT: 'WhileStatement',
DOWHILESTATEMENT: 'DoWhileStatement',
FORSTATEMENT: 'ForStatement',
CONTINUE: 'Continue',
BREAK: 'Break',
RETURN: 'Return',
THROW: 'Throw',
EMITSTATEMENT: 'EmitStatement',
VARIABLEDECLARATIONSTATEMENT: 'VariableDeclarationStatement',
EXPRESSIONSTATEMENT: 'ExpressionStatement',
CONDITIONAL: 'Conditional',
ASSIGNMENT: 'Assignment',
TUPLEEXPRESSION: 'TupleExpression',
UNARYOPERATION: 'UnaryOperation',
BINARYOPERATION: 'BinaryOperation',
FUNCTIONCALL: 'FunctionCall',
FUNCTIONCALLOPTIONS: 'FunctionCallOptions',
NEWEXPRESSION: 'NewExpression',
MEMBERACCESS: 'MemberAccess',
INDEXACCESS: 'IndexAccess',
INDEXRANGEACCESS: 'IndexRangeAccess',
ELEMENTARYTYPENAMEEXPRESSION: 'ElementaryTypeNameExpression',
LITERAL: 'Literal',
IDENTIFIER: 'Identifier',
STRUCTUREDDOCUMENTATION: 'StructuredDocumentation'
}
const basicTypes: Record<string, string> = {
UINT: 'uint256',
BOOL: 'bool',
ADDRESS: 'address',
PAYABLE_ADDRESS: 'address payable',
BYTES32: 'bytes32',
STRING_MEM: 'string memory',
BYTES_MEM: 'bytes memory',
BYTES4: 'bytes4'
}
const basicRegex: Record<string, string> = {
CONTRACTTYPE: '^contract ',
FUNCTIONTYPE: '^function \\(',
EXTERNALFUNCTIONTYPE: '^function \\(.*\\).* external',
CONSTANTFUNCTIONTYPE: '^function \\(.*\\).* (view|pure)',
REFTYPE: '(storage)|(mapping\\()|(\\[\\])',
FUNCTIONSIGNATURE: '^function \\(([^\\(]*)\\)',
LIBRARYTYPE: '^type\\(library (.*)\\)'
}
const basicFunctionTypes: Record<string, string> = {
SEND: buildFunctionSignature([basicTypes.UINT], [basicTypes.BOOL], false),
'CALL-0.4': buildFunctionSignature([], [basicTypes.BOOL], true),
CALL: buildFunctionSignature([basicTypes.BYTES_MEM], [basicTypes.BOOL, basicTypes.BYTES_MEM], true),
'DELEGATECALL-0.4': buildFunctionSignature([], [basicTypes.BOOL], false),
DELEGATECALL: buildFunctionSignature([basicTypes.BYTES_MEM], [basicTypes.BOOL, basicTypes.BYTES_MEM], false),
TRANSFER: buildFunctionSignature([basicTypes.UINT], [], false)
}
const builtinFunctions: Record<string, boolean> = {
'keccak256()': true,
'sha3()': true,
'sha256()': true,
'ripemd160()': true,
'ecrecover(bytes32,uint8,bytes32,bytes32)': true,
'addmod(uint256,uint256,uint256)': true,
'mulmod(uint256,uint256,uint256)': true,
'selfdestruct(address)': true,
'selfdestruct(address payable)': true,
'revert()': true,
'revert(string memory)': true,
'assert(bool)': true,
'require(bool)': true,
'require(bool,string memory)': true,
'gasleft()': true,
'blockhash(uint256)': true,
'address(address)': true
}
const lowLevelCallTypes: Record<string, Record<string, string>> = {
'CALL-0.4': { ident: 'call', type: basicFunctionTypes['CALL-0.4'] },
CALL: { ident: 'call', type: basicFunctionTypes.CALL },
CALLCODE: { ident: 'callcode', type: basicFunctionTypes['CALL-0.4'] },
'DELEGATECALL-0.4': { ident: 'delegatecall', type: basicFunctionTypes['DELEGATECALL-0.4'] },
DELEGATECALL: { ident: 'delegatecall', type: basicFunctionTypes.DELEGATECALL },
SEND: { ident: 'send', type: basicFunctionTypes.SEND },
TRANSFER: { ident: 'transfer', type: basicFunctionTypes.TRANSFER }
}
const specialVariables: Record<string, SpecialObjDetail> = {
BLOCKTIMESTAMP: { obj: 'block', member: 'timestamp', type: basicTypes.UINT },
BLOCKHASH: {
obj: 'block',
member: 'blockhash',
type: buildFunctionSignature([basicTypes.UINT], [basicTypes.BYTES32], false, 'view')
}
}
const abiNamespace: Record<string, SpecialObjDetail> = {
ENCODE: {
obj: 'abi',
member: 'encode',
type: buildFunctionSignature([], [basicTypes.BYTES_MEM], false, 'pure')
},
ENCODEPACKED: {
obj: 'abi',
member: 'encodePacked',
type: buildFunctionSignature([], [basicTypes.BYTES_MEM], false, 'pure')
},
ENCODE_SELECT: {
obj: 'abi',
member: 'encodeWithSelector',
type: buildFunctionSignature([basicTypes.BYTES4], [basicTypes.BYTES_MEM], false, 'pure')
},
ENCODE_SIG: {
obj: 'abi',
member: 'encodeWithSignature',
type: buildFunctionSignature([basicTypes.STRING_MEM], [basicTypes.BYTES_MEM], false, 'pure')
}
}
// #################### Trivial Getters
function getType (node: any): string {
return node.typeDescriptions.typeString
}
// #################### Complex Getters
/**
* Returns the type parameter of function call AST nodes. Throws if not a function call node
* @func {ASTNode} Function call node
* @return {string} type of function call
*/
function getFunctionCallType (func: FunctionCallAstNode): string {
return getType(func.expression)
}
/**
* Get the variable name written to by a effect node, except for inline assembly because there is no information to find out where we write to. Trows if not a effect node or is inlineassmbly.
* Example: x = 10; => x
* @effectNode {ASTNode} Assignmnet node
* @return {string} variable name written to
*/
function getEffectedVariableName (effectNode: AssignmentAstNode | UnaryOperationAstNode): string {
if (!isEffect(effectNode)) throw new Error('staticAnalysisCommon.js: not an effect Node')
if(effectNode.nodeType === 'Assignment' || effectNode.nodeType === 'UnaryOperation') {
const IdentNode: IdentifierAstNode = findFirstSubNodeLTR(effectNode, exactMatch(nodeTypes.IDENTIFIER))
return IdentNode.name
} else throw new Error('staticAnalysisCommon.js: wrong node type')
}
/**
* Returns the identifier of a local call, Throws on wrong node.
* Example: f(103) => f
* @localCallNode {ASTNode} Function call node
* @return {string} name of the function called
*/
function getLocalCallName (localCallNode: FunctionCallAstNode): string {
if (!isLocalCall(localCallNode) && !isAbiNamespaceCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not a local call Node')
return localCallNode.expression.name
}
/**
* Returns the identifier of a this local call, Throws on wrong node.
* Example: this.f(103) => f
* @localCallNode {ASTNode} Function call node
* @return {string} name of the function called
*/
function getThisLocalCallName (thisLocalCallNode: FunctionCallAstNode): string {
if (!isThisLocalCall(thisLocalCallNode.expression)) throw new Error('staticAnalysisCommon.js: not a this local call Node')
return thisLocalCallNode.expression.memberName
}
/**
* Returns the identifier of a super local call, Throws on wrong node.
* Example: super.f(103) => f
* @localCallNode {ASTNode} Function call node
* @return {string} name of the function called
*/
function getSuperLocalCallName (superLocalCallNode: FunctionCallAstNode): string {
if (!isSuperLocalCall(superLocalCallNode.expression)) throw new Error('staticAnalysisCommon.js: not a super local call Node')
return superLocalCallNode.expression.memberName
}
/**
* Returns the contract type of a external direct call, Throws on wrong node.
* Example:
* foo x = foo(0xdeadbeef...);
* x.f(103) => foo
* @extDirectCall {ASTNode} Function call node
* @return {string} name of the contract the function is defined in
*/
function getExternalDirectCallContractName (extDirectCall: FunctionCallAstNode): string {
if (!isExternalDirectCall(extDirectCall)) throw new Error('staticAnalysisCommon.js: not an external direct call Node')
return extDirectCall.expression.expression.typeDescriptions.typeString.replace(new RegExp(basicRegex.CONTRACTTYPE), '')
}
/**
* Returns the name of the contract of a this local call (current contract), Throws on wrong node.
* Example:
* Contract foo {
* ...
* this.f(103) => foo
* ...
* @thisLocalCall {ASTNode} Function call node
* @return {string} name of the contract the function is defined in
*/
function getThisLocalCallContractName (thisLocalCall: FunctionCallAstNode): string {
if (!isThisLocalCall(thisLocalCall.expression)) throw new Error('staticAnalysisCommon.js: not a this local call Node')
return thisLocalCall.expression.expression.typeDescriptions.typeString.replace(new RegExp(basicRegex.CONTRACTTYPE), '')
}
/**
* Returns the function identifier of a external direct call, Throws on wrong node.
* Example:
* foo x = foo(0xdeadbeef...);
* x.f(103) => f
* @extDirectCall {ASTNode} Function call node
* @return {string} name of the function called
*/
function getExternalDirectCallMemberName (extDirectCall: FunctionCallAstNode): string {
if (!isExternalDirectCall(extDirectCall)) throw new Error('staticAnalysisCommon.js: not an external direct call Node')
return extDirectCall.expression.memberName
}
/**
* Returns the name of a contract, Throws on wrong node.
* Example:
* Contract foo { => foo
* @contract {ASTNode} Contract Definition node
* @return {string} name of a contract defined
*/
function getContractName (contract: ContractDefinitionAstNode): string {
if (!nodeType(contract, exactMatch(nodeTypes.CONTRACTDEFINITION))) throw new Error('staticAnalysisCommon.js: not a ContractDefinition Node')
return contract.name
}
/**
* Returns the name of a function definition, Throws on wrong node.
* Example:
* func foo(uint bla) { => foo
* @funcDef {ASTNode} Function Definition node
* @return {string} name of a function defined
*/
function getFunctionDefinitionName (funcDef: FunctionDefinitionAstNode): string {
if (!nodeType(funcDef, exactMatch(nodeTypes.FUNCTIONDEFINITION))) throw new Error('staticAnalysisCommon.js: not a FunctionDefinition Node')
return funcDef.name
}
/**
* Returns the identifier of an inheritance specifier, Throws on wrong node.
* Example:
* contract KingOfTheEtherThrone is b { => b
* @func {ASTNode} Inheritance specifier
* @return {string} name of contract inherited from
*/
function getInheritsFromName (inheritsNode: InheritanceSpecifierAstNode): string {
if (!nodeType(inheritsNode, exactMatch(nodeTypes.INHERITANCESPECIFIER))) throw new Error('staticAnalysisCommon.js: not an InheritanceSpecifier Node')
return inheritsNode.baseName.name
}
/**
* Returns the identifier of a variable definition, Throws on wrong node.
* Example:
* var x = 10; => x
* @varDeclNode {ASTNode} Variable declaration node
* @return {string} variable name
*/
function getDeclaredVariableName (varDeclNode: VariableDeclarationAstNode): string {
if (!nodeType(varDeclNode, exactMatch(nodeTypes.VARIABLEDECLARATION))) throw new Error('staticAnalysisCommon.js: not a VariableDeclaration Node')
return varDeclNode.name
}
/**
* Returns the type of a variable definition, Throws on wrong node.
* Example:
* var x = 10; => x
* @varDeclNode {ASTNode} Variable declaration node
* @return {string} variable type
*/
function getDeclaredVariableType (varDeclNode: VariableDeclarationAstNode): string {
return varDeclNode.typeName.name
}
/**
* Returns state variable declaration nodes for a contract, Throws on wrong node.
* Example:
* contract foo {
* ...
* var y = true;
* var x = 10; => [y,x]
* @contractNode {ASTNode} Contract Definition node
* @return {list variable declaration} state variable node list
*/
function getStateVariableDeclarationsFromContractNode (contractNode: ContractDefinitionAstNode): VariableDeclarationAstNode[] {
return contractNode.nodes.filter(el => el.nodeType === "VariableDeclaration")
}
/**
* Returns parameter node for a function or modifier definition, Throws on wrong node.
* Example:
* function bar(uint a, uint b) => uint a, uint b
* @funcNode {ASTNode} Contract Definition node
* @return {parameterlist node} parameterlist node
*/
function getFunctionOrModifierDefinitionParameterPart (funcNode: FunctionDefinitionAstNode | ModifierDefinitionAstNode): ParameterListAstNode {
if (!nodeTypeIn(funcNode, [exactMatch(nodeTypes.FUNCTIONDEFINITION), exactMatch(nodeTypes.MODIFIERDEFINITION)])) throw new Error('staticAnalysisCommon.js: not a FunctionDefinition or ModifierDefinition Node')
return funcNode.parameters
}
/**
* Returns return parameter node for a function or modifier definition, Throws on wrong node.
* Example:
* function bar(uint a, uint b) returns (bool a, bool b) => bool a, bool b
* @funcNode {ASTNode} Contract Definition node
* @return {parameterlist node} parameterlist node
*/
function getFunctionDefinitionReturnParameterPart (funcNode: FunctionDefinitionAstNode): ParameterListAstNode {
return funcNode.returnParameters
}
/**
* Extracts the parameter types for a function type signature
* Example:
* function(uint a, uint b) returns (bool) => uint a, uint b
* @func {ASTNode} function call node
* @return {string} parameter signature
*/
function getFunctionCallTypeParameterType (func: FunctionCallAstNode): string | undefined {
const type: string = getFunctionCallType(func)
if (type.startsWith('function (')) {
let paramTypes: string = ''
let openPar: number = 1
for (let x = 10; x < type.length; x++) {
const c: string = type.charAt(x)
if (c === '(') openPar++
else if (c === ')') openPar--
if (openPar === 0) return paramTypes
paramTypes += c
}
} else {
throw new Error('staticAnalysisCommon.js: cannot extract parameter types from function call')
}
}
/**
* Returns the name of the library called, Throws on wrong node.
* Example:
* library set{...}
* contract foo {
* ...
* function () { set.union() => set}
* @funcCall {ASTNode} function call node
* @return {string} name of the lib defined
*/
function getLibraryCallContractName (node: FunctionCallAstNode): string | undefined {
if (!isLibraryCall(node.expression)) throw new Error('staticAnalysisCommon.js: not a library call Node')
const types: RegExpExecArray | null = new RegExp(basicRegex.LIBRARYTYPE).exec(node.expression.expression.typeDescriptions.typeString)
if(types)
return types[1]
}
/**
* Returns the name of the function of a library call, Throws on wrong node.
* Example:
* library set{...}
* contract foo {
* ...
* function () { set.union() => uinion}
* @func {ASTNode} function call node
* @return {string} name of function called on the library
*/
function getLibraryCallMemberName (funcCall: FunctionCallAstNode): string {
if (!isLibraryCall(funcCall.expression)) throw new Error('staticAnalysisCommon.js: not a library call Node')
return funcCall.expression.memberName
}
/**
* Returns full qualified name for a function call, Throws on wrong node.
* Example:
* contract foo {
* ...
* function bar(uint b) { }
* function baz() {
* bar(10) => foo.bar(uint)
* @func {ASTNode} function call node
* @func {ASTNode} contract defintion
* @return {string} full qualified identifier for the function call
*/
function getFullQualifiedFunctionCallIdent (contract: ContractDefinitionAstNode, func: FunctionCallAstNode): string {
if (isLocalCall(func)) return getContractName(contract) + '.' + getLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
else if (isThisLocalCall(func.expression)) return getThisLocalCallContractName(func) + '.' + getThisLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
else if (isSuperLocalCall(func.expression)) return getContractName(contract) + '.' + getSuperLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
else if (isExternalDirectCall(func)) return getExternalDirectCallContractName(func) + '.' + getExternalDirectCallMemberName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
else if (isLibraryCall(func.expression)) return getLibraryCallContractName(func) + '.' + getLibraryCallMemberName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
else throw new Error('staticAnalysisCommon.js: Can not get function name from non function call node')
}
function getFullQuallyfiedFuncDefinitionIdent (contract: ContractDefinitionAstNode, func: FunctionDefinitionAstNode, paramTypes: any[]): string {
return getContractName(contract) + '.' + getFunctionDefinitionName(func) + '(' + util.concatWithSeperator(paramTypes, ',') + ')'
}
function getUnAssignedTopLevelBinOps (subScope: BlockAstNode | IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): ExpressionStatementAstNode[] {
let result: ExpressionStatementAstNode[] = []
if(subScope && subScope.nodeType === 'Block')
result = subScope.statements.filter(isBinaryOpInExpression)
// for 'without braces' loops
else if (subScope && subScope.nodeType && isSubScopeStatement(subScope)) {
if (subScope.nodeType === 'IfStatement'){
if((subScope.trueBody && subScope.trueBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(subScope.trueBody)))
result.push(subScope.trueBody)
if (subScope.falseBody && subScope.falseBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(subScope.falseBody))
result.push(subScope.falseBody)
}
else {
if(subScope.body && subScope.body.nodeType === "ExpressionStatement" && isBinaryOpInExpression(subScope.body))
result.push(subScope.body)
}
}
return result
}
// #################### Trivial Node Identification
function isStatement (node: any): boolean {
return nodeType(node, 'Statement$') || node.nodeType === "Block" || node.nodeType === "Return"
}
// #################### Complex Node Identification
/**
* True if function defintion has function body
* @funcNode {ASTNode} function defintion node
* @return {bool}
*/
function hasFunctionBody (funcNode: FunctionDefinitionAstNode): boolean {
return funcNode.body !== null
}
/**
* True if node is a delete instruction of a dynamic array
* @node {ASTNode} node to check for
* @return {bool}
*/
function isDeleteOfDynamicArray (node: UnaryOperationAstNode): boolean {
return isDeleteUnaryOperation(node) && isDynamicArrayAccess(node.subExpression)
}
/**
* True if node is node is a ref to a dynamic array
* @node {ASTNode} node to check for
* @return {bool}
*/
function isDynamicArrayAccess (node: IdentifierAstNode): boolean {
return getType(node).endsWith('[] storage ref') || typeDescription(node, 'bytes storage ref') || typeDescription(node, 'string storage ref')
}
/**
* True if node accesses 'length' member of dynamic array
* @node {ASTNode} node to check for
* @return {bool}
*/
function isDynamicArrayLengthAccess (node: MemberAccessAstNode): boolean {
return (node.memberName === 'length') && // accessing 'length' member
node.expression['typeDescriptions']['typeString'].indexOf('[]') !== -1 // member is accessed from dynamic array, notice [] without any number
}
/**
* True if node is a delete instruction for an element from a dynamic array
* @node {ASTNode} node to check for
* @return {bool}
*/
function isDeleteFromDynamicArray (node: UnaryOperationAstNode): boolean {
return isDeleteUnaryOperation(node) && node.subExpression.nodeType === 'IndexAccess'
}
/**
* True if node is the access of a mapping index
* @node {ASTNode} node to check for
* @return {bool}
*/
function isMappingIndexAccess (node: IndexAccessAstNode): boolean {
return node.typeDescriptions.typeString.startsWith('mapping')
}
/**
* True if call to code within the current contracts context including (delegate) library call
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLocalCallGraphRelevantNode (node: FunctionCallAstNode): boolean {
return ((isLocalCall(node) || isSuperLocalCall(node.expression) || isLibraryCall(node.expression)) && !isBuiltinFunctionCall(node))
}
/**
* True if is builtin function like assert, sha3, erecover, ...
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isBuiltinFunctionCall (node: FunctionCallAstNode): boolean {
return (node.nodeType === 'FunctionCall' && isLocalCall(node) && builtinFunctions[getLocalCallName(node) + '(' + getFunctionCallTypeParameterType(node) + ')'] === true) || isAbiNamespaceCall(node)
}
/**
* True if is builtin function like assert, sha3, erecover, ...
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isAbiNamespaceCall (node: FunctionCallAstNode): boolean {
return Object.keys(abiNamespace).some((key) => abiNamespace.hasOwnProperty(key) && node.expression && isSpecialVariableAccess(node.expression, abiNamespace[key]))
}
/**
* True if node is a call to selfdestruct
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isSelfdestructCall (node: FunctionCallAstNode): boolean {
return isBuiltinFunctionCall(node) && getLocalCallName(node) === 'selfdestruct'
}
/**
* True if node is a call to builtin assert(bool)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isAssertCall (node: FunctionCallAstNode): boolean {
return isBuiltinFunctionCall(node) && getLocalCallName(node) === 'assert'
}
/**
* True if node is a call to builtin require(bool)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isRequireCall (node: FunctionCallAstNode): boolean {
return isBuiltinFunctionCall(node) && getLocalCallName(node) === 'require'
}
/**
* True if is storage variable declaration
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isStorageVariableDeclaration (node: VariableDeclarationAstNode): boolean {
return node.storageLocation === 'storage' && new RegExp(basicRegex.REFTYPE).test(node.typeDescriptions.typeIdentifier)
}
/**
* True if is interaction with external contract (change in context, no delegate calls) (send, call of other contracts)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isInteraction (node: FunctionCallAstNode): boolean {
return isLLCall(node.expression) || isLLSend(node.expression) || isExternalDirectCall(node) || isTransfer(node.expression) ||
isLLCall04(node.expression) || isLLSend04(node.expression) ||
// to cover case of address.call.value.gas , See: inheritance.sol
(node.expression && node.expression.expression && isLLCall(node.expression.expression)) ||
(node.expression && node.expression.expression && isLLCall04(node.expression.expression))
}
/**
* True if node changes state of a variable or is inline assembly (does not include check if it is a global state change, on a state variable)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isEffect (node: AssignmentAstNode | UnaryOperationAstNode | InlineAssemblyAstNode): boolean {
return node.nodeType === "Assignment" ||
(node.nodeType === "UnaryOperation" && (isPlusPlusUnaryOperation(node) || isMinusMinusUnaryOperation(node))) ||
node.nodeType === "InlineAssembly"
}
/**
* True if node changes state of a variable or is inline assembly (Checks if variable is a state variable via provided list)
* @node {ASTNode} some AstNode
* @node {list Variable declaration} state variable declaration currently in scope
* @return {bool}
*/
function isWriteOnStateVariable (effectNode: AssignmentAstNode | InlineAssemblyAstNode | UnaryOperationAstNode, stateVariables: VariableDeclarationAstNode[]): boolean {
return effectNode.nodeType === "InlineAssembly" || (isEffect(effectNode) && isStateVariable(getEffectedVariableName(effectNode), stateVariables))
}
/**
* True if there is a variable with name, name in stateVariables
* @node {ASTNode} some AstNode
* @node {list Variable declaration} state variable declaration currently in scope
* @return {bool}
*/
function isStateVariable (name: string, stateVariables: VariableDeclarationAstNode[]): boolean {
return stateVariables.some((item: VariableDeclarationAstNode) => item.stateVariable && name === getDeclaredVariableName(item))
}
/**
* True if is function defintion that is flaged as constant
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isConstantFunction (node: FunctionDefinitionAstNode): boolean {
return node.stateMutability === 'view' || node.stateMutability === 'pure'
}
/**
* True if variable decalaration is converted into a getter method
* @node {ASTNode} variable declaration AstNode
* @return {bool}
*/
function isVariableTurnedIntoGetter (varDeclNode: VariableDeclarationAstNode): boolean {
return varDeclNode.stateVariable && varDeclNode.visibility === 'public';
}
/**
* True if is function defintion has payable modifier
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isPayableFunction (node: FunctionDefinitionAstNode): boolean {
return node.stateMutability === 'payable'
}
/**
* True if is constructor
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isConstructor (node: FunctionDefinitionAstNode): boolean {
return node.kind === "constructor"
}
/**
* True if node is integer division that truncates (not only int literals since those yield a rational value)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isIntDivision (node: BinaryOperationAstNode): boolean {
return operator(node, exactMatch(util.escapeRegExp('/'))) && typeDescription(node.rightExpression, util.escapeRegExp('int'))
}
/**
* True if is block / SubScope has top level binops (e.g. that are not assigned to anything, most of the time confused compare instead of assign)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isSubScopeWithTopLevelUnAssignedBinOp (node: BlockAstNode | IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): boolean | undefined {
if(node.nodeType === 'Block')
return node.statements.some(isBinaryOpInExpression)
// for 'without braces' loops
else if (node && node.nodeType && isSubScopeStatement(node)) {
if (node.nodeType === 'IfStatement')
return (node.trueBody && node.trueBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(node.trueBody)) ||
(node.falseBody && node.falseBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(node.falseBody))
else
return node.body && node.body.nodeType === "ExpressionStatement" && isBinaryOpInExpression(node.body)
}
}
function isSubScopeStatement (node: IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): boolean {
if(node.nodeType === 'IfStatement')
return (node.trueBody && node.trueBody.nodeType && !nodeType(node.trueBody, exactMatch(nodeTypes.BLOCK))) ||
(node.falseBody && node.falseBody.nodeType && !nodeType(node.falseBody, exactMatch(nodeTypes.BLOCK)))
else
return node.body && node.body.nodeType && !nodeType(node.body, exactMatch(nodeTypes.BLOCK))
}
/**
* True if binary operation inside of expression statement
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isBinaryOpInExpression (node: ExpressionStatementAstNode): boolean {
return node.nodeType === "ExpressionStatement" && node.expression.nodeType === "BinaryOperation"
}
/**
* True if unary increment operation
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isPlusPlusUnaryOperation (node: UnaryOperationAstNode): boolean {
return node.operator === '++'
}
/**
* True if unary delete operation
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isDeleteUnaryOperation (node: UnaryOperationAstNode): boolean {
return node.operator === 'delete'
}
/**
* True if unary decrement operation
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isMinusMinusUnaryOperation (node: UnaryOperationAstNode): boolean {
return node.operator === '--'
}
/**
* True if all functions on a contract are implemented
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isFullyImplementedContract (node: ContractDefinitionAstNode): boolean {
return node.fullyImplemented === true
}
/**
* True if it is a library contract defintion
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLibrary (node: ContractDefinitionAstNode): boolean {
return node.contractKind === 'library'
}
/**
* True if it is a local call to non const function
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isCallToNonConstLocalFunction (node: FunctionCallAstNode): boolean {
return isLocalCall(node) && !expressionTypeDescription(node, basicRegex.CONSTANTFUNCTIONTYPE)
}
/**
* True if it is a call to a library
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLibraryCall (node: MemberAccessAstNode): boolean {
return isMemberAccess(node, basicRegex.FUNCTIONTYPE, undefined, basicRegex.LIBRARYTYPE, undefined)
}
/**
* True if it is an external call via defined interface (not low level call)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isExternalDirectCall (node: FunctionCallAstNode): boolean {
return isMemberAccess(node.expression, basicRegex.EXTERNALFUNCTIONTYPE, undefined, basicRegex.CONTRACTTYPE, undefined) && !isThisLocalCall(node.expression) && !isSuperLocalCall(node.expression)
}
/**
* True if access to block.timestamp via now alias
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isNowAccess (node: IdentifierAstNode): boolean {
return node.name === "now" && typeDescription(node, exactMatch(basicTypes.UINT))
}
/**
* True if access to block.timestamp via now alias
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isTxOriginAccess (node: MemberAccessAstNode): boolean {
return isMemberAccess(node, 'address', 'tx', 'tx', 'origin')
}
/**
* True if access to block.timestamp
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isBlockTimestampAccess (node: MemberAccessAstNode): boolean {
return isSpecialVariableAccess(node, specialVariables.BLOCKTIMESTAMP)
}
/**
* True if access to block.blockhash
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isBlockBlockHashAccess (node: FunctionCallAstNode): boolean {
return ( isBuiltinFunctionCall(node) && getLocalCallName(node) === 'blockhash' ) ||
isSpecialVariableAccess(node.expression, specialVariables.BLOCKHASH)
}
/**
* True if call to local function via this keyword
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isThisLocalCall (node: MemberAccessAstNode): boolean {
return isMemberAccess(node, basicRegex.FUNCTIONTYPE, exactMatch('this'), basicRegex.CONTRACTTYPE, undefined)
}
/**
* True if access to local function via super keyword
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isSuperLocalCall (node: MemberAccessAstNode): boolean {
return isMemberAccess(node, basicRegex.FUNCTIONTYPE, exactMatch('super'), basicRegex.CONTRACTTYPE, undefined)
}
/**
* True if call to local function
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLocalCall (node: FunctionCallAstNode): boolean {
return node.nodeType === 'FunctionCall' && node.kind === 'functionCall' &&
node.expression.nodeType === 'Identifier' && expressionTypeDescription(node, basicRegex.FUNCTIONTYPE) &&
!expressionTypeDescription(node, basicRegex.EXTERNALFUNCTIONTYPE)
}
/**
* True if low level call (send, call, delegatecall, callcode)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLowLevelCall (node: MemberAccessAstNode): boolean {
return isLLCall(node) ||
isLLDelegatecall(node) ||
isLLSend(node) ||
isLLSend04(node) ||
isLLCallcode(node) ||
isLLCall04(node) ||
isLLDelegatecall04(node)
}
/**
* True if low level send (solidity < 0.5)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLSend04 (node: MemberAccessAstNode): boolean {
return isMemberAccess(node,
exactMatch(util.escapeRegExp(lowLevelCallTypes.SEND.type)),
undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.SEND.ident))
}
/**
* True if low level send (solidity >= 0.5)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLSend (node: MemberAccessAstNode): boolean {
return isMemberAccess(node,
exactMatch(util.escapeRegExp(lowLevelCallTypes.SEND.type)),
undefined, exactMatch(basicTypes.PAYABLE_ADDRESS), exactMatch(lowLevelCallTypes.SEND.ident))
}
/**
* True if low level call
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLCall (node: MemberAccessAstNode): boolean {
return isMemberAccess(node,
exactMatch(util.escapeRegExp(lowLevelCallTypes.CALL.type)),
undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALL.ident)) ||
isMemberAccess(node,
exactMatch(util.escapeRegExp(lowLevelCallTypes.CALL.type)),
undefined, exactMatch(basicTypes.PAYABLE_ADDRESS), exactMatch(lowLevelCallTypes.CALL.ident))
}
/**
* True if low level payable call (solidity < 0.5)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLCall04 (node: MemberAccessAstNode): boolean {
return isMemberAccess(node,
exactMatch(util.escapeRegExp(lowLevelCallTypes['CALL-0.4'].type)),
undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes['CALL-0.4'].ident))
}
/**
* True if low level callcode
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLCallcode (node: MemberAccessAstNode): boolean {
return isMemberAccess(node,
exactMatch(util.escapeRegExp(lowLevelCallTypes.CALLCODE.type)),
undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALLCODE.ident))
}
/**
* True if low level delegatecall
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLDelegatecall (node: MemberAccessAstNode): boolean {
return isMemberAccess(node,
exactMatch(util.escapeRegExp(lowLevelCallTypes.DELEGATECALL.type)),
undefined, matches(basicTypes.PAYABLE_ADDRESS, basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.DELEGATECALL.ident))
}
/**
* True if low level delegatecall (solidity < 0.5)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLDelegatecall04 (node: MemberAccessAstNode): boolean {
return isMemberAccess(node,
exactMatch(util.escapeRegExp(lowLevelCallTypes['DELEGATECALL-0.4'].type)),
undefined, matches(basicTypes.PAYABLE_ADDRESS, basicTypes.ADDRESS), exactMatch(lowLevelCallTypes['DELEGATECALL-0.4'].ident))
}
/**
* True if transfer call
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isTransfer (node: MemberAccessAstNode): boolean {
return isMemberAccess(node,
exactMatch(util.escapeRegExp(lowLevelCallTypes.TRANSFER.type)),
undefined, matches(basicTypes.ADDRESS, basicTypes.PAYABLE_ADDRESS), exactMatch(lowLevelCallTypes.TRANSFER.ident))
}
function isStringToBytesConversion (node: FunctionCallAstNode): boolean {
return isExplicitCast(node, util.escapeRegExp('string *'), util.escapeRegExp('bytes'))
}
function isExplicitCast (node: FunctionCallAstNode, castFromType: string, castToType: string): boolean {
return node.kind === "typeConversion" &&
nodeType(node.expression, exactMatch(nodeTypes.ELEMENTARYTYPENAMEEXPRESSION)) && node.expression.typeName === castToType &&
nodeType(node.arguments[0], exactMatch(nodeTypes.IDENTIFIER)) && typeDescription(node.arguments[0], castFromType)
}
function isBytesLengthCheck (node: MemberAccessAstNode): boolean {
return isMemberAccess(node, exactMatch(util.escapeRegExp(basicTypes.UINT)), undefined, util.escapeRegExp('bytes *'), 'length')
}
/**
* True if it is a loop
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLoop (node) {
return nodeType(node, exactMatch(nodeTypes.FORSTATEMENT)) ||
nodeType(node, exactMatch(nodeTypes.WHILESTATEMENT)) ||
nodeType(node, exactMatch(nodeTypes.DOWHILESTATEMENT))
}
// #################### Complex Node Identification - Private
function isMemberAccess (node: MemberAccessAstNode, retType: string, accessor: string| undefined, accessorType: string, memberName: string | undefined): boolean {
if(node && nodeType(node, exactMatch('MemberAccess'))) {
const nodeTypeDef: boolean = typeDescription(node, retType)
const nodeMemName: boolean = memName(node, memberName)
const nodeExpMemName: boolean = memName(node.expression, accessor)
const nodeExpTypeDef: boolean = expressionTypeDescription(node, accessorType)
return nodeTypeDef && nodeMemName && nodeExpTypeDef && nodeExpMemName
} else return false
}
function isSpecialVariableAccess (node: MemberAccessAstNode, varType: SpecialObjDetail): boolean {
return isMemberAccess(node, exactMatch(util.escapeRegExp(varType.type)), varType.obj, varType.obj, varType.member)
}
// #################### Node Identification Primitives