From 740b04acd8f5fc2dd4a9b6fefbe8b5687ccdbeac Mon Sep 17 00:00:00 2001 From: Esorat Date: Mon, 25 Nov 2024 12:16:58 +0700 Subject: [PATCH 1/5] WIP: Add extra effects to nodes. Without put in to tact/utils.ts --- src/internals/ir/callGraph.ts | 176 +++++++- test/all/sample-jetton.expected.callgraph.dot | 64 +-- .../all/sample-jetton.expected.callgraph.json | 378 ++++++++++++------ test/all/sample-jetton.expected.callgraph.mmd | 64 +-- 4 files changed, 482 insertions(+), 200 deletions(-) diff --git a/src/internals/ir/callGraph.ts b/src/internals/ir/callGraph.ts index 4f178a0d..ee522a20 100644 --- a/src/internals/ir/callGraph.ts +++ b/src/internals/ir/callGraph.ts @@ -3,7 +3,7 @@ import { TactASTStore } from "./astStore"; import { IdxGenerator } from "./indices"; import { MistiContext } from "../../"; import { Logger } from "../../internals/logger"; -import { findInExpressions, forEachExpression } from "../tact/iterators"; +import { forEachExpression } from "../tact/iterators"; import { isSendCall } from "../tact/util"; import { AstFunctionDef, @@ -16,6 +16,11 @@ import { AstId, AstContractDeclaration, AstNode, + AstFieldAccess, + AstStatement, + AstStatementAssign, + AstStatementAugmentedAssign, + AstStatementExpression, } from "@tact-lang/compiler/dist/grammar/ast"; export type CGNodeId = number & { readonly brand: unique symbol }; @@ -24,10 +29,13 @@ export type CGEdgeId = number & { readonly brand: unique symbol }; /** * Flag constants for CGNode. * - * `FLAG_CALLS_SEND` (0b0001): Indicates that the function represented by this node - * contains a direct or indirect call to a "send" function. + * Each flag represents an effect or property of the function represented by the node. */ -const FLAG_CALLS_SEND = 0b0001; +const FlagCallsSend = 0b0001; +const FlagContractStateRead = 0b0010; +const FlagContractStateWrite = 0b0100; +const FlagBlockchainStateRead = 0b1000; +const FlagRandomness = 0b10000; /** * Represents an edge in the call graph, indicating a call from one function to another. @@ -298,17 +306,20 @@ export class CallGraph { | AstReceiver; const funcNodeId = this.astIdToNodeId.get(func.id); if (funcNodeId !== undefined) { + const funcNode = this.getNode(funcNodeId); + if (!funcNode) continue; + + // Process statements + if ("statements" in func && func.statements) { + for (const stmt of func.statements) { + this.processStatement(stmt, funcNodeId, contractName); + } + } + + // Process expressions (if any) forEachExpression(func, (expr) => { this.processExpression(expr, funcNodeId, contractName); }); - const sendCallFound = - findInExpressions(func, isSendCall) !== null; - if (sendCallFound) { - const funcNode = this.getNode(funcNodeId); - if (funcNode) { - funcNode.setFlag(FLAG_CALLS_SEND); - } - } } } } @@ -316,23 +327,57 @@ export class CallGraph { const func = entry as AstFunctionDef; const funcNodeId = this.astIdToNodeId.get(func.id); if (funcNodeId !== undefined) { + const funcNode = this.getNode(funcNodeId); + if (!funcNode) continue; + if (func.statements) { + for (const stmt of func.statements) { + this.processStatement(stmt, funcNodeId); + } + } forEachExpression(func, (expr) => { this.processExpression(expr, funcNodeId); }); - const sendCallFound = findInExpressions(func, isSendCall) !== null; - if (sendCallFound) { - const funcNode = this.getNode(funcNodeId); - if (funcNode) { - funcNode.setFlag(FLAG_CALLS_SEND); - } - } } } } } + /** + * Processes a single statement, identifying assignments and other statements. + * Also detects effects and sets corresponding flags on the function node. + * @param stmt The statement to process. + * @param callerId The node ID of the calling function. + * @param currentContractName The name of the contract, if applicable. + */ + private processStatement( + stmt: AstStatement, + callerId: CGNodeId, + currentContractName?: string, + ) { + const funcNode = this.getNode(callerId); + if (!funcNode) { + return; + } + if ( + stmt.kind === "statement_assign" || + stmt.kind === "statement_augmentedassign" + ) { + if (isContractStateWrite(stmt)) { + funcNode.setFlag(FlagContractStateWrite); + } + } else if (stmt.kind === "statement_expression") { + const stmtExpr = stmt as AstStatementExpression; + this.processExpression( + stmtExpr.expression, + callerId, + currentContractName, + ); + } + } + /** * Processes a single expression, identifying function or method calls to create edges. + * Also detects effects and sets corresponding flags on the function node. * @param expr The expression to process. * @param callerId The node ID of the calling function. * @param currentContractName The name of the contract, if applicable. @@ -356,6 +401,24 @@ export class CallGraph { ); } } + // Detect and set effects + const funcNode = this.getNode(callerId); + if (!funcNode) { + return; + } + + if (isContractStateRead(expr)) { + funcNode.setFlag(FlagContractStateRead); + } + if (isBlockchainStateRead(expr)) { + funcNode.setFlag(FlagBlockchainStateRead); + } + if (isRandomnessCall(expr)) { + funcNode.setFlag(FlagRandomness); + } + if (isSendCall(expr)) { + funcNode.setFlag(FlagCallsSend); + } } /** @@ -431,3 +494,78 @@ export class CallGraph { export function isSelf(expr: AstExpression): boolean { return expr.kind === "id" && (expr as AstId).text === "self"; } + +/** + * Helper function to determine if an expression is a contract state read. + * @param expr The expression to check. + * @returns True if the expression reads from a state variable; otherwise, false. + */ +function isContractStateRead(expr: AstExpression): boolean { + if (expr.kind === "field_access") { + const fieldAccess = expr as AstFieldAccess; + if (fieldAccess.aggregate.kind === "id") { + const idExpr = fieldAccess.aggregate as AstId; + if (idExpr.text === "self") { + return true; // Accessing a state variable via 'self' + } + } + } + return false; +} + +/** + * Helper function to determine if a statement is a contract state write. + * @param stmt The statement to check. + * @returns True if the statement writes to a state variable; otherwise, false. + */ +function isContractStateWrite( + stmt: AstStatementAssign | AstStatementAugmentedAssign, +): boolean { + const pathExpr = stmt.path; + if (pathExpr.kind === "field_access") { + const fieldAccess = pathExpr as AstFieldAccess; + if (fieldAccess.aggregate.kind === "id") { + const idExpr = fieldAccess.aggregate as AstId; + if (idExpr.text === "self") { + return true; + } + } + } + return false; +} + +/** + * Helper function to determine if an expression is a blockchain state read. + * @param expr The expression to check. + * @returns True if the expression reads blockchain state; otherwise, false. + */ +function isBlockchainStateRead(expr: AstExpression): boolean { + if (expr.kind === "static_call") { + const staticCall = expr as AstStaticCall; + const functionName = staticCall.function?.text; + return functionName === "now" || functionName === "timestamp"; + } else if (expr.kind === "method_call") { + const methodCall = expr as AstMethodCall; + const methodName = methodCall.method?.text; + return methodName === "now" || methodName === "timestamp"; + } + return false; +} + +/** + * Helper function to determine if an expression is a randomness call. + * @param expr The expression to check. + * @returns True if the expression introduces randomness; otherwise, false. + */ +function isRandomnessCall(expr: AstExpression): boolean { + if (expr.kind === "static_call") { + const staticCall = expr as AstStaticCall; + const functionName = staticCall.function?.text; + return functionName === "random"; + } else if (expr.kind === "method_call") { + const methodCall = expr as AstMethodCall; + const methodName = methodCall.method?.text; + return methodName === "random"; + } + return false; +} diff --git a/test/all/sample-jetton.expected.callgraph.dot b/test/all/sample-jetton.expected.callgraph.dot index aace0793..b54580c4 100644 --- a/test/all/sample-jetton.expected.callgraph.dot +++ b/test/all/sample-jetton.expected.callgraph.dot @@ -11,59 +11,75 @@ digraph "CallGraph" { node_9 [label="JettonDefaultWallet::receiver_2832"]; node_10 [label="JettonDefaultWallet::receiver_2876"]; node_11 [label="JettonDefaultWallet::get_wallet_data"]; - node_12 [label="context"]; - node_13 [label="require"]; - node_14 [label="SampleJetton::mint"]; - node_15 [label="ctx::readForwardFee"]; - node_16 [label="min"]; - node_17 [label="ton"]; - node_18 [label="contractAddress"]; - node_19 [label="send"]; + node_12 [label="require"]; + node_13 [label="SampleJetton::mint"]; + node_14 [label="context"]; + node_15 [label="send"]; + node_16 [label="ctx::readForwardFee"]; + node_17 [label="min"]; + node_18 [label="ton"]; + node_19 [label="contractAddress"]; node_20 [label="JettonDefaultWallet::toCell"]; node_21 [label="myBalance"]; node_22 [label="msg::loadUint"]; node_23 [label="msg::loadCoins"]; node_2 -> node_12; - node_2 -> node_13; + node_2 -> node_12; node_2 -> node_13; node_2 -> node_14; + node_2 -> node_12; + node_2 -> node_12; + node_2 -> node_13; node_3 -> node_12; node_3 -> node_13; node_3 -> node_14; + node_3 -> node_12; + node_3 -> node_13; + node_4 -> node_12; + node_4 -> node_14; node_4 -> node_12; - node_4 -> node_13; node_6 -> node_12; - node_6 -> node_13; - node_6 -> node_15; + node_6 -> node_12; + node_6 -> node_12; node_6 -> node_15; - node_6 -> node_13; + node_6 -> node_14; + node_6 -> node_12; + node_6 -> node_16; node_6 -> node_16; + node_6 -> node_12; node_6 -> node_17; - node_6 -> node_13; node_6 -> node_18; + node_6 -> node_12; node_6 -> node_19; + node_6 -> node_15; node_6 -> node_20; node_7 -> node_12; - node_7 -> node_13; - node_7 -> node_18; - node_7 -> node_13; + node_7 -> node_14; + node_7 -> node_12; node_7 -> node_19; + node_7 -> node_12; + node_7 -> node_15; node_7 -> node_20; node_7 -> node_8; + node_7 -> node_16; node_7 -> node_15; - node_7 -> node_19; node_7 -> node_20; node_8 -> node_21; - node_8 -> node_16; + node_8 -> node_17; + node_9 -> node_12; + node_9 -> node_12; + node_9 -> node_12; + node_9 -> node_15; + node_9 -> node_14; + node_9 -> node_12; + node_9 -> node_12; + node_9 -> node_16; node_9 -> node_12; - node_9 -> node_13; - node_9 -> node_13; node_9 -> node_15; - node_9 -> node_13; - node_9 -> node_19; node_9 -> node_20; + node_10 -> node_12; node_10 -> node_22; node_10 -> node_22; node_10 -> node_23; - node_10 -> node_13; + node_10 -> node_12; } diff --git a/test/all/sample-jetton.expected.callgraph.json b/test/all/sample-jetton.expected.callgraph.json index be183287..e9047f10 100644 --- a/test/all/sample-jetton.expected.callgraph.json +++ b/test/all/sample-jetton.expected.callgraph.json @@ -14,7 +14,10 @@ 1, 2, 3, - 4 + 4, + 5, + 6, + 7 ] }, { @@ -22,9 +25,11 @@ "name": "SampleJetton::receiver_1882", "inEdges": [], "outEdges": [ - 5, - 6, - 7 + 8, + 9, + 10, + 11, + 12 ] }, { @@ -32,8 +37,9 @@ "name": "SampleJetton::receiver_1905", "inEdges": [], "outEdges": [ - 8, - 9 + 13, + 14, + 15 ] }, { @@ -47,24 +53,11 @@ "name": "JettonDefaultWallet::receiver_2517", "inEdges": [], "outEdges": [ - 10, - 11, - 12, - 13, - 14, - 15, 16, 17, 18, 19, - 20 - ] - }, - { - "idx": 7, - "name": "JettonDefaultWallet::receiver_2687", - "inEdges": [], - "outEdges": [ + 20, 21, 22, 23, @@ -77,15 +70,33 @@ 30 ] }, + { + "idx": 7, + "name": "JettonDefaultWallet::receiver_2687", + "inEdges": [], + "outEdges": [ + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41 + ] + }, { "idx": 8, "name": "JettonDefaultWallet::msgValue", "inEdges": [ - 27 + 38 ], "outEdges": [ - 31, - 32 + 42, + 43 ] }, { @@ -93,13 +104,17 @@ "name": "JettonDefaultWallet::receiver_2832", "inEdges": [], "outEdges": [ - 33, - 34, - 35, - 36, - 37, - 38, - 39 + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54 ] }, { @@ -107,10 +122,11 @@ "name": "JettonDefaultWallet::receiver_2876", "inEdges": [], "outEdges": [ - 40, - 41, - 42, - 43 + 55, + 56, + 57, + 58, + 59 ] }, { @@ -121,91 +137,107 @@ }, { "idx": 12, - "name": "context", + "name": "require", "inEdges": [ 1, + 2, 5, + 6, 8, - 10, + 11, + 13, + 15, + 16, + 17, + 18, 21, - 33 + 24, + 27, + 31, + 33, + 35, + 44, + 45, + 46, + 49, + 50, + 52, + 55, + 59 ], "outEdges": [] }, { "idx": 13, - "name": "require", + "name": "SampleJetton::mint", "inEdges": [ - 2, 3, - 6, + 7, 9, - 11, - 14, - 17, - 22, - 24, - 34, - 35, - 37, - 43 + 12 ], "outEdges": [] }, { "idx": 14, - "name": "SampleJetton::mint", + "name": "context", "inEdges": [ 4, - 7 + 10, + 14, + 20, + 32, + 48 ], "outEdges": [] }, { "idx": 15, - "name": "ctx::readForwardFee", + "name": "send", "inEdges": [ - 12, - 13, - 28, - 36 + 19, + 29, + 36, + 40, + 47, + 53 ], "outEdges": [] }, { "idx": 16, - "name": "min", + "name": "ctx::readForwardFee", "inEdges": [ - 15, - 32 + 22, + 23, + 39, + 51 ], "outEdges": [] }, { "idx": 17, - "name": "ton", + "name": "min", "inEdges": [ - 16 + 25, + 43 ], "outEdges": [] }, { "idx": 18, - "name": "contractAddress", + "name": "ton", "inEdges": [ - 18, - 23 + 26 ], "outEdges": [] }, { "idx": 19, - "name": "send", + "name": "contractAddress", "inEdges": [ - 19, - 25, - 29, - 38 + 28, + 34 ], "outEdges": [] }, @@ -213,10 +245,10 @@ "idx": 20, "name": "JettonDefaultWallet::toCell", "inEdges": [ - 20, - 26, 30, - 39 + 37, + 41, + 54 ], "outEdges": [] }, @@ -224,7 +256,7 @@ "idx": 21, "name": "myBalance", "inEdges": [ - 31 + 42 ], "outEdges": [] }, @@ -232,8 +264,8 @@ "idx": 22, "name": "msg::loadUint", "inEdges": [ - 40, - 41 + 56, + 57 ], "outEdges": [] }, @@ -241,7 +273,7 @@ "idx": 23, "name": "msg::loadCoins", "inEdges": [ - 42 + 58 ], "outEdges": [] } @@ -255,7 +287,7 @@ { "idx": 2, "src": 2, - "dst": 13 + "dst": 12 }, { "idx": 3, @@ -269,198 +301,278 @@ }, { "idx": 5, - "src": 3, + "src": 2, "dst": 12 }, { "idx": 6, + "src": 2, + "dst": 12 + }, + { + "idx": 7, + "src": 2, + "dst": 13 + }, + { + "idx": 8, + "src": 3, + "dst": 12 + }, + { + "idx": 9, "src": 3, "dst": 13 }, { - "idx": 7, + "idx": 10, "src": 3, "dst": 14 }, { - "idx": 8, + "idx": 11, + "src": 3, + "dst": 12 + }, + { + "idx": 12, + "src": 3, + "dst": 13 + }, + { + "idx": 13, "src": 4, "dst": 12 }, { - "idx": 9, + "idx": 14, "src": 4, - "dst": 13 + "dst": 14 }, { - "idx": 10, + "idx": 15, + "src": 4, + "dst": 12 + }, + { + "idx": 16, "src": 6, "dst": 12 }, { - "idx": 11, + "idx": 17, "src": 6, - "dst": 13 + "dst": 12 }, { - "idx": 12, + "idx": 18, "src": 6, - "dst": 15 + "dst": 12 }, { - "idx": 13, + "idx": 19, "src": 6, "dst": 15 }, { - "idx": 14, + "idx": 20, "src": 6, - "dst": 13 + "dst": 14 }, { - "idx": 15, + "idx": 21, + "src": 6, + "dst": 12 + }, + { + "idx": 22, "src": 6, "dst": 16 }, { - "idx": 16, + "idx": 23, "src": 6, - "dst": 17 + "dst": 16 }, { - "idx": 17, + "idx": 24, "src": 6, - "dst": 13 + "dst": 12 }, { - "idx": 18, + "idx": 25, + "src": 6, + "dst": 17 + }, + { + "idx": 26, "src": 6, "dst": 18 }, { - "idx": 19, + "idx": 27, + "src": 6, + "dst": 12 + }, + { + "idx": 28, "src": 6, "dst": 19 }, { - "idx": 20, + "idx": 29, + "src": 6, + "dst": 15 + }, + { + "idx": 30, "src": 6, "dst": 20 }, { - "idx": 21, + "idx": 31, "src": 7, "dst": 12 }, { - "idx": 22, + "idx": 32, "src": 7, - "dst": 13 + "dst": 14 }, { - "idx": 23, + "idx": 33, "src": 7, - "dst": 18 + "dst": 12 }, { - "idx": 24, + "idx": 34, "src": 7, - "dst": 13 + "dst": 19 }, { - "idx": 25, + "idx": 35, "src": 7, - "dst": 19 + "dst": 12 }, { - "idx": 26, + "idx": 36, + "src": 7, + "dst": 15 + }, + { + "idx": 37, "src": 7, "dst": 20 }, { - "idx": 27, + "idx": 38, "src": 7, "dst": 8 }, { - "idx": 28, + "idx": 39, "src": 7, - "dst": 15 + "dst": 16 }, { - "idx": 29, + "idx": 40, "src": 7, - "dst": 19 + "dst": 15 }, { - "idx": 30, + "idx": 41, "src": 7, "dst": 20 }, { - "idx": 31, + "idx": 42, "src": 8, "dst": 21 }, { - "idx": 32, + "idx": 43, "src": 8, - "dst": 16 + "dst": 17 }, { - "idx": 33, + "idx": 44, "src": 9, "dst": 12 }, { - "idx": 34, + "idx": 45, "src": 9, - "dst": 13 + "dst": 12 }, { - "idx": 35, + "idx": 46, "src": 9, - "dst": 13 + "dst": 12 }, { - "idx": 36, + "idx": 47, "src": 9, "dst": 15 }, { - "idx": 37, + "idx": 48, "src": 9, - "dst": 13 + "dst": 14 }, { - "idx": 38, + "idx": 49, "src": 9, - "dst": 19 + "dst": 12 }, { - "idx": 39, + "idx": 50, + "src": 9, + "dst": 12 + }, + { + "idx": 51, + "src": 9, + "dst": 16 + }, + { + "idx": 52, + "src": 9, + "dst": 12 + }, + { + "idx": 53, + "src": 9, + "dst": 15 + }, + { + "idx": 54, "src": 9, "dst": 20 }, { - "idx": 40, + "idx": 55, + "src": 10, + "dst": 12 + }, + { + "idx": 56, "src": 10, "dst": 22 }, { - "idx": 41, + "idx": 57, "src": 10, "dst": 22 }, { - "idx": 42, + "idx": 58, "src": 10, "dst": 23 }, { - "idx": 43, + "idx": 59, "src": 10, - "dst": 13 + "dst": 12 } ] } \ No newline at end of file diff --git a/test/all/sample-jetton.expected.callgraph.mmd b/test/all/sample-jetton.expected.callgraph.mmd index 3cdb21bd..19366b40 100644 --- a/test/all/sample-jetton.expected.callgraph.mmd +++ b/test/all/sample-jetton.expected.callgraph.mmd @@ -10,58 +10,74 @@ graph TD node_9["JettonDefaultWallet::receiver_2832"] node_10["JettonDefaultWallet::receiver_2876"] node_11["JettonDefaultWallet::get_wallet_data"] - node_12["context"] - node_13["require"] - node_14["SampleJetton::mint"] - node_15["ctx::readForwardFee"] - node_16["min"] - node_17["ton"] - node_18["contractAddress"] - node_19["send"] + node_12["require"] + node_13["SampleJetton::mint"] + node_14["context"] + node_15["send"] + node_16["ctx::readForwardFee"] + node_17["min"] + node_18["ton"] + node_19["contractAddress"] node_20["JettonDefaultWallet::toCell"] node_21["myBalance"] node_22["msg::loadUint"] node_23["msg::loadCoins"] node_2 --> node_12 - node_2 --> node_13 + node_2 --> node_12 node_2 --> node_13 node_2 --> node_14 + node_2 --> node_12 + node_2 --> node_12 + node_2 --> node_13 node_3 --> node_12 node_3 --> node_13 node_3 --> node_14 + node_3 --> node_12 + node_3 --> node_13 + node_4 --> node_12 + node_4 --> node_14 node_4 --> node_12 - node_4 --> node_13 node_6 --> node_12 - node_6 --> node_13 - node_6 --> node_15 + node_6 --> node_12 + node_6 --> node_12 node_6 --> node_15 - node_6 --> node_13 + node_6 --> node_14 + node_6 --> node_12 + node_6 --> node_16 node_6 --> node_16 + node_6 --> node_12 node_6 --> node_17 - node_6 --> node_13 node_6 --> node_18 + node_6 --> node_12 node_6 --> node_19 + node_6 --> node_15 node_6 --> node_20 node_7 --> node_12 - node_7 --> node_13 - node_7 --> node_18 - node_7 --> node_13 + node_7 --> node_14 + node_7 --> node_12 node_7 --> node_19 + node_7 --> node_12 + node_7 --> node_15 node_7 --> node_20 node_7 --> node_8 + node_7 --> node_16 node_7 --> node_15 - node_7 --> node_19 node_7 --> node_20 node_8 --> node_21 - node_8 --> node_16 + node_8 --> node_17 + node_9 --> node_12 + node_9 --> node_12 + node_9 --> node_12 + node_9 --> node_15 + node_9 --> node_14 + node_9 --> node_12 + node_9 --> node_12 + node_9 --> node_16 node_9 --> node_12 - node_9 --> node_13 - node_9 --> node_13 node_9 --> node_15 - node_9 --> node_13 - node_9 --> node_19 node_9 --> node_20 + node_10 --> node_12 node_10 --> node_22 node_10 --> node_22 node_10 --> node_23 - node_10 --> node_13 + node_10 --> node_12 From a3325c45719bfa4c0a90dd777fddb857cf6d1f96 Mon Sep 17 00:00:00 2001 From: Esorat Date: Wed, 27 Nov 2024 13:23:22 +0700 Subject: [PATCH 2/5] Fix: add enum, remake helper functions. Chore: add EffectFlags to sendInloop detector. --- src/detectors/builtin/sendInLoop.ts | 7 ++- src/internals/ir/callGraph.ts | 74 +++++++++++------------------ 2 files changed, 32 insertions(+), 49 deletions(-) diff --git a/src/detectors/builtin/sendInLoop.ts b/src/detectors/builtin/sendInLoop.ts index 7a854b15..0d7bcd35 100644 --- a/src/detectors/builtin/sendInLoop.ts +++ b/src/detectors/builtin/sendInLoop.ts @@ -1,5 +1,5 @@ import { CompilationUnit } from "../../internals/ir"; -import { CallGraph } from "../../internals/ir/callGraph"; +import { CallGraph, EffectFlags } from "../../internals/ir/callGraph"; import { forEachStatement, foldExpressions } from "../../internals/tact"; import { isSendCall } from "../../internals/tact/util"; import { MistiTactWarning, Severity } from "../../internals/warnings"; @@ -120,7 +120,10 @@ export class SendInLoop extends ASTDetector { const calleeNodeId = callGraph.getNodeIdByName(calleeName); if (calleeNodeId !== undefined) { const calleeNode = callGraph.getNode(calleeNodeId); - if (calleeNode && calleeNode.hasFlag(0b0001)) { + if ( + calleeNode && + calleeNode.hasEffect(EffectFlags.CALLS_SEND) + ) { const functionName = calleeNode.name.includes("::") ? calleeNode.name.split("::").pop() : calleeNode.name; diff --git a/src/internals/ir/callGraph.ts b/src/internals/ir/callGraph.ts index ee522a20..3b4a275b 100644 --- a/src/internals/ir/callGraph.ts +++ b/src/internals/ir/callGraph.ts @@ -27,19 +27,20 @@ export type CGNodeId = number & { readonly brand: unique symbol }; export type CGEdgeId = number & { readonly brand: unique symbol }; /** - * Flag constants for CGNode. + * Effect flags for CGNode. * * Each flag represents an effect or property of the function represented by the node. */ -const FlagCallsSend = 0b0001; -const FlagContractStateRead = 0b0010; -const FlagContractStateWrite = 0b0100; -const FlagBlockchainStateRead = 0b1000; -const FlagRandomness = 0b10000; +export enum EffectFlags { + CALLS_SEND = 1 << 0, + CONTRACT_STATE_READ = 1 << 1, + CONTRACT_STATE_WRITE = 1 << 2, + ACCESSES_DATETIME = 1 << 3, + RANDOMNESS = 1 << 4, +} /** * Represents an edge in the call graph, indicating a call from one function to another. - * Each edge has a unique index (`idx`) generated using `IdxGenerator`. */ class CGEdge { public idx: CGEdgeId; @@ -58,13 +59,12 @@ class CGEdge { /** * Represents a node in the call graph, corresponding to a function or method. - * Nodes maintain references to incoming and outgoing edges. */ class CGNode { public idx: CGNodeId; public inEdges: Set = new Set(); public outEdges: Set = new Set(); - public flags: number = 0; + public effects: number = 0; /** * @param astId The AST ID of the function or method this node represents (can be `undefined` for synthetic nodes) @@ -82,23 +82,18 @@ class CGNode { } } - // Method to set a flag - public setFlag(flag: number) { - this.flags |= flag; + public addEffect(effect: EffectFlags) { + this.effects |= effect; } - // Method to check if a flag is set - public hasFlag(flag: number): boolean { - return (this.flags & flag) !== 0; + public hasEffect(effect: EffectFlags): boolean { + return (this.effects & effect) !== 0; } } /** * Represents the call graph, a directed graph where nodes represent functions or methods, * and edges indicate calls between them. - * - * The `CallGraph` class provides methods to build the graph from a Tact AST, retrieve nodes/edges, - * analyze connectivity, and add function calls dynamically. */ export class CallGraph { private nodeMap: Map = new Map(); @@ -309,14 +304,12 @@ export class CallGraph { const funcNode = this.getNode(funcNodeId); if (!funcNode) continue; - // Process statements if ("statements" in func && func.statements) { for (const stmt of func.statements) { this.processStatement(stmt, funcNodeId, contractName); } } - // Process expressions (if any) forEachExpression(func, (expr) => { this.processExpression(expr, funcNodeId, contractName); }); @@ -363,7 +356,7 @@ export class CallGraph { stmt.kind === "statement_augmentedassign" ) { if (isContractStateWrite(stmt)) { - funcNode.setFlag(FlagContractStateWrite); + funcNode.addEffect(EffectFlags.CONTRACT_STATE_WRITE); } } else if (stmt.kind === "statement_expression") { const stmtExpr = stmt as AstStatementExpression; @@ -375,13 +368,6 @@ export class CallGraph { } } - /** - * Processes a single expression, identifying function or method calls to create edges. - * Also detects effects and sets corresponding flags on the function node. - * @param expr The expression to process. - * @param callerId The node ID of the calling function. - * @param currentContractName The name of the contract, if applicable. - */ private processExpression( expr: AstExpression, callerId: CGNodeId, @@ -401,23 +387,22 @@ export class CallGraph { ); } } - // Detect and set effects + const funcNode = this.getNode(callerId); if (!funcNode) { return; } - if (isContractStateRead(expr)) { - funcNode.setFlag(FlagContractStateRead); + funcNode.addEffect(EffectFlags.CONTRACT_STATE_READ); } - if (isBlockchainStateRead(expr)) { - funcNode.setFlag(FlagBlockchainStateRead); + if (accessesDatetime(expr)) { + funcNode.addEffect(EffectFlags.ACCESSES_DATETIME); } if (isRandomnessCall(expr)) { - funcNode.setFlag(FlagRandomness); + funcNode.addEffect(EffectFlags.RANDOMNESS); } if (isSendCall(expr)) { - funcNode.setFlag(FlagCallsSend); + funcNode.addEffect(EffectFlags.CALLS_SEND); } } @@ -506,7 +491,7 @@ function isContractStateRead(expr: AstExpression): boolean { if (fieldAccess.aggregate.kind === "id") { const idExpr = fieldAccess.aggregate as AstId; if (idExpr.text === "self") { - return true; // Accessing a state variable via 'self' + return true; } } } @@ -531,23 +516,21 @@ function isContractStateWrite( } } } + // Note: This function does not currently detect state writes via method calls on state variables (e.g., Map.set()). + // Handling such cases may require more advanced analysis involving the symbol table or data flow analysis. return false; } /** * Helper function to determine if an expression is a blockchain state read. * @param expr The expression to check. - * @returns True if the expression reads blockchain state; otherwise, false. + * @returns True if the statement writes to a state variable; otherwise, false. */ -function isBlockchainStateRead(expr: AstExpression): boolean { +function accessesDatetime(expr: AstExpression): boolean { if (expr.kind === "static_call") { const staticCall = expr as AstStaticCall; const functionName = staticCall.function?.text; return functionName === "now" || functionName === "timestamp"; - } else if (expr.kind === "method_call") { - const methodCall = expr as AstMethodCall; - const methodName = methodCall.method?.text; - return methodName === "now" || methodName === "timestamp"; } return false; } @@ -561,11 +544,8 @@ function isRandomnessCall(expr: AstExpression): boolean { if (expr.kind === "static_call") { const staticCall = expr as AstStaticCall; const functionName = staticCall.function?.text; - return functionName === "random"; - } else if (expr.kind === "method_call") { - const methodCall = expr as AstMethodCall; - const methodName = methodCall.method?.text; - return methodName === "random"; + const prgUseNames = new Set(["nativeRandom", "nativeRandomInterval"]); + return prgUseNames.has(functionName || ""); } return false; } From 29250affcb45be79276a09c1c8e3807469eebf49 Mon Sep 17 00:00:00 2001 From: Esorat Date: Thu, 28 Nov 2024 21:03:35 +0700 Subject: [PATCH 3/5] Fix: Separate to random use and random seed initialization --- src/internals/ir/callGraph.ts | 41 ++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/internals/ir/callGraph.ts b/src/internals/ir/callGraph.ts index 3b4a275b..843f0ece 100644 --- a/src/internals/ir/callGraph.ts +++ b/src/internals/ir/callGraph.ts @@ -36,7 +36,8 @@ export enum EffectFlags { CONTRACT_STATE_READ = 1 << 1, CONTRACT_STATE_WRITE = 1 << 2, ACCESSES_DATETIME = 1 << 3, - RANDOMNESS = 1 << 4, + RANDOMNESS_USE = 1 << 4, + RANDOMNESS_SEED_INITIALIZATION = 1 << 5, } /** @@ -368,6 +369,12 @@ export class CallGraph { } } + /** + * Processes an expression, adding edges and setting effect flags as necessary. + * @param expr The expression to process. + * @param callerId The node ID of the calling function. + * @param currentContractName The name of the contract, if applicable. + */ private processExpression( expr: AstExpression, callerId: CGNodeId, @@ -398,8 +405,11 @@ export class CallGraph { if (accessesDatetime(expr)) { funcNode.addEffect(EffectFlags.ACCESSES_DATETIME); } - if (isRandomnessCall(expr)) { - funcNode.addEffect(EffectFlags.RANDOMNESS); + if (isRandomnessUseCall(expr)) { + funcNode.addEffect(EffectFlags.RANDOMNESS_USE); + } + if (isRandomnessSeedInitializationCall(expr)) { + funcNode.addEffect(EffectFlags.RANDOMNESS_SEED_INITIALIZATION); } if (isSendCall(expr)) { funcNode.addEffect(EffectFlags.CALLS_SEND); @@ -522,9 +532,9 @@ function isContractStateWrite( } /** - * Helper function to determine if an expression is a blockchain state read. + * Helper function to determine if an expression accesses the blockchain datetime. * @param expr The expression to check. - * @returns True if the statement writes to a state variable; otherwise, false. + * @returns True if the expression accesses datetime; otherwise, false. */ function accessesDatetime(expr: AstExpression): boolean { if (expr.kind === "static_call") { @@ -536,11 +546,11 @@ function accessesDatetime(expr: AstExpression): boolean { } /** - * Helper function to determine if an expression is a randomness call. + * Helper function to determine if an expression is a randomness use call. * @param expr The expression to check. - * @returns True if the expression introduces randomness; otherwise, false. + * @returns True if the expression uses randomness; otherwise, false. */ -function isRandomnessCall(expr: AstExpression): boolean { +function isRandomnessUseCall(expr: AstExpression): boolean { if (expr.kind === "static_call") { const staticCall = expr as AstStaticCall; const functionName = staticCall.function?.text; @@ -549,3 +559,18 @@ function isRandomnessCall(expr: AstExpression): boolean { } return false; } + +/** + * Helper function to determine if an expression is a randomness seed initialization call. + * @param expr The expression to check. + * @returns True if the expression initializes the randomness seed; otherwise, false. + */ +function isRandomnessSeedInitializationCall(expr: AstExpression): boolean { + if (expr.kind === "static_call") { + const staticCall = expr as AstStaticCall; + const functionName = staticCall.function?.text; + const prgSeedNames = new Set(["setPrgSeed"]); + return prgSeedNames.has(functionName || ""); + } + return false; +} From 4d0628cb16674c6b1e0300c80022348ca66ab109 Mon Sep 17 00:00:00 2001 From: Esorat Date: Fri, 29 Nov 2024 19:33:56 +0700 Subject: [PATCH 4/5] Fix: update needed tests for check callgraph dump correct apply effects. --- test/all/syntax.expected.callgraph.dot | 57 ++- test/all/syntax.expected.callgraph.json | 285 ++++++++++++- test/all/syntax.expected.callgraph.mmd | 57 ++- test/all/syntax.expected.cfg.dot | 100 ++++- test/all/syntax.expected.cfg.json | 541 +++++++++++++++++++++++- test/all/syntax.expected.cfg.mmd | 87 +++- test/all/syntax.expected.out | 99 +++++ test/all/syntax.tact | 111 +++++ 8 files changed, 1297 insertions(+), 40 deletions(-) diff --git a/test/all/syntax.expected.callgraph.dot b/test/all/syntax.expected.callgraph.dot index 565484dc..5d136332 100644 --- a/test/all/syntax.expected.callgraph.dot +++ b/test/all/syntax.expected.callgraph.dot @@ -2,14 +2,51 @@ digraph "CallGraph" { node [shape=box]; node_1 [label="test_try"]; node_2 [label="test_loops"]; - node_3 [label="TestContract::getter"]; - node_4 [label="TestContractF::test"]; - node_5 [label="TestContractT::test"]; - node_6 [label="TestContractT::receiver_1722"]; - node_7 [label="dump"]; - node_8 [label="emptyMap"]; - node_9 [label="TestContractT::getA"]; - node_1 -> node_7; - node_2 -> node_8; - node_5 -> node_9; + node_3 [label="testTryCatch"]; + node_4 [label="testLoops"]; + node_5 [label="TestContract::getter"]; + node_6 [label="TestContractF::test"]; + node_7 [label="TestContractT::test"]; + node_8 [label="TestContractT::receiver_1722"]; + node_9 [label="EffectTestContract::contract_init_1823"]; + node_10 [label="EffectTestContract::funcWithSend"]; + node_11 [label="EffectTestContract::funcWithStateRead"]; + node_12 [label="EffectTestContract::funcWithStateWrite"]; + node_13 [label="EffectTestContract::funcWithDatetimeAccess"]; + node_14 [label="EffectTestContract::funcWithRandomnessUse"]; + node_15 [label="EffectTestContract::funcWithRandomnessSeedInit"]; + node_16 [label="EffectTestContract::generatePseudoRandom"]; + node_17 [label="EffectTestContract::funcWithMultipleEffects"]; + node_18 [label="dump"]; + node_19 [label="emptyMap"]; + node_20 [label="m::set"]; + node_21 [label="TestContractT::getA"]; + node_22 [label="sender"]; + node_23 [label="send"]; + node_24 [label="now"]; + node_25 [label="random"]; + node_26 [label="pow"]; + node_1 -> node_18; + node_2 -> node_19; + node_3 -> node_18; + node_4 -> node_20; + node_4 -> node_20; + node_4 -> node_20; + node_4 -> node_19; + node_4 -> node_20; + node_4 -> node_20; + node_4 -> node_20; + node_7 -> node_21; + node_9 -> node_22; + node_10 -> node_23; + node_10 -> node_23; + node_13 -> node_24; + node_14 -> node_25; + node_16 -> node_26; + node_17 -> node_23; + node_17 -> node_15; + node_17 -> node_25; + node_17 -> node_23; + node_17 -> node_24; + node_17 -> node_15; } diff --git a/test/all/syntax.expected.callgraph.json b/test/all/syntax.expected.callgraph.json index 346edc0b..7fabb450 100644 --- a/test/all/syntax.expected.callgraph.json +++ b/test/all/syntax.expected.callgraph.json @@ -18,51 +18,208 @@ }, { "idx": 3, + "name": "testTryCatch", + "inEdges": [], + "outEdges": [ + 3 + ] + }, + { + "idx": 4, + "name": "testLoops", + "inEdges": [], + "outEdges": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ] + }, + { + "idx": 5, "name": "TestContract::getter", "inEdges": [], "outEdges": [] }, { - "idx": 4, + "idx": 6, "name": "TestContractF::test", "inEdges": [], "outEdges": [] }, { - "idx": 5, + "idx": 7, "name": "TestContractT::test", "inEdges": [], "outEdges": [ - 3 + 11 ] }, { - "idx": 6, + "idx": 8, "name": "TestContractT::receiver_1722", "inEdges": [], "outEdges": [] }, { - "idx": 7, + "idx": 9, + "name": "EffectTestContract::contract_init_1823", + "inEdges": [], + "outEdges": [ + 12 + ] + }, + { + "idx": 10, + "name": "EffectTestContract::funcWithSend", + "inEdges": [], + "outEdges": [ + 13, + 14 + ] + }, + { + "idx": 11, + "name": "EffectTestContract::funcWithStateRead", + "inEdges": [], + "outEdges": [] + }, + { + "idx": 12, + "name": "EffectTestContract::funcWithStateWrite", + "inEdges": [], + "outEdges": [] + }, + { + "idx": 13, + "name": "EffectTestContract::funcWithDatetimeAccess", + "inEdges": [], + "outEdges": [ + 15 + ] + }, + { + "idx": 14, + "name": "EffectTestContract::funcWithRandomnessUse", + "inEdges": [], + "outEdges": [ + 16 + ] + }, + { + "idx": 15, + "name": "EffectTestContract::funcWithRandomnessSeedInit", + "inEdges": [ + 19, + 23 + ], + "outEdges": [] + }, + { + "idx": 16, + "name": "EffectTestContract::generatePseudoRandom", + "inEdges": [], + "outEdges": [ + 17 + ] + }, + { + "idx": 17, + "name": "EffectTestContract::funcWithMultipleEffects", + "inEdges": [], + "outEdges": [ + 18, + 19, + 20, + 21, + 22, + 23 + ] + }, + { + "idx": 18, "name": "dump", "inEdges": [ - 1 + 1, + 3 ], "outEdges": [] }, { - "idx": 8, + "idx": 19, "name": "emptyMap", "inEdges": [ - 2 + 2, + 7 ], "outEdges": [] }, { - "idx": 9, + "idx": 20, + "name": "m::set", + "inEdges": [ + 4, + 5, + 6, + 8, + 9, + 10 + ], + "outEdges": [] + }, + { + "idx": 21, "name": "TestContractT::getA", "inEdges": [ - 3 + 11 + ], + "outEdges": [] + }, + { + "idx": 22, + "name": "sender", + "inEdges": [ + 12 + ], + "outEdges": [] + }, + { + "idx": 23, + "name": "send", + "inEdges": [ + 13, + 14, + 18, + 21 + ], + "outEdges": [] + }, + { + "idx": 24, + "name": "now", + "inEdges": [ + 15, + 22 + ], + "outEdges": [] + }, + { + "idx": 25, + "name": "random", + "inEdges": [ + 16, + 20 + ], + "outEdges": [] + }, + { + "idx": 26, + "name": "pow", + "inEdges": [ + 17 ], "outEdges": [] } @@ -71,17 +228,117 @@ { "idx": 1, "src": 1, - "dst": 7 + "dst": 18 }, { "idx": 2, "src": 2, - "dst": 8 + "dst": 19 }, { "idx": 3, - "src": 5, - "dst": 9 + "src": 3, + "dst": 18 + }, + { + "idx": 4, + "src": 4, + "dst": 20 + }, + { + "idx": 5, + "src": 4, + "dst": 20 + }, + { + "idx": 6, + "src": 4, + "dst": 20 + }, + { + "idx": 7, + "src": 4, + "dst": 19 + }, + { + "idx": 8, + "src": 4, + "dst": 20 + }, + { + "idx": 9, + "src": 4, + "dst": 20 + }, + { + "idx": 10, + "src": 4, + "dst": 20 + }, + { + "idx": 11, + "src": 7, + "dst": 21 + }, + { + "idx": 12, + "src": 9, + "dst": 22 + }, + { + "idx": 13, + "src": 10, + "dst": 23 + }, + { + "idx": 14, + "src": 10, + "dst": 23 + }, + { + "idx": 15, + "src": 13, + "dst": 24 + }, + { + "idx": 16, + "src": 14, + "dst": 25 + }, + { + "idx": 17, + "src": 16, + "dst": 26 + }, + { + "idx": 18, + "src": 17, + "dst": 23 + }, + { + "idx": 19, + "src": 17, + "dst": 15 + }, + { + "idx": 20, + "src": 17, + "dst": 25 + }, + { + "idx": 21, + "src": 17, + "dst": 23 + }, + { + "idx": 22, + "src": 17, + "dst": 24 + }, + { + "idx": 23, + "src": 17, + "dst": 15 } ] } \ No newline at end of file diff --git a/test/all/syntax.expected.callgraph.mmd b/test/all/syntax.expected.callgraph.mmd index 720a23bb..9e3015bd 100644 --- a/test/all/syntax.expected.callgraph.mmd +++ b/test/all/syntax.expected.callgraph.mmd @@ -1,13 +1,50 @@ graph TD node_1["test_try"] node_2["test_loops"] - node_3["TestContract::getter"] - node_4["TestContractF::test"] - node_5["TestContractT::test"] - node_6["TestContractT::receiver_1722"] - node_7["dump"] - node_8["emptyMap"] - node_9["TestContractT::getA"] - node_1 --> node_7 - node_2 --> node_8 - node_5 --> node_9 + node_3["testTryCatch"] + node_4["testLoops"] + node_5["TestContract::getter"] + node_6["TestContractF::test"] + node_7["TestContractT::test"] + node_8["TestContractT::receiver_1722"] + node_9["EffectTestContract::contract_init_1823"] + node_10["EffectTestContract::funcWithSend"] + node_11["EffectTestContract::funcWithStateRead"] + node_12["EffectTestContract::funcWithStateWrite"] + node_13["EffectTestContract::funcWithDatetimeAccess"] + node_14["EffectTestContract::funcWithRandomnessUse"] + node_15["EffectTestContract::funcWithRandomnessSeedInit"] + node_16["EffectTestContract::generatePseudoRandom"] + node_17["EffectTestContract::funcWithMultipleEffects"] + node_18["dump"] + node_19["emptyMap"] + node_20["m::set"] + node_21["TestContractT::getA"] + node_22["sender"] + node_23["send"] + node_24["now"] + node_25["random"] + node_26["pow"] + node_1 --> node_18 + node_2 --> node_19 + node_3 --> node_18 + node_4 --> node_20 + node_4 --> node_20 + node_4 --> node_20 + node_4 --> node_19 + node_4 --> node_20 + node_4 --> node_20 + node_4 --> node_20 + node_7 --> node_21 + node_9 --> node_22 + node_10 --> node_23 + node_10 --> node_23 + node_13 --> node_24 + node_14 --> node_25 + node_16 --> node_26 + node_17 --> node_23 + node_17 --> node_15 + node_17 --> node_25 + node_17 --> node_23 + node_17 --> node_24 + node_17 --> node_15 diff --git a/test/all/syntax.expected.cfg.dot b/test/all/syntax.expected.cfg.dot index 26e8f1c1..2ed197ad 100644 --- a/test/all/syntax.expected.cfg.dot +++ b/test/all/syntax.expected.cfg.dot @@ -46,18 +46,114 @@ digraph "syntax" { "test_loops_85" -> "test_loops_86"; "test_loops_86" -> "test_loops_86"; } + subgraph "cluster_testTryCatch" { + label="testTryCatch"; + "testTryCatch_87" [label="try ... catch (err)"]; + "testTryCatch_88" [label="a += 1",style=filled,fillcolor="#66A7DB"]; + "testTryCatch_89" [label="dump(err)",style=filled,fillcolor="#66A7DB"]; + "testTryCatch_87" -> "testTryCatch_88"; + "testTryCatch_87" -> "testTryCatch_89"; + } + subgraph "cluster_testLoops" { + label="testLoops"; + "testLoops_90" [label="let sum: Int = 0"]; + "testLoops_91" [label="let i: Int = 0"]; + "testLoops_92" [label="while (i < 10)"]; + "testLoops_93" [label="i = i + 1"]; + "testLoops_94" [label="sum = sum + i"]; + "testLoops_95" [label="until (i <= 0)"]; + "testLoops_96" [label="i = i - 1"]; + "testLoops_97" [label="sum = sum + i"]; + "testLoops_98" [label="repeat (10)"]; + "testLoops_99" [label="i = i + 1"]; + "testLoops_100" [label="sum = sum + i"]; + "testLoops_101" [label="let m: map = emptyMap()"]; + "testLoops_102" [label="m.set(1, 10)"]; + "testLoops_103" [label="m.set(2, 20)"]; + "testLoops_104" [label="m.set(3, 30)"]; + "testLoops_105" [label="foreach ((key, value) of m)"]; + "testLoops_106" [label="sum = sum + value"]; + "testLoops_90" -> "testLoops_91"; + "testLoops_91" -> "testLoops_92"; + "testLoops_92" -> "testLoops_93"; + "testLoops_93" -> "testLoops_94"; + "testLoops_94" -> "testLoops_92"; + "testLoops_92" -> "testLoops_95"; + "testLoops_95" -> "testLoops_96"; + "testLoops_96" -> "testLoops_97"; + "testLoops_97" -> "testLoops_95"; + "testLoops_95" -> "testLoops_98"; + "testLoops_98" -> "testLoops_99"; + "testLoops_99" -> "testLoops_100"; + "testLoops_100" -> "testLoops_98"; + "testLoops_98" -> "testLoops_101"; + "testLoops_101" -> "testLoops_102"; + "testLoops_102" -> "testLoops_103"; + "testLoops_103" -> "testLoops_104"; + "testLoops_104" -> "testLoops_105"; + "testLoops_105" -> "testLoops_106"; + "testLoops_106" -> "testLoops_105"; + } subgraph "cluster_TestContract__getter" { label="TestContract__getter"; - "TestContract__getter_87" [label="return 0",style=filled,fillcolor="#66A7DB"]; + "TestContract__getter_107" [label="return 0",style=filled,fillcolor="#66A7DB"]; } subgraph "cluster_TestContractF__test" { label="TestContractF__test"; } subgraph "cluster_TestContractT__test" { label="TestContractT__test"; - "TestContractT__test_88" [label="return self.getA()",style=filled,fillcolor="#66A7DB"]; + "TestContractT__test_108" [label="return self.getA()",style=filled,fillcolor="#66A7DB"]; } subgraph "cluster_TestContractT__receive_external_fallback_1722" { label="TestContractT__receive_external_fallback_1722"; } + subgraph "cluster_EffectTestContract__init_1942" { + label="EffectTestContract__init_1942"; + "EffectTestContract__init_1942_109" [label="self.destAddress = sender()",style=filled,fillcolor="#66A7DB"]; + } + subgraph "cluster_EffectTestContract__funcWithSend" { + label="EffectTestContract__funcWithSend"; + "EffectTestContract__funcWithSend_110" [label="let amount: Int = 100"]; + "EffectTestContract__funcWithSend_111" [label="send(SendParameters{to: self.destAddress, value: amount})",style=filled,fillcolor="#66A7DB"]; + "EffectTestContract__funcWithSend_110" -> "EffectTestContract__funcWithSend_111"; + } + subgraph "cluster_EffectTestContract__funcWithStateRead" { + label="EffectTestContract__funcWithStateRead"; + "EffectTestContract__funcWithStateRead_112" [label="let value: Int = self.someVariable",style=filled,fillcolor="#66A7DB"]; + } + subgraph "cluster_EffectTestContract__funcWithStateWrite" { + label="EffectTestContract__funcWithStateWrite"; + "EffectTestContract__funcWithStateWrite_113" [label="self.someVariable = 42",style=filled,fillcolor="#66A7DB"]; + } + subgraph "cluster_EffectTestContract__funcWithDatetimeAccess" { + label="EffectTestContract__funcWithDatetimeAccess"; + "EffectTestContract__funcWithDatetimeAccess_114" [label="let currentTime: Int = now()",style=filled,fillcolor="#66A7DB"]; + } + subgraph "cluster_EffectTestContract__funcWithRandomnessUse" { + label="EffectTestContract__funcWithRandomnessUse"; + "EffectTestContract__funcWithRandomnessUse_115" [label="let randValue: Int = random(1, 100)",style=filled,fillcolor="#66A7DB"]; + } + subgraph "cluster_EffectTestContract__funcWithRandomnessSeedInit" { + label="EffectTestContract__funcWithRandomnessSeedInit"; + "EffectTestContract__funcWithRandomnessSeedInit_116" [label="self.randomnessSeed = seed",style=filled,fillcolor="#66A7DB"]; + } + subgraph "cluster_EffectTestContract__generatePseudoRandom" { + label="EffectTestContract__generatePseudoRandom"; + "EffectTestContract__generatePseudoRandom_117" [label="self.randomnessSeed = (self.randomnessSeed * 1103515245 + 12345) % pow(2, 31)"]; + "EffectTestContract__generatePseudoRandom_118" [label="return self.randomnessSeed",style=filled,fillcolor="#66A7DB"]; + "EffectTestContract__generatePseudoRandom_117" -> "EffectTestContract__generatePseudoRandom_118"; + } + subgraph "cluster_EffectTestContract__funcWithMultipleEffects" { + label="EffectTestContract__funcWithMultipleEffects"; + "EffectTestContract__funcWithMultipleEffects_119" [label="self.someVariable += random(1, 100)"]; + "EffectTestContract__funcWithMultipleEffects_120" [label="send(SendParameters{to: self.destAddress, value: self.someVariable})"]; + "EffectTestContract__funcWithMultipleEffects_121" [label="let time: Int = now()"]; + "EffectTestContract__funcWithMultipleEffects_122" [label="self.funcWithRandomnessSeedInit(time)",style=filled,fillcolor="#66A7DB"]; + "EffectTestContract__funcWithMultipleEffects_119" -> "EffectTestContract__funcWithMultipleEffects_120"; + "EffectTestContract__funcWithMultipleEffects_120" -> "EffectTestContract__funcWithMultipleEffects_121"; + "EffectTestContract__funcWithMultipleEffects_121" -> "EffectTestContract__funcWithMultipleEffects_122"; + } +"119" -> "26"; +"120" -> "35"; } diff --git a/test/all/syntax.expected.cfg.json b/test/all/syntax.expected.cfg.json index b45bd0b2..420f8480 100644 --- a/test/all/syntax.expected.cfg.json +++ b/test/all/syntax.expected.cfg.json @@ -313,6 +313,334 @@ } ] } + }, + { + "name": "testTryCatch", + "cfg": { + "nodes": [ + { + "id": 87, + "stmtID": 1955, + "srcEdges": [], + "dstEdges": [ + 78, + 79 + ] + }, + { + "id": 88, + "stmtID": 1949, + "srcEdges": [ + 78 + ], + "dstEdges": [] + }, + { + "id": 89, + "stmtID": 1954, + "srcEdges": [ + 79 + ], + "dstEdges": [] + } + ], + "edges": [ + { + "id": 78, + "src": 87, + "dst": 88 + }, + { + "id": 79, + "src": 87, + "dst": 89 + } + ] + } + }, + { + "name": "testLoops", + "cfg": { + "nodes": [ + { + "id": 90, + "stmtID": 1961, + "srcEdges": [], + "dstEdges": [ + 80 + ] + }, + { + "id": 91, + "stmtID": 1965, + "srcEdges": [ + 80 + ], + "dstEdges": [ + 81 + ] + }, + { + "id": 92, + "stmtID": 1979, + "srcEdges": [ + 81, + 84 + ], + "dstEdges": [ + 82, + 85 + ] + }, + { + "id": 93, + "stmtID": 1973, + "srcEdges": [ + 82 + ], + "dstEdges": [ + 83 + ] + }, + { + "id": 94, + "stmtID": 1978, + "srcEdges": [ + 83 + ], + "dstEdges": [ + 84 + ] + }, + { + "id": 95, + "stmtID": 1993, + "srcEdges": [ + 85, + 88 + ], + "dstEdges": [ + 86, + 89 + ] + }, + { + "id": 96, + "stmtID": 1987, + "srcEdges": [ + 86 + ], + "dstEdges": [ + 87 + ] + }, + { + "id": 97, + "stmtID": 1992, + "srcEdges": [ + 87 + ], + "dstEdges": [ + 88 + ] + }, + { + "id": 98, + "stmtID": 2005, + "srcEdges": [ + 89, + 92 + ], + "dstEdges": [ + 90, + 93 + ] + }, + { + "id": 99, + "stmtID": 1999, + "srcEdges": [ + 90 + ], + "dstEdges": [ + 91 + ] + }, + { + "id": 100, + "stmtID": 2004, + "srcEdges": [ + 91 + ], + "dstEdges": [ + 92 + ] + }, + { + "id": 101, + "stmtID": 2012, + "srcEdges": [ + 93 + ], + "dstEdges": [ + 94 + ] + }, + { + "id": 102, + "stmtID": 2018, + "srcEdges": [ + 94 + ], + "dstEdges": [ + 95 + ] + }, + { + "id": 103, + "stmtID": 2024, + "srcEdges": [ + 95 + ], + "dstEdges": [ + 96 + ] + }, + { + "id": 104, + "stmtID": 2030, + "srcEdges": [ + 96 + ], + "dstEdges": [ + 97 + ] + }, + { + "id": 105, + "stmtID": 2039, + "srcEdges": [ + 97, + 99 + ], + "dstEdges": [ + 98 + ] + }, + { + "id": 106, + "stmtID": 2038, + "srcEdges": [ + 98 + ], + "dstEdges": [ + 99 + ] + } + ], + "edges": [ + { + "id": 80, + "src": 90, + "dst": 91 + }, + { + "id": 81, + "src": 91, + "dst": 92 + }, + { + "id": 82, + "src": 92, + "dst": 93 + }, + { + "id": 83, + "src": 93, + "dst": 94 + }, + { + "id": 84, + "src": 94, + "dst": 92 + }, + { + "id": 85, + "src": 92, + "dst": 95 + }, + { + "id": 86, + "src": 95, + "dst": 96 + }, + { + "id": 87, + "src": 96, + "dst": 97 + }, + { + "id": 88, + "src": 97, + "dst": 95 + }, + { + "id": 89, + "src": 95, + "dst": 98 + }, + { + "id": 90, + "src": 98, + "dst": 99 + }, + { + "id": 91, + "src": 99, + "dst": 100 + }, + { + "id": 92, + "src": 100, + "dst": 98 + }, + { + "id": 93, + "src": 98, + "dst": 101 + }, + { + "id": 94, + "src": 101, + "dst": 102 + }, + { + "id": 95, + "src": 102, + "dst": 103 + }, + { + "id": 96, + "src": 103, + "dst": 104 + }, + { + "id": 97, + "src": 104, + "dst": 105 + }, + { + "id": 98, + "src": 105, + "dst": 106 + }, + { + "id": 99, + "src": 106, + "dst": 105 + } + ] + } } ], "contracts": [ @@ -324,7 +652,7 @@ "cfg": { "nodes": [ { - "id": 87, + "id": 107, "stmtID": 1662, "srcEdges": [], "dstEdges": [] @@ -355,7 +683,7 @@ "cfg": { "nodes": [ { - "id": 88, + "id": 108, "stmtID": 1720, "srcEdges": [], "dstEdges": [] @@ -372,6 +700,215 @@ } } ] + }, + { + "name": "EffectTestContract", + "methods": [ + { + "name": "EffectTestContract.init_1942", + "cfg": { + "nodes": [ + { + "id": 109, + "stmtID": 1822, + "srcEdges": [], + "dstEdges": [] + } + ], + "edges": [] + } + }, + { + "name": "EffectTestContract.funcWithSend", + "cfg": { + "nodes": [ + { + "id": 110, + "stmtID": 1828, + "srcEdges": [], + "dstEdges": [ + 100 + ] + }, + { + "id": 111, + "stmtID": 1841, + "srcEdges": [ + 100 + ], + "dstEdges": [] + } + ], + "edges": [ + { + "id": 100, + "src": 110, + "dst": 111 + } + ] + } + }, + { + "name": "EffectTestContract.funcWithStateRead", + "cfg": { + "nodes": [ + { + "id": 112, + "stmtID": 1849, + "srcEdges": [], + "dstEdges": [] + } + ], + "edges": [] + } + }, + { + "name": "EffectTestContract.funcWithStateWrite", + "cfg": { + "nodes": [ + { + "id": 113, + "stmtID": 1856, + "srcEdges": [], + "dstEdges": [] + } + ], + "edges": [] + } + }, + { + "name": "EffectTestContract.funcWithDatetimeAccess", + "cfg": { + "nodes": [ + { + "id": 114, + "stmtID": 1863, + "srcEdges": [], + "dstEdges": [] + } + ], + "edges": [] + } + }, + { + "name": "EffectTestContract.funcWithRandomnessUse", + "cfg": { + "nodes": [ + { + "id": 115, + "stmtID": 1872, + "srcEdges": [], + "dstEdges": [] + } + ], + "edges": [] + } + }, + { + "name": "EffectTestContract.funcWithRandomnessSeedInit", + "cfg": { + "nodes": [ + { + "id": 116, + "stmtID": 1882, + "srcEdges": [], + "dstEdges": [] + } + ], + "edges": [] + } + }, + { + "name": "EffectTestContract.generatePseudoRandom", + "cfg": { + "nodes": [ + { + "id": 117, + "stmtID": 1901, + "srcEdges": [], + "dstEdges": [ + 101 + ] + }, + { + "id": 118, + "stmtID": 1905, + "srcEdges": [ + 101 + ], + "dstEdges": [] + } + ], + "edges": [ + { + "id": 101, + "src": 117, + "dst": 118 + } + ] + } + }, + { + "name": "EffectTestContract.funcWithMultipleEffects", + "cfg": { + "nodes": [ + { + "id": 119, + "stmtID": 1915, + "srcEdges": [], + "dstEdges": [ + 102 + ] + }, + { + "id": 120, + "stmtID": 1930, + "srcEdges": [ + 102 + ], + "dstEdges": [ + 103 + ] + }, + { + "id": 121, + "stmtID": 1935, + "srcEdges": [ + 103 + ], + "dstEdges": [ + 104 + ] + }, + { + "id": 122, + "stmtID": 1940, + "srcEdges": [ + 104 + ], + "dstEdges": [] + } + ], + "edges": [ + { + "id": 102, + "src": 119, + "dst": 120 + }, + { + "id": 103, + "src": 120, + "dst": 121 + }, + { + "id": 104, + "src": 121, + "dst": 122 + } + ] + } + } + ] } ] } \ No newline at end of file diff --git a/test/all/syntax.expected.cfg.mmd b/test/all/syntax.expected.cfg.mmd index 34217d53..547703ff 100644 --- a/test/all/syntax.expected.cfg.mmd +++ b/test/all/syntax.expected.cfg.mmd @@ -43,13 +43,96 @@ subgraph test_loops test_loops_85 --> test_loops_86 test_loops_86 --> test_loops_86 end +subgraph testTryCatch + testTryCatch_87["try ... catch (err)"] + testTryCatch_88["a += 1"]:::exitNode + testTryCatch_89["dump(err)"]:::exitNode + testTryCatch_87 --> testTryCatch_88 + testTryCatch_87 --> testTryCatch_89 +end +subgraph testLoops + testLoops_90["let sum: Int = 0"] + testLoops_91["let i: Int = 0"] + testLoops_92["while (i < 10)"] + testLoops_93["i = i + 1"] + testLoops_94["sum = sum + i"] + testLoops_95["until (i <= 0)"] + testLoops_96["i = i - 1"] + testLoops_97["sum = sum + i"] + testLoops_98["repeat (10)"] + testLoops_99["i = i + 1"] + testLoops_100["sum = sum + i"] + testLoops_101["let m: map<Int, Int> = emptyMap()"] + testLoops_102["m.set(1, 10)"] + testLoops_103["m.set(2, 20)"] + testLoops_104["m.set(3, 30)"] + testLoops_105["foreach ((key, value) of m)"] + testLoops_106["sum = sum + value"] + testLoops_90 --> testLoops_91 + testLoops_91 --> testLoops_92 + testLoops_92 --> testLoops_93 + testLoops_93 --> testLoops_94 + testLoops_94 --> testLoops_92 + testLoops_92 --> testLoops_95 + testLoops_95 --> testLoops_96 + testLoops_96 --> testLoops_97 + testLoops_97 --> testLoops_95 + testLoops_95 --> testLoops_98 + testLoops_98 --> testLoops_99 + testLoops_99 --> testLoops_100 + testLoops_100 --> testLoops_98 + testLoops_98 --> testLoops_101 + testLoops_101 --> testLoops_102 + testLoops_102 --> testLoops_103 + testLoops_103 --> testLoops_104 + testLoops_104 --> testLoops_105 + testLoops_105 --> testLoops_106 + testLoops_106 --> testLoops_105 +end subgraph TestContract__getter - TestContract__getter_87["return 0"]:::exitNode + TestContract__getter_107["return 0"]:::exitNode end subgraph TestContractF__test end subgraph TestContractT__test - TestContractT__test_88["return self.getA()"]:::exitNode + TestContractT__test_108["return self.getA()"]:::exitNode end subgraph TestContractT__receive_external_fallback_1722 end +subgraph EffectTestContract__init_1942 + EffectTestContract__init_1942_109["self.destAddress = sender()"]:::exitNode +end +subgraph EffectTestContract__funcWithSend + EffectTestContract__funcWithSend_110["let amount: Int = 100"] + EffectTestContract__funcWithSend_111["send(SendParameters{to: self.destAddress, value: amount})"]:::exitNode + EffectTestContract__funcWithSend_110 --> EffectTestContract__funcWithSend_111 +end +subgraph EffectTestContract__funcWithStateRead + EffectTestContract__funcWithStateRead_112["let value: Int = self.someVariable"]:::exitNode +end +subgraph EffectTestContract__funcWithStateWrite + EffectTestContract__funcWithStateWrite_113["self.someVariable = 42"]:::exitNode +end +subgraph EffectTestContract__funcWithDatetimeAccess + EffectTestContract__funcWithDatetimeAccess_114["let currentTime: Int = now()"]:::exitNode +end +subgraph EffectTestContract__funcWithRandomnessUse + EffectTestContract__funcWithRandomnessUse_115["let randValue: Int = random(1, 100)"]:::exitNode +end +subgraph EffectTestContract__funcWithRandomnessSeedInit + EffectTestContract__funcWithRandomnessSeedInit_116["self.randomnessSeed = seed"]:::exitNode +end +subgraph EffectTestContract__generatePseudoRandom + EffectTestContract__generatePseudoRandom_117["self.randomnessSeed = (self.randomnessSeed * 1103515245 + 12345) % pow(2, 31)"] + EffectTestContract__generatePseudoRandom_118["return self.randomnessSeed"]:::exitNode + EffectTestContract__generatePseudoRandom_117 --> EffectTestContract__generatePseudoRandom_118 +end +subgraph EffectTestContract__funcWithMultipleEffects + EffectTestContract__funcWithMultipleEffects_119["self.someVariable += random(1, 100)"] + EffectTestContract__funcWithMultipleEffects_120["send(SendParameters{to: self.destAddress, value: self.someVariable})"] + EffectTestContract__funcWithMultipleEffects_121["let time: Int = now()"] + EffectTestContract__funcWithMultipleEffects_122["self.funcWithRandomnessSeedInit(time)"]:::exitNode + EffectTestContract__funcWithMultipleEffects_119 --> EffectTestContract__funcWithMultipleEffects_120 + EffectTestContract__funcWithMultipleEffects_120 --> EffectTestContract__funcWithMultipleEffects_121 + EffectTestContract__funcWithMultipleEffects_121 --> EffectTestContract__funcWithMultipleEffects_122 +end diff --git a/test/all/syntax.expected.out b/test/all/syntax.expected.out index 5161159e..f07737d6 100644 --- a/test/all/syntax.expected.out +++ b/test/all/syntax.expected.out @@ -1,3 +1,30 @@ +[MEDIUM] NeverAccessedVariables: Variable value is never accessed +test/all/syntax.tact:97:9: + 96 | fun funcWithStateRead() { +> 97 | let value: Int = self.someVariable; + ^ + 98 | } +Help: Consider removing the variable +See: https://nowarp.io/tools/misti/docs/detectors/NeverAccessedVariables + +[MEDIUM] NeverAccessedVariables: Variable currentTime is never accessed +test/all/syntax.tact:107:9: + 106 | fun funcWithDatetimeAccess() { +> 107 | let currentTime: Int = now(); + ^ + 108 | } +Help: Consider removing the variable +See: https://nowarp.io/tools/misti/docs/detectors/NeverAccessedVariables + +[MEDIUM] NeverAccessedVariables: Variable randValue is never accessed +test/all/syntax.tact:112:9: + 111 | fun funcWithRandomnessUse() { +> 112 | let randValue: Int = random(1, 100); + ^ + 113 | } +Help: Consider removing the variable +See: https://nowarp.io/tools/misti/docs/detectors/NeverAccessedVariables + [INFO] DumpIsUsed: Found `dump` usage test/all/syntax.tact:41:35: 40 | try { /* empty */ } @@ -7,6 +34,15 @@ test/all/syntax.tact:41:35: Help: Using `dump` in production code can sometimes indicate complex code that requires additional review See: https://nowarp.io/tools/misti/docs/detectors/DumpIsUsed +[INFO] DumpIsUsed: Found `dump` usage +test/all/syntax.tact:141:9: + 140 | } catch (err) { +> 141 | dump(err); + ^ + 142 | } +Help: Using `dump` in production code can sometimes indicate complex code that requires additional review +See: https://nowarp.io/tools/misti/docs/detectors/DumpIsUsed + [INFO] PreferAugmentedAssign: Prefer augmented assignment: i += 1 test/all/syntax.tact:48:7: 47 | while (i < 10) { @@ -59,4 +95,67 @@ test/all/syntax.tact:57:7: ^ 58 | } Help: Consider using augmented assignment instead: sum += i +See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign + +[INFO] PreferAugmentedAssign: Prefer augmented assignment: i += 1 +test/all/syntax.tact:150:9: + 149 | while (i < 10) { +> 150 | i = i + 1; + ^ + 151 | sum = sum + i; +Help: Consider using augmented assignment instead: i += 1 +See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign + +[INFO] PreferAugmentedAssign: Prefer augmented assignment: sum += i +test/all/syntax.tact:151:9: + 150 | i = i + 1; +> 151 | sum = sum + i; + ^ + 152 | } +Help: Consider using augmented assignment instead: sum += i +See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign + +[INFO] PreferAugmentedAssign: Prefer augmented assignment: i -= 1 +test/all/syntax.tact:155:9: + 154 | do { +> 155 | i = i - 1; + ^ + 156 | sum = sum + i; +Help: Consider using augmented assignment instead: i -= 1 +See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign + +[INFO] PreferAugmentedAssign: Prefer augmented assignment: sum += i +test/all/syntax.tact:156:9: + 155 | i = i - 1; +> 156 | sum = sum + i; + ^ + 157 | } until (i <= 0); +Help: Consider using augmented assignment instead: sum += i +See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign + +[INFO] PreferAugmentedAssign: Prefer augmented assignment: i += 1 +test/all/syntax.tact:160:9: + 159 | repeat (10) { +> 160 | i = i + 1; + ^ + 161 | sum = sum + i; +Help: Consider using augmented assignment instead: i += 1 +See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign + +[INFO] PreferAugmentedAssign: Prefer augmented assignment: sum += i +test/all/syntax.tact:161:9: + 160 | i = i + 1; +> 161 | sum = sum + i; + ^ + 162 | } +Help: Consider using augmented assignment instead: sum += i +See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign + +[INFO] PreferAugmentedAssign: Prefer augmented assignment: sum += value +test/all/syntax.tact:170:9: + 169 | foreach (key, value in m) { +> 170 | sum = sum + value; + ^ + 171 | } +Help: Consider using augmented assignment instead: sum += value See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign \ No newline at end of file diff --git a/test/all/syntax.tact b/test/all/syntax.tact index 816aa9ac..b1e3d99b 100644 --- a/test/all/syntax.tact +++ b/test/all/syntax.tact @@ -59,3 +59,114 @@ fun test_loops() { let m: map = emptyMap(); foreach (k, v in m) {} } + +// +// Tact code covering all EffectFlags for testing CallGraph effects. +// +// Effects covered: +// - CALLS_SEND +// - CONTRACT_STATE_READ +// - CONTRACT_STATE_WRITE +// - ACCESSES_DATETIME +// - RANDOMNESS_USE +// - RANDOMNESS_SEED_INITIALIZATION +// + +contract EffectTestContract { + // State variable for read/write, initialized to 0 + someVariable: Int = 0; + destAddress: Address; + randomnessSeed: Int = 0; + + // Initialize destAddress in the constructor + init() { + self.destAddress = sender(); + } + + // Function that calls 'send' (CALLS_SEND) + fun funcWithSend() { + let amount: Int = 100; + send(SendParameters{ + to: self.destAddress, + value: amount + }); + } + + // Function that reads from contract state (CONTRACT_STATE_READ) + fun funcWithStateRead() { + let value: Int = self.someVariable; + } + + // Function that writes to contract state (CONTRACT_STATE_WRITE) + fun funcWithStateWrite() { + self.someVariable = 42; + } + + // Function that accesses datetime (ACCESSES_DATETIME) + fun funcWithDatetimeAccess() { + let currentTime: Int = now(); + } + + // Function that uses randomness (RANDOMNESS_USE) + fun funcWithRandomnessUse() { + let randValue: Int = random(1, 100); + } + + // Function that initializes randomness seed (RANDOMNESS_SEED_INITIALIZATION) + fun funcWithRandomnessSeedInit(seed: Int) { + self.randomnessSeed = seed; + } + + fun generatePseudoRandom(): Int { + self.randomnessSeed = (self.randomnessSeed * 1103515245 + 12345) % pow(2, 31); + return self.randomnessSeed; + } + + // Function that combines multiple effects + fun funcWithMultipleEffects() { + self.someVariable += random(1, 100); + send(SendParameters{ + to: self.destAddress, + value: self.someVariable + }); + let time: Int = now(); + self.funcWithRandomnessSeedInit(time); + } +} + +fun testTryCatch(a: Int) { + try { + a += 1; + } catch (err) { + dump(err); + } +} + +fun testLoops() { + let sum: Int = 0; + let i: Int = 0; + + while (i < 10) { + i = i + 1; + sum = sum + i; + } + + do { + i = i - 1; + sum = sum + i; + } until (i <= 0); + + repeat (10) { + i = i + 1; + sum = sum + i; + } + + let m: map = emptyMap(); + m.set(1, 10); + m.set(2, 20); + m.set(3, 30); + + foreach (key, value in m) { + sum = sum + value; + } +} From 9e994df69e5999e7f19267539800c9ac37215e38 Mon Sep 17 00:00:00 2001 From: Georgiy Komarov Date: Sun, 1 Dec 2024 13:55:01 +0000 Subject: [PATCH 5/5] finalize the pr --- CHANGELOG.md | 1 + src/detectors/builtin/ensurePrgSeed.ts | 18 +- src/detectors/builtin/sendInLoop.ts | 7 +- src/internals/ir/callGraph.ts | 257 +++++------ src/internals/tact/util.ts | 19 + src/tools/dumpCallgraph.ts | 50 ++- test/all/sample-jetton.expected.callgraph.dot | 114 +++-- .../all/sample-jetton.expected.callgraph.json | 400 ++++++------------ test/all/sample-jetton.expected.callgraph.mmd | 114 +++-- test/all/syntax.expected.callgraph.dot | 92 ++-- test/all/syntax.expected.callgraph.json | 208 +++------ test/all/syntax.expected.callgraph.mmd | 92 ++-- test/all/syntax.expected.cfg.dot | 77 ++-- test/all/syntax.expected.cfg.json | 166 ++------ test/all/syntax.expected.cfg.mmd | 58 +-- test/all/syntax.expected.out | 124 +++--- test/all/syntax.tact | 107 ++--- 17 files changed, 766 insertions(+), 1138 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f49a504e..5817914c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ShortCircuitCondition` detector: PR [#202](https://github.com/nowarp/misti/pull/202) - `PreferredStdlibApi` detector now suggest some preferred replacements for cell methods - Add Callgraph: PR [#185](https://github.com/nowarp/misti/pull/185) +- Add function effects to Callgraph: PR [#227](https://github.com/nowarp/misti/pull/227) ### Changed - `SuspiciousMessageMode` detector now suggests using SendDefaultMode instead of 0 for mode: PR [#199](https://github.com/nowarp/misti/pull/199/) diff --git a/src/detectors/builtin/ensurePrgSeed.ts b/src/detectors/builtin/ensurePrgSeed.ts index 71213797..9765d952 100644 --- a/src/detectors/builtin/ensurePrgSeed.ts +++ b/src/detectors/builtin/ensurePrgSeed.ts @@ -1,5 +1,9 @@ import { CompilationUnit } from "../../internals/ir"; -import { forEachExpression } from "../../internals/tact"; +import { + forEachExpression, + PRG_INIT_NAMES, + PRG_NATIVE_USE_NAMES, +} from "../../internals/tact"; import { MistiTactWarning, Severity } from "../../internals/warnings"; import { ASTDetector } from "../detector"; import { AstStaticCall, idText } from "@tact-lang/compiler/dist/grammar/ast"; @@ -38,20 +42,14 @@ export class EnsurePrgSeed extends ASTDetector { severity = Severity.MEDIUM; async check(cu: CompilationUnit): Promise { - const prgInitNames = new Set([ - "nativePrepareRandom", - "nativeRandomize", - "nativeRandomizeLt", - ]); - const prgUseNames = new Set(["nativeRandom", "nativeRandomInterval"]); const randomCalls = cu.ast.getProgramEntries().reduce( (acc, node) => { forEachExpression(node, (expr) => { if (expr.kind === "static_call") { - if (prgInitNames.has(idText(expr.function))) { + if (PRG_INIT_NAMES.has(idText(expr.function))) { acc.hasInitializer = true; } - if (prgUseNames.has(idText(expr.function))) { + if (PRG_NATIVE_USE_NAMES.has(idText(expr.function))) { acc.uses.push(expr); } } @@ -72,7 +70,7 @@ export class EnsurePrgSeed extends ASTDetector { `PRG seed should be initialized before using ${idText(use.function)}`, use.loc, { - suggestion: `Use ${Array.from(prgInitNames) + suggestion: `Use ${Array.from(PRG_INIT_NAMES) .map((name) => "`" + name + "`") .join( ", ", diff --git a/src/detectors/builtin/sendInLoop.ts b/src/detectors/builtin/sendInLoop.ts index 0d7bcd35..ec92156f 100644 --- a/src/detectors/builtin/sendInLoop.ts +++ b/src/detectors/builtin/sendInLoop.ts @@ -1,5 +1,5 @@ import { CompilationUnit } from "../../internals/ir"; -import { CallGraph, EffectFlags } from "../../internals/ir/callGraph"; +import { CallGraph, Effect } from "../../internals/ir/callGraph"; import { forEachStatement, foldExpressions } from "../../internals/tact"; import { isSendCall } from "../../internals/tact/util"; import { MistiTactWarning, Severity } from "../../internals/warnings"; @@ -120,10 +120,7 @@ export class SendInLoop extends ASTDetector { const calleeNodeId = callGraph.getNodeIdByName(calleeName); if (calleeNodeId !== undefined) { const calleeNode = callGraph.getNode(calleeNodeId); - if ( - calleeNode && - calleeNode.hasEffect(EffectFlags.CALLS_SEND) - ) { + if (calleeNode && calleeNode.hasEffect(Effect.Send)) { const functionName = calleeNode.name.includes("::") ? calleeNode.name.split("::").pop() : calleeNode.name; diff --git a/src/internals/ir/callGraph.ts b/src/internals/ir/callGraph.ts index 843f0ece..07eb7bf4 100644 --- a/src/internals/ir/callGraph.ts +++ b/src/internals/ir/callGraph.ts @@ -3,8 +3,15 @@ import { TactASTStore } from "./astStore"; import { IdxGenerator } from "./indices"; import { MistiContext } from "../../"; import { Logger } from "../../internals/logger"; -import { forEachExpression } from "../tact/iterators"; -import { isSendCall } from "../tact/util"; +import { findInExpressions, forEachExpression } from "../tact/iterators"; +import { + DATETIME_NAMES, + isSelfAccess, + isSendCall, + PRG_INIT_NAMES, + PRG_NATIVE_USE_NAMES, + PRG_SAFE_USE_NAMES, +} from "../tact/util"; import { AstFunctionDef, AstReceiver, @@ -18,26 +25,29 @@ import { AstNode, AstFieldAccess, AstStatement, - AstStatementAssign, - AstStatementAugmentedAssign, - AstStatementExpression, + idText, } from "@tact-lang/compiler/dist/grammar/ast"; +import { prettyPrint } from "@tact-lang/compiler/dist/prettyPrinter"; export type CGNodeId = number & { readonly brand: unique symbol }; export type CGEdgeId = number & { readonly brand: unique symbol }; /** - * Effect flags for CGNode. - * - * Each flag represents an effect or property of the function represented by the node. + * Effects flags for callgraph functions */ -export enum EffectFlags { - CALLS_SEND = 1 << 0, - CONTRACT_STATE_READ = 1 << 1, - CONTRACT_STATE_WRITE = 1 << 2, - ACCESSES_DATETIME = 1 << 3, - RANDOMNESS_USE = 1 << 4, - RANDOMNESS_SEED_INITIALIZATION = 1 << 5, +export enum Effect { + /** Uses functions that send funds. */ + Send = 1 << 0, + /** Reads contract's state. */ + StateRead = 1 << 1, + /** Writes contract's state. */ + StateWrite = 1 << 2, + /** Accesses datetime functions. */ + AccessDatetime = 1 << 3, + /** Uses PRG. */ + PrgUse = 1 << 4, + /** Inits PRG seed. */ + PrgSeedInit = 1 << 5, } /** @@ -73,7 +83,7 @@ class CGNode { * @param logger A logger instance for logging messages */ constructor( - public astId: number | undefined, + public astId: AstNode["id"] | undefined, public name: string, private logger: Logger, ) { @@ -83,13 +93,27 @@ class CGNode { } } - public addEffect(effect: EffectFlags) { + public addEffect(effect: Effect) { this.effects |= effect; } - public hasEffect(effect: EffectFlags): boolean { + public hasEffect(effect: Effect): boolean { return (this.effects & effect) !== 0; } + + /** + * Pretty-prints a signature of the function is available + */ + public signature(ast: TactASTStore): string | undefined { + if (!this.astId) return undefined; + const fun = ast.getFunction(this.astId); + if (!fun) return undefined; + const signature = prettyPrint(fun) + .split("{")[0] + .replace(/\s+/g, " ") + .trim(); + return signature; + } } /** @@ -101,12 +125,12 @@ export class CallGraph { private astIdToNodeId: Map = new Map(); private nameToNodeId: Map = new Map(); private edgesMap: Map = new Map(); - private logger: Logger; + private readonly logger: Logger; /** * @param ctx The MistiContext providing a logger and other utilities */ - constructor(private ctx: MistiContext) { + constructor(ctx: MistiContext) { this.logger = ctx.logger; } @@ -194,18 +218,18 @@ export class CallGraph { * @returns The constructed `CallGraph`. */ public build(astStore: TactASTStore): CallGraph { - for (const entry of astStore.getProgramEntries()) { + astStore.getProgramEntries().forEach((entry) => { if (entry.kind === "contract") { const contract = entry as AstContract; const contractName = contract.name.text; - for (const declaration of contract.declarations) { + contract.declarations.forEach((declaration) => { this.addContractDeclarationToGraph(declaration, contractName); - } + }); } else if (entry.kind === "function_def") { const func = entry as AstFunctionDef; this.addFunctionToGraph(func); } - } + }); this.analyzeFunctionCalls(astStore); return this; } @@ -289,7 +313,6 @@ export class CallGraph { for (const entry of astStore.getProgramEntries()) { if (entry.kind === "contract") { const contract = entry as AstContract; - const contractName = contract.name.text; for (const declaration of contract.declarations) { if ( declaration.kind === "function_def" || @@ -304,15 +327,8 @@ export class CallGraph { if (funcNodeId !== undefined) { const funcNode = this.getNode(funcNodeId); if (!funcNode) continue; - - if ("statements" in func && func.statements) { - for (const stmt of func.statements) { - this.processStatement(stmt, funcNodeId, contractName); - } - } - - forEachExpression(func, (expr) => { - this.processExpression(expr, funcNodeId, contractName); + func.statements.forEach((stmt) => { + this.processStatement(stmt, funcNodeId); }); } } @@ -323,13 +339,8 @@ export class CallGraph { if (funcNodeId !== undefined) { const funcNode = this.getNode(funcNodeId); if (!funcNode) continue; - if (func.statements) { - for (const stmt of func.statements) { - this.processStatement(stmt, funcNodeId); - } - } - forEachExpression(func, (expr) => { - this.processExpression(expr, funcNodeId); + func.statements.forEach((stmt) => { + this.processStatement(stmt, funcNodeId); }); } } @@ -341,32 +352,27 @@ export class CallGraph { * Also detects effects and sets corresponding flags on the function node. * @param stmt The statement to process. * @param callerId The node ID of the calling function. - * @param currentContractName The name of the contract, if applicable. + * @param contractName Name of the processed contract, if applicable. */ private processStatement( stmt: AstStatement, callerId: CGNodeId, - currentContractName?: string, + contractName?: string, ) { const funcNode = this.getNode(callerId); - if (!funcNode) { - return; + if (!funcNode) return; + if (isContractStateWrite(stmt)) { + funcNode.addEffect(Effect.StateWrite); } if ( stmt.kind === "statement_assign" || stmt.kind === "statement_augmentedassign" ) { - if (isContractStateWrite(stmt)) { - funcNode.addEffect(EffectFlags.CONTRACT_STATE_WRITE); - } - } else if (stmt.kind === "statement_expression") { - const stmtExpr = stmt as AstStatementExpression; - this.processExpression( - stmtExpr.expression, - callerId, - currentContractName, - ); - } + this.processExpression(stmt.expression, callerId, contractName); + } else + forEachExpression(stmt, (expr) => { + this.processExpression(expr, callerId, contractName); + }); } /** @@ -380,6 +386,7 @@ export class CallGraph { callerId: CGNodeId, currentContractName?: string, ) { + // Connect CG nodes if (expr.kind === "static_call" || expr.kind === "method_call") { const functionName = this.getFunctionCallName( expr as AstStaticCall | AstMethodCall, @@ -395,25 +402,23 @@ export class CallGraph { } } + // Add effects to the caller node const funcNode = this.getNode(callerId); - if (!funcNode) { - return; - } - if (isContractStateRead(expr)) { - funcNode.addEffect(EffectFlags.CONTRACT_STATE_READ); - } - if (accessesDatetime(expr)) { - funcNode.addEffect(EffectFlags.ACCESSES_DATETIME); - } - if (isRandomnessUseCall(expr)) { - funcNode.addEffect(EffectFlags.RANDOMNESS_USE); - } - if (isRandomnessSeedInitializationCall(expr)) { - funcNode.addEffect(EffectFlags.RANDOMNESS_SEED_INITIALIZATION); - } - if (isSendCall(expr)) { - funcNode.addEffect(EffectFlags.CALLS_SEND); + if (!funcNode) return; + if (expr.kind === "static_call") { + const functionName = idText(expr.function); + if (DATETIME_NAMES.has(functionName)) + funcNode.addEffect(Effect.AccessDatetime); + else if ( + PRG_NATIVE_USE_NAMES.has(functionName) || + PRG_SAFE_USE_NAMES.has(functionName) + ) + funcNode.addEffect(Effect.PrgUse); + else if (PRG_INIT_NAMES.has(functionName)) + funcNode.addEffect(Effect.PrgSeedInit); } + if (isSendCall(expr)) funcNode.addEffect(Effect.Send); + if (isContractStateRead(expr)) funcNode.addEffect(Effect.StateRead); } /** @@ -481,19 +486,8 @@ export class CallGraph { } } -/** - * Helper function to check if an expression represents 'self'. - * @param expr The expression to check. - * @returns True if the expression is 'self'; otherwise, false. - */ -export function isSelf(expr: AstExpression): boolean { - return expr.kind === "id" && (expr as AstId).text === "self"; -} - /** * Helper function to determine if an expression is a contract state read. - * @param expr The expression to check. - * @returns True if the expression reads from a state variable; otherwise, false. */ function isContractStateRead(expr: AstExpression): boolean { if (expr.kind === "field_access") { @@ -510,67 +504,42 @@ function isContractStateRead(expr: AstExpression): boolean { /** * Helper function to determine if a statement is a contract state write. - * @param stmt The statement to check. - * @returns True if the statement writes to a state variable; otherwise, false. - */ -function isContractStateWrite( - stmt: AstStatementAssign | AstStatementAugmentedAssign, -): boolean { - const pathExpr = stmt.path; - if (pathExpr.kind === "field_access") { - const fieldAccess = pathExpr as AstFieldAccess; - if (fieldAccess.aggregate.kind === "id") { - const idExpr = fieldAccess.aggregate as AstId; - if (idExpr.text === "self") { - return true; - } - } - } - // Note: This function does not currently detect state writes via method calls on state variables (e.g., Map.set()). - // Handling such cases may require more advanced analysis involving the symbol table or data flow analysis. - return false; -} - -/** - * Helper function to determine if an expression accesses the blockchain datetime. - * @param expr The expression to check. - * @returns True if the expression accesses datetime; otherwise, false. - */ -function accessesDatetime(expr: AstExpression): boolean { - if (expr.kind === "static_call") { - const staticCall = expr as AstStaticCall; - const functionName = staticCall.function?.text; - return functionName === "now" || functionName === "timestamp"; - } - return false; -} - -/** - * Helper function to determine if an expression is a randomness use call. - * @param expr The expression to check. - * @returns True if the expression uses randomness; otherwise, false. */ -function isRandomnessUseCall(expr: AstExpression): boolean { - if (expr.kind === "static_call") { - const staticCall = expr as AstStaticCall; - const functionName = staticCall.function?.text; - const prgUseNames = new Set(["nativeRandom", "nativeRandomInterval"]); - return prgUseNames.has(functionName || ""); +function isContractStateWrite(stmt: AstStatement): boolean { + if ( + stmt.kind === "statement_assign" || + stmt.kind === "statement_augmentedassign" + ) { + return isSelfAccess(stmt.path); } - return false; -} -/** - * Helper function to determine if an expression is a randomness seed initialization call. - * @param expr The expression to check. - * @returns True if the expression initializes the randomness seed; otherwise, false. - */ -function isRandomnessSeedInitializationCall(expr: AstExpression): boolean { - if (expr.kind === "static_call") { - const staticCall = expr as AstStaticCall; - const functionName = staticCall.function?.text; - const prgSeedNames = new Set(["setPrgSeed"]); - return prgSeedNames.has(functionName || ""); - } - return false; + // https://docs.tact-lang.org/book/maps/ + const MAP_MUTATING_OPERATIONS = new Set(["set", "del", "replace"]); + // For slices, cells, builders: + // https://github.com/tact-lang/tact/blob/08133e8418f3c6dcb49229b45cfeb7dd261bbe1f/stdlib/std/cells.tact#L75 + const CELL_MUTATING_OPERATIONS = new Set([ + "loadRef", + "loadBits", + "loadInt", + "loadUint", + "loadBool", + "loadBit", + "loadCoins", + "loadAddress", + "skipBits", + ]); + // Strings: + // https://github.com/tact-lang/tact/blob/08133e8418f3c6dcb49229b45cfeb7dd261bbe1f/stdlib/std/text.tact#L18 + const STRING_MUTATING_OPERATIONS = new Set(["append"]); + return ( + null !== + findInExpressions( + stmt, + (expr) => + expr.kind === "method_call" && + (MAP_MUTATING_OPERATIONS.has(idText(expr.method)) || + STRING_MUTATING_OPERATIONS.has(idText(expr.method)) || + CELL_MUTATING_OPERATIONS.has(idText(expr.method))), + ) + ); } diff --git a/src/internals/tact/util.ts b/src/internals/tact/util.ts index 08486b35..afeea8fa 100644 --- a/src/internals/tact/util.ts +++ b/src/internals/tact/util.ts @@ -23,6 +23,25 @@ import { prettyPrint } from "@tact-lang/compiler/dist/prettyPrinter"; import { Interval as RawInterval } from "ohm-js"; import * as path from "path"; +/** Stdlib functions that access datetime functions. */ +export const DATETIME_NAMES = new Set(["now", "timestamp"]); + +/** Stdlib functions that initialize PRG seed. */ +export const PRG_INIT_NAMES = new Set([ + "nativePrepareRandom", + "nativeRandomize", + "nativeRandomizeLt", +]); + +/** Native stdlib functions that use PRG. */ +export const PRG_NATIVE_USE_NAMES = new Set([ + "nativeRandom", + "nativeRandomInterval", +]); + +/** Safe Tact wrapper functions that use PRG. */ +export const PRG_SAFE_USE_NAMES = new Set(["random", "randomInt"]); + /** * Creates a concise string representation of `SrcInfo`. */ diff --git a/src/tools/dumpCallgraph.ts b/src/tools/dumpCallgraph.ts index bf3fd98a..d568c4c1 100644 --- a/src/tools/dumpCallgraph.ts +++ b/src/tools/dumpCallgraph.ts @@ -1,8 +1,8 @@ import { Tool } from "./tool"; import { ToolOutput } from "../cli/result"; import { MistiContext } from "../internals/context"; -import { CompilationUnit } from "../internals/ir"; -import { CallGraph } from "../internals/ir/callGraph"; +import { CompilationUnit, TactASTStore } from "../internals/ir"; +import { CallGraph, Effect } from "../internals/ir/callGraph"; import { unreachable } from "../internals/util"; import JSONbig from "json-bigint"; @@ -35,9 +35,15 @@ export class DumpCallGraph extends Tool { switch (format) { case "dot": - return this.makeOutput(cu, GraphvizDumper.dumpCallGraph(callGraph)); + return this.makeOutput( + cu, + GraphvizDumper.dumpCallGraph(cu.ast, callGraph), + ); case "mmd": - return this.makeOutput(cu, MermaidDumper.dumpCallGraph(callGraph)); + return this.makeOutput( + cu, + MermaidDumper.dumpCallGraph(cu.ast, callGraph), + ); case "json": return this.makeOutput(cu, JSONDumper.dumpCallGraph(callGraph)); default: @@ -60,15 +66,19 @@ export class DumpCallGraph extends Tool { * Utility class to dump the call graph in Mermaid format. */ class MermaidDumper { - public static dumpCallGraph(callGraph: CallGraph): string { + public static dumpCallGraph(ast: TactASTStore, callGraph: CallGraph): string { if (!callGraph || callGraph.getNodes().size === 0) { return 'graph TD\n empty["Empty Call Graph"]'; } let diagram = "graph TD\n"; callGraph.getNodes().forEach((node) => { const nodeId = `node_${node.idx}`; - const label = node.name?.replace(/"/g, '\\"') || "Unknown"; - diagram += ` ${nodeId}["${label}"]\n`; + const label = (node.signature(ast) || node.name || "Unknown").replace( + /"/g, + "'", + ); + const effects = getEffectsTooltip(node.effects); + diagram += ` ${nodeId}["${label}${effects}"]\n`; }); callGraph.getEdges().forEach((edge) => { const srcId = `node_${edge.src}`; @@ -83,15 +93,19 @@ class MermaidDumper { * Utility class to dump the call graph in DOT (Graphviz) format. */ class GraphvizDumper { - public static dumpCallGraph(callGraph: CallGraph): string { + public static dumpCallGraph(ast: TactASTStore, callGraph: CallGraph): string { if (!callGraph || callGraph.getNodes().size === 0) { return 'digraph "CallGraph" {\n node [shape=box];\n empty [label="Empty Call Graph"];\n}\n'; } let dot = `digraph "CallGraph" {\n node [shape=box];\n`; callGraph.getNodes().forEach((node) => { const nodeId = `node_${node.idx}`; - const label = node.name?.replace(/"/g, '\\"') || "Unknown"; - dot += ` ${nodeId} [label="${label}"];\n`; + const label = (node.signature(ast) || node.name || "Unknown").replace( + /"/g, + "'", + ); + const effects = getEffectsTooltip(node.effects); + dot += ` ${nodeId} [label="${label}${effects}"];\n`; }); callGraph.getEdges().forEach((edge) => { const srcId = `node_${edge.src}`; @@ -114,7 +128,7 @@ class JSONDumper { const data = { nodes: Array.from(callGraph.getNodes().values()).map((node) => ({ idx: node.idx, - name: node.name || "Unknown", + name: node.signature || node.name || "Unknown", inEdges: Array.from(node.inEdges || []), outEdges: Array.from(node.outEdges || []), })), @@ -127,3 +141,17 @@ class JSONDumper { return JSONbig.stringify(data, null, 2); } } + +function getEffectsTooltip(effects: number): string { + if (effects === 0) return ""; + + const effectsList = []; + if (effects & Effect.Send) effectsList.push("Send"); + if (effects & Effect.StateRead) effectsList.push("StateRead"); + if (effects & Effect.StateWrite) effectsList.push("StateWrite"); + if (effects & Effect.AccessDatetime) effectsList.push("AccessDatetime"); + if (effects & Effect.PrgUse) effectsList.push("PrgUse"); + if (effects & Effect.PrgSeedInit) effectsList.push("PrgSeedInit"); + + return effectsList.length > 0 ? `\n[${effectsList.join(",")}]` : ""; +} diff --git a/test/all/sample-jetton.expected.callgraph.dot b/test/all/sample-jetton.expected.callgraph.dot index b54580c4..be8a94e0 100644 --- a/test/all/sample-jetton.expected.callgraph.dot +++ b/test/all/sample-jetton.expected.callgraph.dot @@ -1,85 +1,81 @@ digraph "CallGraph" { node [shape=box]; - node_1 [label="SampleJetton::contract_init_1817"]; - node_2 [label="SampleJetton::receiver_1857"]; - node_3 [label="SampleJetton::receiver_1882"]; - node_4 [label="SampleJetton::receiver_1905"]; - node_5 [label="JettonDefaultWallet::contract_init_2359"]; - node_6 [label="JettonDefaultWallet::receiver_2517"]; - node_7 [label="JettonDefaultWallet::receiver_2687"]; - node_8 [label="JettonDefaultWallet::msgValue"]; - node_9 [label="JettonDefaultWallet::receiver_2832"]; - node_10 [label="JettonDefaultWallet::receiver_2876"]; - node_11 [label="JettonDefaultWallet::get_wallet_data"]; - node_12 [label="require"]; - node_13 [label="SampleJetton::mint"]; - node_14 [label="context"]; - node_15 [label="send"]; - node_16 [label="ctx::readForwardFee"]; - node_17 [label="min"]; - node_18 [label="ton"]; - node_19 [label="contractAddress"]; - node_20 [label="JettonDefaultWallet::toCell"]; - node_21 [label="myBalance"]; - node_22 [label="msg::loadUint"]; - node_23 [label="msg::loadCoins"]; - node_2 -> node_12; + node_1 [label="init(owner: Address, content: Cell, max_supply: Int) +[StateWrite]"]; + node_2 [label="receive(msg: Mint) +[StateRead]"]; + node_3 [label="receive('Mint: 100') +[StateRead]"]; + node_4 [label="receive('Owner: MintClose') +[StateRead,StateWrite]"]; + node_5 [label="init(master: Address, owner: Address) +[StateWrite]"]; + node_6 [label="receive(msg: TokenTransfer) +[Send,StateRead,StateWrite]"]; + node_7 [label="receive(msg: TokenTransferInternal) +[Send,StateRead,StateWrite]"]; + node_8 [label="get fun msgValue(value: Int): Int +[StateRead]"]; + node_9 [label="receive(msg: TokenBurn) +[Send,StateRead,StateWrite]"]; + node_10 [label="bounced(msg: Slice) +[StateWrite]"]; + node_11 [label="get fun get_wallet_data(): JettonWalletData +[StateRead]"]; + node_12 [label="context"]; + node_13 [label="require"]; + node_14 [label="mint"]; + node_15 [label="ctx::readForwardFee"]; + node_16 [label="min"]; + node_17 [label="ton"]; + node_18 [label="contractAddress"]; + node_19 [label="send"]; + node_20 [label="toCell"]; + node_21 [label="msgValue"]; + node_22 [label="myBalance"]; + node_23 [label="msg::loadUint"]; + node_24 [label="msg::loadCoins"]; node_2 -> node_12; node_2 -> node_13; - node_2 -> node_14; - node_2 -> node_12; - node_2 -> node_12; node_2 -> node_13; + node_2 -> node_14; node_3 -> node_12; node_3 -> node_13; node_3 -> node_14; - node_3 -> node_12; - node_3 -> node_13; node_4 -> node_12; - node_4 -> node_14; - node_4 -> node_12; - node_6 -> node_12; - node_6 -> node_12; + node_4 -> node_13; node_6 -> node_12; + node_6 -> node_13; node_6 -> node_15; - node_6 -> node_14; - node_6 -> node_12; - node_6 -> node_16; + node_6 -> node_15; + node_6 -> node_13; node_6 -> node_16; - node_6 -> node_12; node_6 -> node_17; + node_6 -> node_13; node_6 -> node_18; - node_6 -> node_12; node_6 -> node_19; - node_6 -> node_15; node_6 -> node_20; node_7 -> node_12; - node_7 -> node_14; - node_7 -> node_12; + node_7 -> node_13; + node_7 -> node_18; + node_7 -> node_13; node_7 -> node_19; - node_7 -> node_12; - node_7 -> node_15; node_7 -> node_20; - node_7 -> node_8; - node_7 -> node_16; + node_7 -> node_21; node_7 -> node_15; + node_7 -> node_19; node_7 -> node_20; - node_8 -> node_21; - node_8 -> node_17; - node_9 -> node_12; - node_9 -> node_12; - node_9 -> node_12; - node_9 -> node_15; - node_9 -> node_14; - node_9 -> node_12; - node_9 -> node_12; - node_9 -> node_16; + node_8 -> node_22; + node_8 -> node_16; node_9 -> node_12; + node_9 -> node_13; + node_9 -> node_13; node_9 -> node_15; + node_9 -> node_13; + node_9 -> node_19; node_9 -> node_20; - node_10 -> node_12; - node_10 -> node_22; - node_10 -> node_22; node_10 -> node_23; - node_10 -> node_12; + node_10 -> node_23; + node_10 -> node_24; + node_10 -> node_13; } diff --git a/test/all/sample-jetton.expected.callgraph.json b/test/all/sample-jetton.expected.callgraph.json index e9047f10..c7166931 100644 --- a/test/all/sample-jetton.expected.callgraph.json +++ b/test/all/sample-jetton.expected.callgraph.json @@ -2,62 +2,62 @@ "nodes": [ { "idx": 1, - "name": "SampleJetton::contract_init_1817", "inEdges": [], "outEdges": [] }, { "idx": 2, - "name": "SampleJetton::receiver_1857", "inEdges": [], "outEdges": [ 1, 2, 3, - 4, - 5, - 6, - 7 + 4 ] }, { "idx": 3, - "name": "SampleJetton::receiver_1882", "inEdges": [], "outEdges": [ - 8, - 9, - 10, - 11, - 12 + 5, + 6, + 7 ] }, { "idx": 4, - "name": "SampleJetton::receiver_1905", "inEdges": [], "outEdges": [ - 13, - 14, - 15 + 8, + 9 ] }, { "idx": 5, - "name": "JettonDefaultWallet::contract_init_2359", "inEdges": [], "outEdges": [] }, { "idx": 6, - "name": "JettonDefaultWallet::receiver_2517", "inEdges": [], "outEdges": [ + 10, + 11, + 12, + 13, + 14, + 15, 16, 17, 18, 19, - 20, + 20 + ] + }, + { + "idx": 7, + "inEdges": [], + "outEdges": [ 21, 22, 23, @@ -71,209 +71,159 @@ ] }, { - "idx": 7, - "name": "JettonDefaultWallet::receiver_2687", + "idx": 8, "inEdges": [], "outEdges": [ 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41 - ] - }, - { - "idx": 8, - "name": "JettonDefaultWallet::msgValue", - "inEdges": [ - 38 - ], - "outEdges": [ - 42, - 43 + 32 ] }, { "idx": 9, - "name": "JettonDefaultWallet::receiver_2832", "inEdges": [], "outEdges": [ - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54 + 33, + 34, + 35, + 36, + 37, + 38, + 39 ] }, { "idx": 10, - "name": "JettonDefaultWallet::receiver_2876", "inEdges": [], "outEdges": [ - 55, - 56, - 57, - 58, - 59 + 40, + 41, + 42, + 43 ] }, { "idx": 11, - "name": "JettonDefaultWallet::get_wallet_data", "inEdges": [], "outEdges": [] }, { "idx": 12, - "name": "require", "inEdges": [ 1, - 2, 5, - 6, 8, - 11, - 13, - 15, - 16, - 17, - 18, + 10, 21, - 24, - 27, - 31, - 33, - 35, - 44, - 45, - 46, - 49, - 50, - 52, - 55, - 59 + 33 ], "outEdges": [] }, { "idx": 13, - "name": "SampleJetton::mint", "inEdges": [ + 2, 3, - 7, + 6, 9, - 12 + 11, + 14, + 17, + 22, + 24, + 34, + 35, + 37, + 43 ], "outEdges": [] }, { "idx": 14, - "name": "context", "inEdges": [ 4, - 10, - 14, - 20, - 32, - 48 + 7 ], "outEdges": [] }, { "idx": 15, - "name": "send", "inEdges": [ - 19, - 29, - 36, - 40, - 47, - 53 + 12, + 13, + 28, + 36 ], "outEdges": [] }, { "idx": 16, - "name": "ctx::readForwardFee", "inEdges": [ - 22, - 23, - 39, - 51 + 15, + 32 ], "outEdges": [] }, { "idx": 17, - "name": "min", "inEdges": [ - 25, - 43 + 16 ], "outEdges": [] }, { "idx": 18, - "name": "ton", "inEdges": [ - 26 + 18, + 23 ], "outEdges": [] }, { "idx": 19, - "name": "contractAddress", "inEdges": [ - 28, - 34 + 19, + 25, + 29, + 38 ], "outEdges": [] }, { "idx": 20, - "name": "JettonDefaultWallet::toCell", "inEdges": [ + 20, + 26, 30, - 37, - 41, - 54 + 39 ], "outEdges": [] }, { "idx": 21, - "name": "myBalance", "inEdges": [ - 42 + 27 ], "outEdges": [] }, { "idx": 22, - "name": "msg::loadUint", "inEdges": [ - 56, - 57 + 31 ], "outEdges": [] }, { "idx": 23, - "name": "msg::loadCoins", "inEdges": [ - 58 + 40, + 41 + ], + "outEdges": [] + }, + { + "idx": 24, + "inEdges": [ + 42 ], "outEdges": [] } @@ -287,7 +237,7 @@ { "idx": 2, "src": 2, - "dst": 12 + "dst": 13 }, { "idx": 3, @@ -301,278 +251,198 @@ }, { "idx": 5, - "src": 2, - "dst": 12 - }, - { - "idx": 6, - "src": 2, - "dst": 12 - }, - { - "idx": 7, - "src": 2, - "dst": 13 - }, - { - "idx": 8, "src": 3, "dst": 12 }, { - "idx": 9, + "idx": 6, "src": 3, "dst": 13 }, { - "idx": 10, + "idx": 7, "src": 3, "dst": 14 }, { - "idx": 11, - "src": 3, - "dst": 12 - }, - { - "idx": 12, - "src": 3, - "dst": 13 - }, - { - "idx": 13, + "idx": 8, "src": 4, "dst": 12 }, { - "idx": 14, - "src": 4, - "dst": 14 - }, - { - "idx": 15, + "idx": 9, "src": 4, - "dst": 12 - }, - { - "idx": 16, - "src": 6, - "dst": 12 + "dst": 13 }, { - "idx": 17, + "idx": 10, "src": 6, "dst": 12 }, { - "idx": 18, + "idx": 11, "src": 6, - "dst": 12 + "dst": 13 }, { - "idx": 19, + "idx": 12, "src": 6, "dst": 15 }, { - "idx": 20, - "src": 6, - "dst": 14 - }, - { - "idx": 21, + "idx": 13, "src": 6, - "dst": 12 + "dst": 15 }, { - "idx": 22, + "idx": 14, "src": 6, - "dst": 16 + "dst": 13 }, { - "idx": 23, + "idx": 15, "src": 6, "dst": 16 }, { - "idx": 24, - "src": 6, - "dst": 12 - }, - { - "idx": 25, + "idx": 16, "src": 6, "dst": 17 }, { - "idx": 26, + "idx": 17, "src": 6, - "dst": 18 + "dst": 13 }, { - "idx": 27, + "idx": 18, "src": 6, - "dst": 12 + "dst": 18 }, { - "idx": 28, + "idx": 19, "src": 6, "dst": 19 }, { - "idx": 29, - "src": 6, - "dst": 15 - }, - { - "idx": 30, + "idx": 20, "src": 6, "dst": 20 }, { - "idx": 31, + "idx": 21, "src": 7, "dst": 12 }, { - "idx": 32, - "src": 7, - "dst": 14 - }, - { - "idx": 33, + "idx": 22, "src": 7, - "dst": 12 + "dst": 13 }, { - "idx": 34, + "idx": 23, "src": 7, - "dst": 19 + "dst": 18 }, { - "idx": 35, + "idx": 24, "src": 7, - "dst": 12 + "dst": 13 }, { - "idx": 36, + "idx": 25, "src": 7, - "dst": 15 + "dst": 19 }, { - "idx": 37, + "idx": 26, "src": 7, "dst": 20 }, { - "idx": 38, + "idx": 27, "src": 7, - "dst": 8 + "dst": 21 }, { - "idx": 39, + "idx": 28, "src": 7, - "dst": 16 + "dst": 15 }, { - "idx": 40, + "idx": 29, "src": 7, - "dst": 15 + "dst": 19 }, { - "idx": 41, + "idx": 30, "src": 7, "dst": 20 }, { - "idx": 42, + "idx": 31, "src": 8, - "dst": 21 + "dst": 22 }, { - "idx": 43, + "idx": 32, "src": 8, - "dst": 17 + "dst": 16 }, { - "idx": 44, + "idx": 33, "src": 9, "dst": 12 }, { - "idx": 45, + "idx": 34, "src": 9, - "dst": 12 + "dst": 13 }, { - "idx": 46, + "idx": 35, "src": 9, - "dst": 12 + "dst": 13 }, { - "idx": 47, + "idx": 36, "src": 9, "dst": 15 }, { - "idx": 48, - "src": 9, - "dst": 14 - }, - { - "idx": 49, - "src": 9, - "dst": 12 - }, - { - "idx": 50, - "src": 9, - "dst": 12 - }, - { - "idx": 51, - "src": 9, - "dst": 16 - }, - { - "idx": 52, + "idx": 37, "src": 9, - "dst": 12 + "dst": 13 }, { - "idx": 53, + "idx": 38, "src": 9, - "dst": 15 + "dst": 19 }, { - "idx": 54, + "idx": 39, "src": 9, "dst": 20 }, { - "idx": 55, - "src": 10, - "dst": 12 - }, - { - "idx": 56, + "idx": 40, "src": 10, - "dst": 22 + "dst": 23 }, { - "idx": 57, + "idx": 41, "src": 10, - "dst": 22 + "dst": 23 }, { - "idx": 58, + "idx": 42, "src": 10, - "dst": 23 + "dst": 24 }, { - "idx": 59, + "idx": 43, "src": 10, - "dst": 12 + "dst": 13 } ] } \ No newline at end of file diff --git a/test/all/sample-jetton.expected.callgraph.mmd b/test/all/sample-jetton.expected.callgraph.mmd index 19366b40..beb79431 100644 --- a/test/all/sample-jetton.expected.callgraph.mmd +++ b/test/all/sample-jetton.expected.callgraph.mmd @@ -1,83 +1,79 @@ graph TD - node_1["SampleJetton::contract_init_1817"] - node_2["SampleJetton::receiver_1857"] - node_3["SampleJetton::receiver_1882"] - node_4["SampleJetton::receiver_1905"] - node_5["JettonDefaultWallet::contract_init_2359"] - node_6["JettonDefaultWallet::receiver_2517"] - node_7["JettonDefaultWallet::receiver_2687"] - node_8["JettonDefaultWallet::msgValue"] - node_9["JettonDefaultWallet::receiver_2832"] - node_10["JettonDefaultWallet::receiver_2876"] - node_11["JettonDefaultWallet::get_wallet_data"] - node_12["require"] - node_13["SampleJetton::mint"] - node_14["context"] - node_15["send"] - node_16["ctx::readForwardFee"] - node_17["min"] - node_18["ton"] - node_19["contractAddress"] - node_20["JettonDefaultWallet::toCell"] - node_21["myBalance"] - node_22["msg::loadUint"] - node_23["msg::loadCoins"] - node_2 --> node_12 + node_1["init(owner: Address, content: Cell, max_supply: Int) +[StateWrite]"] + node_2["receive(msg: Mint) +[StateRead]"] + node_3["receive('Mint: 100') +[StateRead]"] + node_4["receive('Owner: MintClose') +[StateRead,StateWrite]"] + node_5["init(master: Address, owner: Address) +[StateWrite]"] + node_6["receive(msg: TokenTransfer) +[Send,StateRead,StateWrite]"] + node_7["receive(msg: TokenTransferInternal) +[Send,StateRead,StateWrite]"] + node_8["get fun msgValue(value: Int): Int +[StateRead]"] + node_9["receive(msg: TokenBurn) +[Send,StateRead,StateWrite]"] + node_10["bounced(msg: Slice) +[StateWrite]"] + node_11["get fun get_wallet_data(): JettonWalletData +[StateRead]"] + node_12["context"] + node_13["require"] + node_14["mint"] + node_15["ctx::readForwardFee"] + node_16["min"] + node_17["ton"] + node_18["contractAddress"] + node_19["send"] + node_20["toCell"] + node_21["msgValue"] + node_22["myBalance"] + node_23["msg::loadUint"] + node_24["msg::loadCoins"] node_2 --> node_12 node_2 --> node_13 - node_2 --> node_14 - node_2 --> node_12 - node_2 --> node_12 node_2 --> node_13 + node_2 --> node_14 node_3 --> node_12 node_3 --> node_13 node_3 --> node_14 - node_3 --> node_12 - node_3 --> node_13 node_4 --> node_12 - node_4 --> node_14 - node_4 --> node_12 - node_6 --> node_12 - node_6 --> node_12 + node_4 --> node_13 node_6 --> node_12 + node_6 --> node_13 node_6 --> node_15 - node_6 --> node_14 - node_6 --> node_12 - node_6 --> node_16 + node_6 --> node_15 + node_6 --> node_13 node_6 --> node_16 - node_6 --> node_12 node_6 --> node_17 + node_6 --> node_13 node_6 --> node_18 - node_6 --> node_12 node_6 --> node_19 - node_6 --> node_15 node_6 --> node_20 node_7 --> node_12 - node_7 --> node_14 - node_7 --> node_12 + node_7 --> node_13 + node_7 --> node_18 + node_7 --> node_13 node_7 --> node_19 - node_7 --> node_12 - node_7 --> node_15 node_7 --> node_20 - node_7 --> node_8 - node_7 --> node_16 + node_7 --> node_21 node_7 --> node_15 + node_7 --> node_19 node_7 --> node_20 - node_8 --> node_21 - node_8 --> node_17 - node_9 --> node_12 - node_9 --> node_12 - node_9 --> node_12 - node_9 --> node_15 - node_9 --> node_14 - node_9 --> node_12 - node_9 --> node_12 - node_9 --> node_16 + node_8 --> node_22 + node_8 --> node_16 node_9 --> node_12 + node_9 --> node_13 + node_9 --> node_13 node_9 --> node_15 + node_9 --> node_13 + node_9 --> node_19 node_9 --> node_20 - node_10 --> node_12 - node_10 --> node_22 - node_10 --> node_22 node_10 --> node_23 - node_10 --> node_12 + node_10 --> node_23 + node_10 --> node_24 + node_10 --> node_13 diff --git a/test/all/syntax.expected.callgraph.dot b/test/all/syntax.expected.callgraph.dot index 5d136332..a96d3163 100644 --- a/test/all/syntax.expected.callgraph.dot +++ b/test/all/syntax.expected.callgraph.dot @@ -1,52 +1,46 @@ digraph "CallGraph" { node [shape=box]; - node_1 [label="test_try"]; - node_2 [label="test_loops"]; - node_3 [label="testTryCatch"]; - node_4 [label="testLoops"]; - node_5 [label="TestContract::getter"]; - node_6 [label="TestContractF::test"]; - node_7 [label="TestContractT::test"]; - node_8 [label="TestContractT::receiver_1722"]; - node_9 [label="EffectTestContract::contract_init_1823"]; - node_10 [label="EffectTestContract::funcWithSend"]; - node_11 [label="EffectTestContract::funcWithStateRead"]; - node_12 [label="EffectTestContract::funcWithStateWrite"]; - node_13 [label="EffectTestContract::funcWithDatetimeAccess"]; - node_14 [label="EffectTestContract::funcWithRandomnessUse"]; - node_15 [label="EffectTestContract::funcWithRandomnessSeedInit"]; - node_16 [label="EffectTestContract::generatePseudoRandom"]; - node_17 [label="EffectTestContract::funcWithMultipleEffects"]; - node_18 [label="dump"]; - node_19 [label="emptyMap"]; - node_20 [label="m::set"]; - node_21 [label="TestContractT::getA"]; - node_22 [label="sender"]; - node_23 [label="send"]; - node_24 [label="now"]; - node_25 [label="random"]; - node_26 [label="pow"]; - node_1 -> node_18; - node_2 -> node_19; - node_3 -> node_18; - node_4 -> node_20; - node_4 -> node_20; - node_4 -> node_20; - node_4 -> node_19; - node_4 -> node_20; - node_4 -> node_20; - node_4 -> node_20; - node_7 -> node_21; - node_9 -> node_22; - node_10 -> node_23; - node_10 -> node_23; - node_13 -> node_24; - node_14 -> node_25; - node_16 -> node_26; - node_17 -> node_23; - node_17 -> node_15; - node_17 -> node_25; - node_17 -> node_23; - node_17 -> node_24; - node_17 -> node_15; + node_1 [label="fun test_try(a: Int)"]; + node_2 [label="fun test_loops()"]; + node_3 [label="fun testTryCatch(a: Int)"]; + node_4 [label="fun testLoops() +[StateWrite]"]; + node_5 [label="override get fun getter(): Int"]; + node_6 [label="fun test()"]; + node_7 [label="fun test(): Int"]; + node_8 [label="external()"]; + node_9 [label="init() +[StateWrite]"]; + node_10 [label="fun funcWithSend() +[Send,StateRead]"]; + node_11 [label="fun funcWithStateRead() +[StateRead]"]; + node_12 [label="fun funcWithStateWrite() +[StateWrite]"]; + node_13 [label="fun funcWithMultipleEffects() +[AccessDatetime,PrgUse,PrgSeedInit]"]; + node_14 [label="dump"]; + node_15 [label="emptyMap"]; + node_16 [label="m::set"]; + node_17 [label="getA"]; + node_18 [label="sender"]; + node_19 [label="send"]; + node_20 [label="newAddress"]; + node_21 [label="now"]; + node_22 [label="random"]; + node_23 [label="nativeRandomizeLt"]; + node_1 -> node_14; + node_2 -> node_15; + node_3 -> node_14; + node_4 -> node_15; + node_4 -> node_16; + node_4 -> node_16; + node_4 -> node_16; + node_7 -> node_17; + node_9 -> node_18; + node_10 -> node_19; + node_12 -> node_20; + node_13 -> node_21; + node_13 -> node_22; + node_13 -> node_23; } diff --git a/test/all/syntax.expected.callgraph.json b/test/all/syntax.expected.callgraph.json index 7fabb450..d933ed51 100644 --- a/test/all/syntax.expected.callgraph.json +++ b/test/all/syntax.expected.callgraph.json @@ -2,7 +2,6 @@ "nodes": [ { "idx": 1, - "name": "test_try", "inEdges": [], "outEdges": [ 1 @@ -10,7 +9,6 @@ }, { "idx": 2, - "name": "test_loops", "inEdges": [], "outEdges": [ 2 @@ -18,7 +16,6 @@ }, { "idx": 3, - "name": "testTryCatch", "inEdges": [], "outEdges": [ 3 @@ -26,200 +23,142 @@ }, { "idx": 4, - "name": "testLoops", "inEdges": [], "outEdges": [ 4, 5, 6, - 7, - 8, - 9, - 10 + 7 ] }, { "idx": 5, - "name": "TestContract::getter", "inEdges": [], "outEdges": [] }, { "idx": 6, - "name": "TestContractF::test", "inEdges": [], "outEdges": [] }, { "idx": 7, - "name": "TestContractT::test", "inEdges": [], "outEdges": [ - 11 + 8 ] }, { "idx": 8, - "name": "TestContractT::receiver_1722", "inEdges": [], "outEdges": [] }, { "idx": 9, - "name": "EffectTestContract::contract_init_1823", "inEdges": [], "outEdges": [ - 12 + 9 ] }, { "idx": 10, - "name": "EffectTestContract::funcWithSend", "inEdges": [], "outEdges": [ - 13, - 14 + 10 ] }, { "idx": 11, - "name": "EffectTestContract::funcWithStateRead", "inEdges": [], "outEdges": [] }, { "idx": 12, - "name": "EffectTestContract::funcWithStateWrite", - "inEdges": [], - "outEdges": [] - }, - { - "idx": 13, - "name": "EffectTestContract::funcWithDatetimeAccess", "inEdges": [], "outEdges": [ - 15 + 11 ] }, { - "idx": 14, - "name": "EffectTestContract::funcWithRandomnessUse", + "idx": 13, "inEdges": [], "outEdges": [ - 16 + 12, + 13, + 14 ] }, { - "idx": 15, - "name": "EffectTestContract::funcWithRandomnessSeedInit", + "idx": 14, "inEdges": [ - 19, - 23 + 1, + 3 ], "outEdges": [] }, { - "idx": 16, - "name": "EffectTestContract::generatePseudoRandom", - "inEdges": [], - "outEdges": [ - 17 - ] - }, - { - "idx": 17, - "name": "EffectTestContract::funcWithMultipleEffects", - "inEdges": [], - "outEdges": [ - 18, - 19, - 20, - 21, - 22, - 23 - ] - }, - { - "idx": 18, - "name": "dump", + "idx": 15, "inEdges": [ - 1, - 3 + 2, + 4 ], "outEdges": [] }, { - "idx": 19, - "name": "emptyMap", + "idx": 16, "inEdges": [ - 2, + 5, + 6, 7 ], "outEdges": [] }, { - "idx": 20, - "name": "m::set", + "idx": 17, "inEdges": [ - 4, - 5, - 6, - 8, - 9, - 10 + 8 ], "outEdges": [] }, { - "idx": 21, - "name": "TestContractT::getA", + "idx": 18, "inEdges": [ - 11 + 9 ], "outEdges": [] }, { - "idx": 22, - "name": "sender", + "idx": 19, "inEdges": [ - 12 + 10 ], "outEdges": [] }, { - "idx": 23, - "name": "send", + "idx": 20, "inEdges": [ - 13, - 14, - 18, - 21 + 11 ], "outEdges": [] }, { - "idx": 24, - "name": "now", + "idx": 21, "inEdges": [ - 15, - 22 + 12 ], "outEdges": [] }, { - "idx": 25, - "name": "random", + "idx": 22, "inEdges": [ - 16, - 20 + 13 ], "outEdges": [] }, { - "idx": 26, - "name": "pow", + "idx": 23, "inEdges": [ - 17 + 14 ], "outEdges": [] } @@ -228,117 +167,72 @@ { "idx": 1, "src": 1, - "dst": 18 + "dst": 14 }, { "idx": 2, "src": 2, - "dst": 19 + "dst": 15 }, { "idx": 3, "src": 3, - "dst": 18 + "dst": 14 }, { "idx": 4, "src": 4, - "dst": 20 + "dst": 15 }, { "idx": 5, "src": 4, - "dst": 20 + "dst": 16 }, { "idx": 6, "src": 4, - "dst": 20 + "dst": 16 }, { "idx": 7, "src": 4, - "dst": 19 + "dst": 16 }, { "idx": 8, - "src": 4, - "dst": 20 + "src": 7, + "dst": 17 }, { "idx": 9, - "src": 4, - "dst": 20 + "src": 9, + "dst": 18 }, { "idx": 10, - "src": 4, - "dst": 20 + "src": 10, + "dst": 19 }, { "idx": 11, - "src": 7, - "dst": 21 + "src": 12, + "dst": 20 }, { "idx": 12, - "src": 9, - "dst": 22 + "src": 13, + "dst": 21 }, { "idx": 13, - "src": 10, - "dst": 23 + "src": 13, + "dst": 22 }, { "idx": 14, - "src": 10, - "dst": 23 - }, - { - "idx": 15, "src": 13, - "dst": 24 - }, - { - "idx": 16, - "src": 14, - "dst": 25 - }, - { - "idx": 17, - "src": 16, - "dst": 26 - }, - { - "idx": 18, - "src": 17, - "dst": 23 - }, - { - "idx": 19, - "src": 17, - "dst": 15 - }, - { - "idx": 20, - "src": 17, - "dst": 25 - }, - { - "idx": 21, - "src": 17, "dst": 23 - }, - { - "idx": 22, - "src": 17, - "dst": 24 - }, - { - "idx": 23, - "src": 17, - "dst": 15 } ] } \ No newline at end of file diff --git a/test/all/syntax.expected.callgraph.mmd b/test/all/syntax.expected.callgraph.mmd index 9e3015bd..dc7d854c 100644 --- a/test/all/syntax.expected.callgraph.mmd +++ b/test/all/syntax.expected.callgraph.mmd @@ -1,50 +1,44 @@ graph TD - node_1["test_try"] - node_2["test_loops"] - node_3["testTryCatch"] - node_4["testLoops"] - node_5["TestContract::getter"] - node_6["TestContractF::test"] - node_7["TestContractT::test"] - node_8["TestContractT::receiver_1722"] - node_9["EffectTestContract::contract_init_1823"] - node_10["EffectTestContract::funcWithSend"] - node_11["EffectTestContract::funcWithStateRead"] - node_12["EffectTestContract::funcWithStateWrite"] - node_13["EffectTestContract::funcWithDatetimeAccess"] - node_14["EffectTestContract::funcWithRandomnessUse"] - node_15["EffectTestContract::funcWithRandomnessSeedInit"] - node_16["EffectTestContract::generatePseudoRandom"] - node_17["EffectTestContract::funcWithMultipleEffects"] - node_18["dump"] - node_19["emptyMap"] - node_20["m::set"] - node_21["TestContractT::getA"] - node_22["sender"] - node_23["send"] - node_24["now"] - node_25["random"] - node_26["pow"] - node_1 --> node_18 - node_2 --> node_19 - node_3 --> node_18 - node_4 --> node_20 - node_4 --> node_20 - node_4 --> node_20 - node_4 --> node_19 - node_4 --> node_20 - node_4 --> node_20 - node_4 --> node_20 - node_7 --> node_21 - node_9 --> node_22 - node_10 --> node_23 - node_10 --> node_23 - node_13 --> node_24 - node_14 --> node_25 - node_16 --> node_26 - node_17 --> node_23 - node_17 --> node_15 - node_17 --> node_25 - node_17 --> node_23 - node_17 --> node_24 - node_17 --> node_15 + node_1["fun test_try(a: Int)"] + node_2["fun test_loops()"] + node_3["fun testTryCatch(a: Int)"] + node_4["fun testLoops() +[StateWrite]"] + node_5["override get fun getter(): Int"] + node_6["fun test()"] + node_7["fun test(): Int"] + node_8["external()"] + node_9["init() +[StateWrite]"] + node_10["fun funcWithSend() +[Send,StateRead]"] + node_11["fun funcWithStateRead() +[StateRead]"] + node_12["fun funcWithStateWrite() +[StateWrite]"] + node_13["fun funcWithMultipleEffects() +[AccessDatetime,PrgUse,PrgSeedInit]"] + node_14["dump"] + node_15["emptyMap"] + node_16["m::set"] + node_17["getA"] + node_18["sender"] + node_19["send"] + node_20["newAddress"] + node_21["now"] + node_22["random"] + node_23["nativeRandomizeLt"] + node_1 --> node_14 + node_2 --> node_15 + node_3 --> node_14 + node_4 --> node_15 + node_4 --> node_16 + node_4 --> node_16 + node_4 --> node_16 + node_7 --> node_17 + node_9 --> node_18 + node_10 --> node_19 + node_12 --> node_20 + node_13 --> node_21 + node_13 --> node_22 + node_13 --> node_23 diff --git a/test/all/syntax.expected.cfg.dot b/test/all/syntax.expected.cfg.dot index 2ed197ad..0d32e039 100644 --- a/test/all/syntax.expected.cfg.dot +++ b/test/all/syntax.expected.cfg.dot @@ -108,52 +108,33 @@ digraph "syntax" { subgraph "cluster_TestContractT__receive_external_fallback_1722" { label="TestContractT__receive_external_fallback_1722"; } - subgraph "cluster_EffectTestContract__init_1942" { - label="EffectTestContract__init_1942"; - "EffectTestContract__init_1942_109" [label="self.destAddress = sender()",style=filled,fillcolor="#66A7DB"]; - } - subgraph "cluster_EffectTestContract__funcWithSend" { - label="EffectTestContract__funcWithSend"; - "EffectTestContract__funcWithSend_110" [label="let amount: Int = 100"]; - "EffectTestContract__funcWithSend_111" [label="send(SendParameters{to: self.destAddress, value: amount})",style=filled,fillcolor="#66A7DB"]; - "EffectTestContract__funcWithSend_110" -> "EffectTestContract__funcWithSend_111"; - } - subgraph "cluster_EffectTestContract__funcWithStateRead" { - label="EffectTestContract__funcWithStateRead"; - "EffectTestContract__funcWithStateRead_112" [label="let value: Int = self.someVariable",style=filled,fillcolor="#66A7DB"]; - } - subgraph "cluster_EffectTestContract__funcWithStateWrite" { - label="EffectTestContract__funcWithStateWrite"; - "EffectTestContract__funcWithStateWrite_113" [label="self.someVariable = 42",style=filled,fillcolor="#66A7DB"]; - } - subgraph "cluster_EffectTestContract__funcWithDatetimeAccess" { - label="EffectTestContract__funcWithDatetimeAccess"; - "EffectTestContract__funcWithDatetimeAccess_114" [label="let currentTime: Int = now()",style=filled,fillcolor="#66A7DB"]; - } - subgraph "cluster_EffectTestContract__funcWithRandomnessUse" { - label="EffectTestContract__funcWithRandomnessUse"; - "EffectTestContract__funcWithRandomnessUse_115" [label="let randValue: Int = random(1, 100)",style=filled,fillcolor="#66A7DB"]; - } - subgraph "cluster_EffectTestContract__funcWithRandomnessSeedInit" { - label="EffectTestContract__funcWithRandomnessSeedInit"; - "EffectTestContract__funcWithRandomnessSeedInit_116" [label="self.randomnessSeed = seed",style=filled,fillcolor="#66A7DB"]; - } - subgraph "cluster_EffectTestContract__generatePseudoRandom" { - label="EffectTestContract__generatePseudoRandom"; - "EffectTestContract__generatePseudoRandom_117" [label="self.randomnessSeed = (self.randomnessSeed * 1103515245 + 12345) % pow(2, 31)"]; - "EffectTestContract__generatePseudoRandom_118" [label="return self.randomnessSeed",style=filled,fillcolor="#66A7DB"]; - "EffectTestContract__generatePseudoRandom_117" -> "EffectTestContract__generatePseudoRandom_118"; - } - subgraph "cluster_EffectTestContract__funcWithMultipleEffects" { - label="EffectTestContract__funcWithMultipleEffects"; - "EffectTestContract__funcWithMultipleEffects_119" [label="self.someVariable += random(1, 100)"]; - "EffectTestContract__funcWithMultipleEffects_120" [label="send(SendParameters{to: self.destAddress, value: self.someVariable})"]; - "EffectTestContract__funcWithMultipleEffects_121" [label="let time: Int = now()"]; - "EffectTestContract__funcWithMultipleEffects_122" [label="self.funcWithRandomnessSeedInit(time)",style=filled,fillcolor="#66A7DB"]; - "EffectTestContract__funcWithMultipleEffects_119" -> "EffectTestContract__funcWithMultipleEffects_120"; - "EffectTestContract__funcWithMultipleEffects_120" -> "EffectTestContract__funcWithMultipleEffects_121"; - "EffectTestContract__funcWithMultipleEffects_121" -> "EffectTestContract__funcWithMultipleEffects_122"; - } -"119" -> "26"; -"120" -> "35"; + subgraph "cluster_EffectTest__init_1971" { + label="EffectTest__init_1971"; + "EffectTest__init_1971_109" [label="self.addr = sender()",style=filled,fillcolor="#66A7DB"]; + } + subgraph "cluster_EffectTest__funcWithSend" { + label="EffectTest__funcWithSend"; + "EffectTest__funcWithSend_110" [label="let amount: Int = 100"]; + "EffectTest__funcWithSend_111" [label="send(SendParameters{to: self.addr, value: amount})",style=filled,fillcolor="#66A7DB"]; + "EffectTest__funcWithSend_110" -> "EffectTest__funcWithSend_111"; + } + subgraph "cluster_EffectTest__funcWithStateRead" { + label="EffectTest__funcWithStateRead"; + "EffectTest__funcWithStateRead_112" [label="let value = self.addr",style=filled,fillcolor="#66A7DB"]; + } + subgraph "cluster_EffectTest__funcWithStateWrite" { + label="EffectTest__funcWithStateWrite"; + "EffectTest__funcWithStateWrite_113" [label="self.addr = newAddress(0, 0)",style=filled,fillcolor="#66A7DB"]; + } + subgraph "cluster_EffectTest__funcWithMultipleEffects" { + label="EffectTest__funcWithMultipleEffects"; + "EffectTest__funcWithMultipleEffects_114" [label="let currentTime: Int = now()"]; + "EffectTest__funcWithMultipleEffects_115" [label="let randValue: Int = random(1, 100)"]; + "EffectTest__funcWithMultipleEffects_116" [label="if (1 > 42)"]; + "EffectTest__funcWithMultipleEffects_117" [label="nativeRandomizeLt()",style=filled,fillcolor="#66A7DB"]; + "EffectTest__funcWithMultipleEffects_114" -> "EffectTest__funcWithMultipleEffects_115"; + "EffectTest__funcWithMultipleEffects_115" -> "EffectTest__funcWithMultipleEffects_116"; + "EffectTest__funcWithMultipleEffects_116" -> "EffectTest__funcWithMultipleEffects_117"; + } +"115" -> "26"; } diff --git a/test/all/syntax.expected.cfg.json b/test/all/syntax.expected.cfg.json index 420f8480..4e6f75bf 100644 --- a/test/all/syntax.expected.cfg.json +++ b/test/all/syntax.expected.cfg.json @@ -320,7 +320,7 @@ "nodes": [ { "id": 87, - "stmtID": 1955, + "stmtID": 1817, "srcEdges": [], "dstEdges": [ 78, @@ -329,7 +329,7 @@ }, { "id": 88, - "stmtID": 1949, + "stmtID": 1811, "srcEdges": [ 78 ], @@ -337,7 +337,7 @@ }, { "id": 89, - "stmtID": 1954, + "stmtID": 1816, "srcEdges": [ 79 ], @@ -364,7 +364,7 @@ "nodes": [ { "id": 90, - "stmtID": 1961, + "stmtID": 1823, "srcEdges": [], "dstEdges": [ 80 @@ -372,7 +372,7 @@ }, { "id": 91, - "stmtID": 1965, + "stmtID": 1827, "srcEdges": [ 80 ], @@ -382,7 +382,7 @@ }, { "id": 92, - "stmtID": 1979, + "stmtID": 1841, "srcEdges": [ 81, 84 @@ -394,7 +394,7 @@ }, { "id": 93, - "stmtID": 1973, + "stmtID": 1835, "srcEdges": [ 82 ], @@ -404,7 +404,7 @@ }, { "id": 94, - "stmtID": 1978, + "stmtID": 1840, "srcEdges": [ 83 ], @@ -414,7 +414,7 @@ }, { "id": 95, - "stmtID": 1993, + "stmtID": 1855, "srcEdges": [ 85, 88 @@ -426,7 +426,7 @@ }, { "id": 96, - "stmtID": 1987, + "stmtID": 1849, "srcEdges": [ 86 ], @@ -436,7 +436,7 @@ }, { "id": 97, - "stmtID": 1992, + "stmtID": 1854, "srcEdges": [ 87 ], @@ -446,7 +446,7 @@ }, { "id": 98, - "stmtID": 2005, + "stmtID": 1867, "srcEdges": [ 89, 92 @@ -458,7 +458,7 @@ }, { "id": 99, - "stmtID": 1999, + "stmtID": 1861, "srcEdges": [ 90 ], @@ -468,7 +468,7 @@ }, { "id": 100, - "stmtID": 2004, + "stmtID": 1866, "srcEdges": [ 91 ], @@ -478,7 +478,7 @@ }, { "id": 101, - "stmtID": 2012, + "stmtID": 1874, "srcEdges": [ 93 ], @@ -488,7 +488,7 @@ }, { "id": 102, - "stmtID": 2018, + "stmtID": 1880, "srcEdges": [ 94 ], @@ -498,7 +498,7 @@ }, { "id": 103, - "stmtID": 2024, + "stmtID": 1886, "srcEdges": [ 95 ], @@ -508,7 +508,7 @@ }, { "id": 104, - "stmtID": 2030, + "stmtID": 1892, "srcEdges": [ 96 ], @@ -518,7 +518,7 @@ }, { "id": 105, - "stmtID": 2039, + "stmtID": 1901, "srcEdges": [ 97, 99 @@ -529,7 +529,7 @@ }, { "id": 106, - "stmtID": 2038, + "stmtID": 1900, "srcEdges": [ 98 ], @@ -702,15 +702,15 @@ ] }, { - "name": "EffectTestContract", + "name": "EffectTest", "methods": [ { - "name": "EffectTestContract.init_1942", + "name": "EffectTest.init_1971", "cfg": { "nodes": [ { "id": 109, - "stmtID": 1822, + "stmtID": 1912, "srcEdges": [], "dstEdges": [] } @@ -719,12 +719,12 @@ } }, { - "name": "EffectTestContract.funcWithSend", + "name": "EffectTest.funcWithSend", "cfg": { "nodes": [ { "id": 110, - "stmtID": 1828, + "stmtID": 1918, "srcEdges": [], "dstEdges": [ 100 @@ -732,7 +732,7 @@ }, { "id": 111, - "stmtID": 1841, + "stmtID": 1931, "srcEdges": [ 100 ], @@ -749,12 +749,12 @@ } }, { - "name": "EffectTestContract.funcWithStateRead", + "name": "EffectTest.funcWithStateRead", "cfg": { "nodes": [ { "id": 112, - "stmtID": 1849, + "stmtID": 1938, "srcEdges": [], "dstEdges": [] } @@ -763,12 +763,12 @@ } }, { - "name": "EffectTestContract.funcWithStateWrite", + "name": "EffectTest.funcWithStateWrite", "cfg": { "nodes": [ { "id": 113, - "stmtID": 1856, + "stmtID": 1948, "srcEdges": [], "dstEdges": [] } @@ -777,92 +777,30 @@ } }, { - "name": "EffectTestContract.funcWithDatetimeAccess", + "name": "EffectTest.funcWithMultipleEffects", "cfg": { "nodes": [ { "id": 114, - "stmtID": 1863, - "srcEdges": [], - "dstEdges": [] - } - ], - "edges": [] - } - }, - { - "name": "EffectTestContract.funcWithRandomnessUse", - "cfg": { - "nodes": [ - { - "id": 115, - "stmtID": 1872, - "srcEdges": [], - "dstEdges": [] - } - ], - "edges": [] - } - }, - { - "name": "EffectTestContract.funcWithRandomnessSeedInit", - "cfg": { - "nodes": [ - { - "id": 116, - "stmtID": 1882, - "srcEdges": [], - "dstEdges": [] - } - ], - "edges": [] - } - }, - { - "name": "EffectTestContract.generatePseudoRandom", - "cfg": { - "nodes": [ - { - "id": 117, - "stmtID": 1901, + "stmtID": 1955, "srcEdges": [], "dstEdges": [ 101 ] }, { - "id": 118, - "stmtID": 1905, + "id": 115, + "stmtID": 1962, "srcEdges": [ 101 ], - "dstEdges": [] - } - ], - "edges": [ - { - "id": 101, - "src": 117, - "dst": 118 - } - ] - } - }, - { - "name": "EffectTestContract.funcWithMultipleEffects", - "cfg": { - "nodes": [ - { - "id": 119, - "stmtID": 1915, - "srcEdges": [], "dstEdges": [ 102 ] }, { - "id": 120, - "stmtID": 1930, + "id": 116, + "stmtID": 1969, "srcEdges": [ 102 ], @@ -871,39 +809,29 @@ ] }, { - "id": 121, - "stmtID": 1935, + "id": 117, + "stmtID": 1968, "srcEdges": [ 103 ], - "dstEdges": [ - 104 - ] - }, - { - "id": 122, - "stmtID": 1940, - "srcEdges": [ - 104 - ], "dstEdges": [] } ], "edges": [ { - "id": 102, - "src": 119, - "dst": 120 + "id": 101, + "src": 114, + "dst": 115 }, { - "id": 103, - "src": 120, - "dst": 121 + "id": 102, + "src": 115, + "dst": 116 }, { - "id": 104, - "src": 121, - "dst": 122 + "id": 103, + "src": 116, + "dst": 117 } ] } diff --git a/test/all/syntax.expected.cfg.mmd b/test/all/syntax.expected.cfg.mmd index 547703ff..1195903c 100644 --- a/test/all/syntax.expected.cfg.mmd +++ b/test/all/syntax.expected.cfg.mmd @@ -99,40 +99,26 @@ subgraph TestContractT__test end subgraph TestContractT__receive_external_fallback_1722 end -subgraph EffectTestContract__init_1942 - EffectTestContract__init_1942_109["self.destAddress = sender()"]:::exitNode -end -subgraph EffectTestContract__funcWithSend - EffectTestContract__funcWithSend_110["let amount: Int = 100"] - EffectTestContract__funcWithSend_111["send(SendParameters{to: self.destAddress, value: amount})"]:::exitNode - EffectTestContract__funcWithSend_110 --> EffectTestContract__funcWithSend_111 -end -subgraph EffectTestContract__funcWithStateRead - EffectTestContract__funcWithStateRead_112["let value: Int = self.someVariable"]:::exitNode -end -subgraph EffectTestContract__funcWithStateWrite - EffectTestContract__funcWithStateWrite_113["self.someVariable = 42"]:::exitNode -end -subgraph EffectTestContract__funcWithDatetimeAccess - EffectTestContract__funcWithDatetimeAccess_114["let currentTime: Int = now()"]:::exitNode -end -subgraph EffectTestContract__funcWithRandomnessUse - EffectTestContract__funcWithRandomnessUse_115["let randValue: Int = random(1, 100)"]:::exitNode -end -subgraph EffectTestContract__funcWithRandomnessSeedInit - EffectTestContract__funcWithRandomnessSeedInit_116["self.randomnessSeed = seed"]:::exitNode -end -subgraph EffectTestContract__generatePseudoRandom - EffectTestContract__generatePseudoRandom_117["self.randomnessSeed = (self.randomnessSeed * 1103515245 + 12345) % pow(2, 31)"] - EffectTestContract__generatePseudoRandom_118["return self.randomnessSeed"]:::exitNode - EffectTestContract__generatePseudoRandom_117 --> EffectTestContract__generatePseudoRandom_118 -end -subgraph EffectTestContract__funcWithMultipleEffects - EffectTestContract__funcWithMultipleEffects_119["self.someVariable += random(1, 100)"] - EffectTestContract__funcWithMultipleEffects_120["send(SendParameters{to: self.destAddress, value: self.someVariable})"] - EffectTestContract__funcWithMultipleEffects_121["let time: Int = now()"] - EffectTestContract__funcWithMultipleEffects_122["self.funcWithRandomnessSeedInit(time)"]:::exitNode - EffectTestContract__funcWithMultipleEffects_119 --> EffectTestContract__funcWithMultipleEffects_120 - EffectTestContract__funcWithMultipleEffects_120 --> EffectTestContract__funcWithMultipleEffects_121 - EffectTestContract__funcWithMultipleEffects_121 --> EffectTestContract__funcWithMultipleEffects_122 +subgraph EffectTest__init_1971 + EffectTest__init_1971_109["self.addr = sender()"]:::exitNode +end +subgraph EffectTest__funcWithSend + EffectTest__funcWithSend_110["let amount: Int = 100"] + EffectTest__funcWithSend_111["send(SendParameters{to: self.addr, value: amount})"]:::exitNode + EffectTest__funcWithSend_110 --> EffectTest__funcWithSend_111 +end +subgraph EffectTest__funcWithStateRead + EffectTest__funcWithStateRead_112["let value = self.addr"]:::exitNode +end +subgraph EffectTest__funcWithStateWrite + EffectTest__funcWithStateWrite_113["self.addr = newAddress(0, 0)"]:::exitNode +end +subgraph EffectTest__funcWithMultipleEffects + EffectTest__funcWithMultipleEffects_114["let currentTime: Int = now()"] + EffectTest__funcWithMultipleEffects_115["let randValue: Int = random(1, 100)"] + EffectTest__funcWithMultipleEffects_116["if (1 > 42)"] + EffectTest__funcWithMultipleEffects_117["nativeRandomizeLt()"]:::exitNode + EffectTest__funcWithMultipleEffects_114 --> EffectTest__funcWithMultipleEffects_115 + EffectTest__funcWithMultipleEffects_115 --> EffectTest__funcWithMultipleEffects_116 + EffectTest__funcWithMultipleEffects_116 --> EffectTest__funcWithMultipleEffects_117 end diff --git a/test/all/syntax.expected.out b/test/all/syntax.expected.out index f07737d6..e97d137f 100644 --- a/test/all/syntax.expected.out +++ b/test/all/syntax.expected.out @@ -1,30 +1,48 @@ [MEDIUM] NeverAccessedVariables: Variable value is never accessed -test/all/syntax.tact:97:9: - 96 | fun funcWithStateRead() { -> 97 | let value: Int = self.someVariable; - ^ - 98 | } +test/all/syntax.tact:114:9: + 113 | fun funcWithStateRead() { +> 114 | let value = self.addr; + ^ + 115 | } Help: Consider removing the variable See: https://nowarp.io/tools/misti/docs/detectors/NeverAccessedVariables [MEDIUM] NeverAccessedVariables: Variable currentTime is never accessed -test/all/syntax.tact:107:9: - 106 | fun funcWithDatetimeAccess() { -> 107 | let currentTime: Int = now(); +test/all/syntax.tact:125:9: + 124 | fun funcWithMultipleEffects() { +> 125 | let currentTime: Int = now(); ^ - 108 | } + 126 | let randValue: Int = random(1, 100); Help: Consider removing the variable See: https://nowarp.io/tools/misti/docs/detectors/NeverAccessedVariables [MEDIUM] NeverAccessedVariables: Variable randValue is never accessed -test/all/syntax.tact:112:9: - 111 | fun funcWithRandomnessUse() { -> 112 | let randValue: Int = random(1, 100); +test/all/syntax.tact:126:9: + 125 | let currentTime: Int = now(); +> 126 | let randValue: Int = random(1, 100); ^ - 113 | } + 127 | if (1 > 42) { Help: Consider removing the variable See: https://nowarp.io/tools/misti/docs/detectors/NeverAccessedVariables +[MEDIUM] FalseCondition: Condition always evaluates to false +test/all/syntax.tact:127:13: + 126 | let randValue: Int = random(1, 100); +> 127 | if (1 > 42) { + ^ + 128 | nativeRandomizeLt(); +Help: Consider removing it if there is no logic error +See: https://nowarp.io/tools/misti/docs/detectors/FalseCondition + +[LOW] ZeroAddress: Using zero address +test/all/syntax.tact:119:35: + 118 | fun funcWithStateWrite() { +> 119 | self.addr = newAddress(0, 0); + ^ + 120 | } +Help: Consider changing code to avoid using it. For example, you could pass the address during the deployment. +See: https://nowarp.io/tools/misti/docs/detectors/ZeroAddress + [INFO] DumpIsUsed: Found `dump` usage test/all/syntax.tact:41:35: 40 | try { /* empty */ } @@ -35,11 +53,11 @@ Help: Using `dump` in production code can sometimes indicate complex code that r See: https://nowarp.io/tools/misti/docs/detectors/DumpIsUsed [INFO] DumpIsUsed: Found `dump` usage -test/all/syntax.tact:141:9: - 140 | } catch (err) { -> 141 | dump(err); - ^ - 142 | } +test/all/syntax.tact:67:9: + 66 | } catch (err) { +> 67 | dump(err); + ^ + 68 | } Help: Using `dump` in production code can sometimes indicate complex code that requires additional review See: https://nowarp.io/tools/misti/docs/detectors/DumpIsUsed @@ -98,64 +116,64 @@ Help: Consider using augmented assignment instead: sum += i See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign [INFO] PreferAugmentedAssign: Prefer augmented assignment: i += 1 -test/all/syntax.tact:150:9: - 149 | while (i < 10) { -> 150 | i = i + 1; - ^ - 151 | sum = sum + i; +test/all/syntax.tact:76:9: + 75 | while (i < 10) { +> 76 | i = i + 1; + ^ + 77 | sum = sum + i; Help: Consider using augmented assignment instead: i += 1 See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign [INFO] PreferAugmentedAssign: Prefer augmented assignment: sum += i -test/all/syntax.tact:151:9: - 150 | i = i + 1; -> 151 | sum = sum + i; - ^ - 152 | } +test/all/syntax.tact:77:9: + 76 | i = i + 1; +> 77 | sum = sum + i; + ^ + 78 | } Help: Consider using augmented assignment instead: sum += i See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign [INFO] PreferAugmentedAssign: Prefer augmented assignment: i -= 1 -test/all/syntax.tact:155:9: - 154 | do { -> 155 | i = i - 1; - ^ - 156 | sum = sum + i; +test/all/syntax.tact:81:9: + 80 | do { +> 81 | i = i - 1; + ^ + 82 | sum = sum + i; Help: Consider using augmented assignment instead: i -= 1 See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign [INFO] PreferAugmentedAssign: Prefer augmented assignment: sum += i -test/all/syntax.tact:156:9: - 155 | i = i - 1; -> 156 | sum = sum + i; - ^ - 157 | } until (i <= 0); +test/all/syntax.tact:82:9: + 81 | i = i - 1; +> 82 | sum = sum + i; + ^ + 83 | } until (i <= 0); Help: Consider using augmented assignment instead: sum += i See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign [INFO] PreferAugmentedAssign: Prefer augmented assignment: i += 1 -test/all/syntax.tact:160:9: - 159 | repeat (10) { -> 160 | i = i + 1; - ^ - 161 | sum = sum + i; +test/all/syntax.tact:86:9: + 85 | repeat (10) { +> 86 | i = i + 1; + ^ + 87 | sum = sum + i; Help: Consider using augmented assignment instead: i += 1 See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign [INFO] PreferAugmentedAssign: Prefer augmented assignment: sum += i -test/all/syntax.tact:161:9: - 160 | i = i + 1; -> 161 | sum = sum + i; - ^ - 162 | } +test/all/syntax.tact:87:9: + 86 | i = i + 1; +> 87 | sum = sum + i; + ^ + 88 | } Help: Consider using augmented assignment instead: sum += i See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign [INFO] PreferAugmentedAssign: Prefer augmented assignment: sum += value -test/all/syntax.tact:170:9: - 169 | foreach (key, value in m) { -> 170 | sum = sum + value; - ^ - 171 | } +test/all/syntax.tact:96:9: + 95 | foreach (key, value in m) { +> 96 | sum = sum + value; + ^ + 97 | } Help: Consider using augmented assignment instead: sum += value See: https://nowarp.io/tools/misti/docs/detectors/PreferAugmentedAssign \ No newline at end of file diff --git a/test/all/syntax.tact b/test/all/syntax.tact index b1e3d99b..427ab840 100644 --- a/test/all/syntax.tact +++ b/test/all/syntax.tact @@ -60,80 +60,6 @@ fun test_loops() { foreach (k, v in m) {} } -// -// Tact code covering all EffectFlags for testing CallGraph effects. -// -// Effects covered: -// - CALLS_SEND -// - CONTRACT_STATE_READ -// - CONTRACT_STATE_WRITE -// - ACCESSES_DATETIME -// - RANDOMNESS_USE -// - RANDOMNESS_SEED_INITIALIZATION -// - -contract EffectTestContract { - // State variable for read/write, initialized to 0 - someVariable: Int = 0; - destAddress: Address; - randomnessSeed: Int = 0; - - // Initialize destAddress in the constructor - init() { - self.destAddress = sender(); - } - - // Function that calls 'send' (CALLS_SEND) - fun funcWithSend() { - let amount: Int = 100; - send(SendParameters{ - to: self.destAddress, - value: amount - }); - } - - // Function that reads from contract state (CONTRACT_STATE_READ) - fun funcWithStateRead() { - let value: Int = self.someVariable; - } - - // Function that writes to contract state (CONTRACT_STATE_WRITE) - fun funcWithStateWrite() { - self.someVariable = 42; - } - - // Function that accesses datetime (ACCESSES_DATETIME) - fun funcWithDatetimeAccess() { - let currentTime: Int = now(); - } - - // Function that uses randomness (RANDOMNESS_USE) - fun funcWithRandomnessUse() { - let randValue: Int = random(1, 100); - } - - // Function that initializes randomness seed (RANDOMNESS_SEED_INITIALIZATION) - fun funcWithRandomnessSeedInit(seed: Int) { - self.randomnessSeed = seed; - } - - fun generatePseudoRandom(): Int { - self.randomnessSeed = (self.randomnessSeed * 1103515245 + 12345) % pow(2, 31); - return self.randomnessSeed; - } - - // Function that combines multiple effects - fun funcWithMultipleEffects() { - self.someVariable += random(1, 100); - send(SendParameters{ - to: self.destAddress, - value: self.someVariable - }); - let time: Int = now(); - self.funcWithRandomnessSeedInit(time); - } -} - fun testTryCatch(a: Int) { try { a += 1; @@ -170,3 +96,36 @@ fun testLoops() { sum = sum + value; } } + +// Test callgraph effects +contract EffectTest { + addr: Address; + // Effect.StateWrite + init() { self.addr = sender(); } + + // Effect.Send + Effect.StateRead + fun funcWithSend() { + let amount: Int = 100; + send(SendParameters{ to: self.addr, value: amount }); + } + + // Effect.StateRead + fun funcWithStateRead() { + let value = self.addr; + } + + // Effect.StateWrite + fun funcWithStateWrite() { + self.addr = newAddress(0, 0); + } + + // Multiple effects: + // Effect.AccessDatetime + Effect.PrgUse + Effect.SeedInit + fun funcWithMultipleEffects() { + let currentTime: Int = now(); + let randValue: Int = random(1, 100); + if (1 > 42) { + nativeRandomizeLt(); + } + } +}