diff --git a/src/detectors/builtin/sendInLoop.ts b/src/detectors/builtin/sendInLoop.ts index 6945cc04..7a854b15 100644 --- a/src/detectors/builtin/sendInLoop.ts +++ b/src/detectors/builtin/sendInLoop.ts @@ -1,19 +1,20 @@ import { CompilationUnit } from "../../internals/ir"; -import { - forEachStatement, - foldExpressions, - isSelf, -} from "../../internals/tact"; +import { CallGraph } from "../../internals/ir/callGraph"; +import { forEachStatement, foldExpressions } from "../../internals/tact"; +import { isSendCall } from "../../internals/tact/util"; import { MistiTactWarning, Severity } from "../../internals/warnings"; import { ASTDetector } from "../detector"; import { AstStatement, AstExpression, - idText, + AstStaticCall, + AstMethodCall, + AstContract, } from "@tact-lang/compiler/dist/grammar/ast"; /** - * An optional detector that identifies send functions being called inside loops. + * An optional detector that identifies send functions being called inside loops, + * including indirect calls via other functions. * * ## Why is it bad? * Calling send functions inside loops can lead to unintended consequences, such as @@ -43,30 +44,57 @@ export class SendInLoop extends ASTDetector { async check(cu: CompilationUnit): Promise { const processedLoopIds = new Set(); const allWarnings: MistiTactWarning[] = []; + const astStore = cu.ast; + const ctx = this.ctx; + const callGraph = new CallGraph(ctx).build(astStore); - Array.from(cu.ast.getProgramEntries()).forEach((node) => { - forEachStatement(node, (stmt) => { - const warnings = this.analyzeStatement(stmt, processedLoopIds); - allWarnings.push(...warnings); - }); - }); - + // Analyze loops and check if any function called within leads to a send + for (const entry of cu.ast.getProgramEntries()) { + if (entry.kind === "contract") { + const contract = entry as AstContract; + const contractName = contract.name.text; + forEachStatement(entry, (stmt) => { + const warnings = this.analyzeStatement( + stmt, + processedLoopIds, + callGraph, + contractName, + ); + allWarnings.push(...warnings); + }); + } else { + forEachStatement(entry, (stmt) => { + const warnings = this.analyzeStatement( + stmt, + processedLoopIds, + callGraph, + ); + allWarnings.push(...warnings); + }); + } + } return allWarnings; } private analyzeStatement( stmt: AstStatement, processedLoopIds: Set, + callGraph: CallGraph, + currentContractName?: string, ): MistiTactWarning[] { if (processedLoopIds.has(stmt.id)) { return []; } if (this.isLoop(stmt)) { processedLoopIds.add(stmt.id); - return foldExpressions( + + const warnings: MistiTactWarning[] = []; + + // Check direct send calls within the loop + foldExpressions( stmt, - (acc, expr) => { - if (this.isSendCall(expr)) { + (acc: MistiTactWarning[], expr: AstExpression) => { + if (isSendCall(expr)) { acc.push( this.makeWarning("Send function called inside a loop", expr.loc, { suggestion: @@ -76,8 +104,45 @@ export class SendInLoop extends ASTDetector { } return acc; }, - [] as MistiTactWarning[], + warnings, + ); + + // Check function calls within the loop that lead to a send + foldExpressions( + stmt, + (acc: MistiTactWarning[], expr: AstExpression) => { + if (expr.kind === "static_call" || expr.kind === "method_call") { + const calleeName = callGraph.getFunctionCallName( + expr as AstStaticCall | AstMethodCall, + currentContractName, + ); + if (calleeName) { + const calleeNodeId = callGraph.getNodeIdByName(calleeName); + if (calleeNodeId !== undefined) { + const calleeNode = callGraph.getNode(calleeNodeId); + if (calleeNode && calleeNode.hasFlag(0b0001)) { + const functionName = calleeNode.name.includes("::") + ? calleeNode.name.split("::").pop() + : calleeNode.name; + acc.push( + this.makeWarning( + `Method "${functionName}" called inside a loop leads to a send function`, + expr.loc, + { + suggestion: + "Consider refactoring to avoid calling send functions inside loops", + }, + ), + ); + } + } + } + } + return acc; + }, + warnings, ); + return warnings; } // If the statement is not a loop, don't flag anything return []; @@ -88,10 +153,11 @@ export class SendInLoop extends ASTDetector { const selfMethodSendFunctions = ["reply", "forward", "notify", "emit"]; return ( (expr.kind === "static_call" && - staticSendFunctions.includes(idText(expr.function))) || + staticSendFunctions.includes(expr.function?.text || "")) || (expr.kind === "method_call" && - isSelf(expr.self) && - selfMethodSendFunctions.includes(idText(expr.method))) + expr.self.kind === "id" && + (expr.self as any).text === "self" && + selfMethodSendFunctions.includes(expr.method?.text || "")) ); } diff --git a/src/internals/ir/callGraph.ts b/src/internals/ir/callGraph.ts index 623fe741..4f178a0d 100644 --- a/src/internals/ir/callGraph.ts +++ b/src/internals/ir/callGraph.ts @@ -3,7 +3,8 @@ import { TactASTStore } from "./astStore"; import { IdxGenerator } from "./indices"; import { MistiContext } from "../../"; import { Logger } from "../../internals/logger"; -import { forEachExpression } from "../tact/iterators"; +import { findInExpressions, forEachExpression } from "../tact/iterators"; +import { isSendCall } from "../tact/util"; import { AstFunctionDef, AstReceiver, @@ -11,17 +12,34 @@ import { AstExpression, AstMethodCall, AstStaticCall, + AstContract, + AstId, + AstContractDeclaration, + AstNode, } from "@tact-lang/compiler/dist/grammar/ast"; -type CGNodeId = number & { readonly brand: unique symbol }; -type CGEdgeId = number & { readonly brand: unique symbol }; +export type CGNodeId = number & { readonly brand: unique symbol }; +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. + */ +const FLAG_CALLS_SEND = 0b0001; /** * 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; + /** + * @param src The source node ID representing the calling function + * @param dst The destination node ID representing the called function + */ constructor( public src: CGNodeId, public dst: CGNodeId, @@ -32,15 +50,18 @@ 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; /** - * @param astId AST id of the relevant function definition. It might be `undefined` if this node doesn’t have a corresponding AST entry, - * which indicates an issue in Misti. + * @param astId The AST ID of the function or method this node represents (can be `undefined` for synthetic nodes) + * @param name The name of the function or method + * @param logger A logger instance for logging messages */ constructor( public astId: number | undefined, @@ -52,64 +73,89 @@ class CGNode { this.logger.debug(`CGNode created without AST ID for function "${name}"`); } } + + // Method to set a flag + public setFlag(flag: number) { + this.flags |= flag; + } + + // Method to check if a flag is set + public hasFlag(flag: number): boolean { + return (this.flags & flag) !== 0; + } } /** - * The `CallGraph` class represents a directed graph where nodes correspond to functions - * or methods in a program, and edges indicate calls between them. + * Represents the call graph, a directed graph where nodes represent functions or methods, + * and edges indicate calls between them. * - * Nodes and edges are uniquely identified using indices generated by `IdxGenerator`. - * This class provides methods to construct the graph from AST data, retrieve nodes and edges, - * and check connectivity between nodes. + * 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(); - private edgesMap: Map = new Map(); + private astIdToNodeId: Map = new Map(); private nameToNodeId: Map = new Map(); + private edgesMap: Map = new Map(); private logger: Logger; + /** + * @param ctx The MistiContext providing a logger and other utilities + */ constructor(private ctx: MistiContext) { this.logger = ctx.logger; } + /** + * Retrieves all nodes in the call graph. + * @returns A map of all nodes by their unique IDs. + */ public getNodes(): Map { return this.nodeMap; } + /** + * Retrieves all edges in the call graph. + * @returns A map of all edges by their unique IDs. + */ public getEdges(): Map { return this.edgesMap; } /** - * Builds the call graph based on functions in the provided AST store. - * @param astStore - The AST store containing functions to be added to the graph. - * @returns The constructed `CallGraph`. + * Retrieves a node's ID by its name. + * @param name The name of the function or method. + * @returns The corresponding node ID, or `undefined` if not found. */ - public build(astStore: TactASTStore): CallGraph { - for (const func of astStore.getFunctions()) { - const funcName = this.getFunctionName(func); - if (funcName) { - const node = new CGNode(func.id, funcName, this.logger); - this.nodeMap.set(node.idx, node); - this.nameToNodeId.set(funcName, node.idx); - } else { - this.logger.error( - `Function with id ${func.id} has no name and will be skipped.`, - ); - } - } - this.analyzeFunctionCalls(astStore); - return this; + public getNodeIdByName(name: string): CGNodeId | undefined { + return this.nameToNodeId.get(name); } /** - * Determines if there exists a path in the call graph from the source node to the destination node. - * This method performs a breadth-first search to find if the destination node is reachable from the source node. + * Retrieves a node's ID by its AST ID. + * @param astId The AST ID of the function. + * @returns The corresponding node ID, or `undefined` if not found. + */ + public getNodeIdByAstId(astId: number): CGNodeId | undefined { + return this.astIdToNodeId.get(astId); + } + + /** + * Retrieves a node by its ID. + * @param nodeId The unique ID of the node. + * @returns The corresponding node, or `undefined` if not found. + */ + public getNode(nodeId: CGNodeId): CGNode | undefined { + return this.nodeMap.get(nodeId); + } + + /** + * Determines if there exists a path from the source node to the destination node. + * This is achieved via a breadth-first search. * - * @param src The ID of the source node to start the search from - * @param dst The ID of the destination node to search for - * @returns true if there exists a path from src to dst in the call graph, false otherwise - * Returns false if either src or dst node IDs are not found in the graph + * @param src The ID of the source node. + * @param dst The ID of the destination node. + * @returns `true` if a path exists; `false` otherwise. */ public areConnected(src: CGNodeId, dst: CGNodeId): boolean { const srcNode = this.nodeMap.get(src); @@ -139,82 +185,211 @@ export class CallGraph { } /** - * Analyzes function calls in the AST store and adds corresponding edges in the call graph. - * @param astStore The AST store to analyze for function calls. + * Builds the call graph using data from the AST store. + * @param astStore The AST store containing program entries. + * @returns The constructed `CallGraph`. */ - private analyzeFunctionCalls(astStore: TactASTStore) { - for (const func of astStore.getFunctions()) { - const funcName = this.getFunctionName(func); - if (funcName) { - const callerId = this.nameToNodeId.get(funcName); - if (callerId !== undefined) { - forEachExpression(func, (expr) => - this.processExpression(expr, callerId), - ); - } else { - this.logger.warn( - `Caller function ${funcName} not found in node map.`, - ); + public build(astStore: TactASTStore): 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) { + this.addContractDeclarationToGraph(declaration, contractName); } + } else if (entry.kind === "function_def") { + const func = entry as AstFunctionDef; + this.addFunctionToGraph(func); + } + } + this.analyzeFunctionCalls(astStore); + return this; + } + + /** + * Adds a contract declaration (function, receiver, or initializer) to the graph. + * @param declaration The declaration to add. + * @param contractName The name of the contract the declaration belongs to. + */ + private addContractDeclarationToGraph( + declaration: AstContractDeclaration, + contractName: string, + ) { + if (declaration.kind === "function_def") { + this.addFunctionToGraph(declaration as AstFunctionDef, contractName); + } else if (declaration.kind === "contract_init") { + this.addFunctionToGraph(declaration as AstContractInit, contractName); + } else if (declaration.kind === "receiver") { + this.addFunctionToGraph(declaration as AstReceiver, contractName); + } + } + + /** + * Adds a function node to the graph. + * @param func The function definition, receiver, or initializer. + * @param contractName The optional contract name for namespacing. + */ + private addFunctionToGraph( + func: AstFunctionDef | AstReceiver | AstContractInit, + contractName?: string, + ) { + const funcName = this.getFunctionName(func, contractName); + if (funcName) { + const node = new CGNode(func.id, funcName, this.logger); + this.nodeMap.set(node.idx, node); + this.nameToNodeId.set(funcName, node.idx); + if (func.id !== undefined) { + this.astIdToNodeId.set(func.id, node.idx); } + } else { + this.logger.error( + `Function with id ${func.id} has no name and will be skipped.`, + ); } } /** - * Extracts the function name based on its type. - * @param func The function definition, receiver, or contract initializer. - * @returns The function name if available; otherwise, `undefined`. + * Extracts the function name based on its type and optional contract name. + * @param func The function definition, receiver, or initializer. + * @param contractName The optional contract name. + * @returns The function name, or `undefined` if it cannot be determined. */ private getFunctionName( func: AstFunctionDef | AstReceiver | AstContractInit, + contractName?: string, ): string | undefined { switch (func.kind) { case "function_def": - return func.name?.text; + return contractName + ? `${contractName}::${func.name?.text}` + : func.name?.text; case "contract_init": - return `contract_init_${func.id}`; + return contractName + ? `${contractName}::contract_init_${func.id}` + : `contract_init_${func.id}`; case "receiver": - return `receiver_${func.id}`; + return contractName + ? `${contractName}::receiver_${func.id}` + : `receiver_${func.id}`; default: unreachable(func); } } /** - * Processes an expression, identifying static and method calls to add edges. - * @param expr The AST expression to process. - * @param callerId The ID of the calling node. + * Analyzes the AST for function calls and adds edges between caller and callee nodes. + * Additionally, sets flags on nodes based on their properties (e.g., if they call 'send'). + * @param astStore The AST store to analyze. */ - private processExpression(expr: AstExpression, callerId: CGNodeId) { - if (expr.kind === "static_call") { - const staticCall = expr as AstStaticCall; - const functionName = staticCall.function?.text; + private analyzeFunctionCalls(astStore: TactASTStore) { + 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" || + declaration.kind === "contract_init" || + declaration.kind === "receiver" + ) { + const func = declaration as + | AstFunctionDef + | AstContractInit + | AstReceiver; + const funcNodeId = this.astIdToNodeId.get(func.id); + if (funcNodeId !== undefined) { + 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); + } + } + } + } + } + } else if (entry.kind === "function_def") { + const func = entry as AstFunctionDef; + const funcNodeId = this.astIdToNodeId.get(func.id); + if (funcNodeId !== undefined) { + 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 expression, identifying function or method calls to create edges. + * @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, + currentContractName?: string, + ) { + if (expr.kind === "static_call" || expr.kind === "method_call") { + const functionName = this.getFunctionCallName( + expr as AstStaticCall | AstMethodCall, + currentContractName, + ); if (functionName) { const calleeId = this.findOrAddFunction(functionName); this.addEdge(callerId, calleeId); } else { this.logger.warn( - `Static call expression missing function name at caller ${callerId}`, + `Call expression missing function name at caller ${callerId}`, ); } + } + } + + /** + * Derives the function call name from a static or method call expression. + * @param expr The call expression. + * @param currentContractName The name of the current contract, if available. + * @returns The fully qualified function name, or `undefined` if it cannot be determined. + */ + public getFunctionCallName( + expr: AstStaticCall | AstMethodCall, + currentContractName?: string, + ): string | undefined { + if (expr.kind === "static_call") { + return expr.function?.text; } else if (expr.kind === "method_call") { - const methodCall = expr as AstMethodCall; - const methodName = methodCall.method?.text; + const methodName = expr.method?.text; if (methodName) { - const calleeId = this.findOrAddFunction(methodName); - this.addEdge(callerId, calleeId); - } else { - this.logger.warn( - `Method call expression missing method name at caller ${callerId}`, - ); + let contractName = currentContractName; + if (expr.self.kind === "id") { + const idExpr = expr.self as AstId; + if (idExpr.text !== "self") { + contractName = idExpr.text; + } + } + return contractName ? `${contractName}::${methodName}` : methodName; } } + return undefined; } /** - * Finds or adds a function to the call graph by name. - * @param name The name of the function to find or add. - * @returns The ID of the found or added function node. + * Finds or creates a function node in the graph by its name. + * @param name The name of the function. + * @returns The node ID of the existing or newly created function. */ private findOrAddFunction(name: string): CGNodeId { const nodeId = this.nameToNodeId.get(name); @@ -228,7 +403,7 @@ export class CallGraph { } /** - * Adds an edge between two nodes in the call graph. + * Adds a directed edge between two nodes in the call graph. * @param src The source node ID. * @param dst The destination node ID. */ @@ -247,3 +422,12 @@ 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"; +} diff --git a/src/internals/ir/index.ts b/src/internals/ir/index.ts index fc8e4f24..63ab9978 100644 --- a/src/internals/ir/index.ts +++ b/src/internals/ir/index.ts @@ -4,3 +4,4 @@ export * from "./imports"; export * from "./ir"; export * from "./types"; export * from "./builders/"; +export * from "./callGraph"; diff --git a/src/internals/tact/util.ts b/src/internals/tact/util.ts index 9a15f82c..08486b35 100644 --- a/src/internals/tact/util.ts +++ b/src/internals/tact/util.ts @@ -538,3 +538,20 @@ export function getConstantLoadSize(call: AstMethodCall): bigint | undefined { return undefined; } } + +/** + * Determines if the given expression is a 'send' call. + * @param expr The expression to check. + * @returns True if the expression is a 'send' call; otherwise, false. + */ +export function isSendCall(expr: AstExpression): boolean { + const staticSendFunctions = ["send", "nativeSendMessage"]; + const selfMethodSendFunctions = ["reply", "forward", "notify", "emit"]; + return ( + (expr.kind === "static_call" && + staticSendFunctions.includes(expr.function?.text || "")) || + (expr.kind === "method_call" && + isSelf(expr.self) && + selfMethodSendFunctions.includes(expr.method?.text || "")) + ); +} diff --git a/test/all/sample-jetton.expected.callgraph.dot b/test/all/sample-jetton.expected.callgraph.dot index 681dce12..aace0793 100644 --- a/test/all/sample-jetton.expected.callgraph.dot +++ b/test/all/sample-jetton.expected.callgraph.dot @@ -1,120 +1,69 @@ digraph "CallGraph" { node [shape=box]; - node_1 [label="reply"]; - node_2 [label="notify"]; - node_3 [label="forward"]; - node_4 [label="requireOwner"]; - node_5 [label="owner"]; - node_6 [label="receiver_1734"]; - node_7 [label="contract_init_1817"]; - node_8 [label="receiver_1857"]; - node_9 [label="receiver_1882"]; - node_10 [label="receiver_1905"]; - node_11 [label="receiver_1939"]; - node_12 [label="receiver_2000"]; - node_13 [label="mint"]; - node_14 [label="requireWallet"]; - node_15 [label="getJettonWalletInit"]; - node_16 [label="get_jetton_data"]; - node_17 [label="get_wallet_address"]; - node_18 [label="contract_init_2359"]; - node_19 [label="receiver_2517"]; - node_20 [label="receiver_2687"]; - node_21 [label="msgValue"]; - node_22 [label="receiver_2832"]; - node_23 [label="receiver_2876"]; - node_24 [label="get_wallet_data"]; - node_25 [label="sender"]; - node_26 [label="context"]; - node_27 [label="myBalance"]; - node_28 [label="nativeReserve"]; - node_29 [label="send"]; - node_30 [label="nativeThrowUnless"]; - node_31 [label="toCell"]; - node_32 [label="require"]; - node_33 [label="contractAddress"]; - node_34 [label="myAddress"]; - node_35 [label="emptySlice"]; - node_36 [label="readForwardFee"]; - node_37 [label="min"]; - node_38 [label="ton"]; - node_39 [label="loadUint"]; - node_40 [label="loadCoins"]; - node_1 -> node_3; - node_1 -> node_25; - node_2 -> node_3; - node_2 -> node_25; - node_3 -> node_26; - node_3 -> node_27; - node_3 -> node_28; - node_3 -> node_29; - node_3 -> node_29; - node_4 -> node_30; - node_4 -> node_25; - node_6 -> node_4; - node_6 -> node_1; - node_6 -> node_31; - node_8 -> node_26; - node_8 -> node_32; - node_8 -> node_32; - node_8 -> node_13; - node_9 -> node_26; - node_9 -> node_32; + 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="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_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_13; + node_2 -> node_14; + node_3 -> node_12; + node_3 -> node_13; + node_3 -> 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_15; + node_6 -> node_13; + node_6 -> node_16; + node_6 -> node_17; + node_6 -> node_13; + node_6 -> node_18; + node_6 -> node_19; + node_6 -> node_20; + node_7 -> node_12; + node_7 -> node_13; + node_7 -> node_18; + node_7 -> node_13; + node_7 -> node_19; + node_7 -> node_20; + node_7 -> node_8; + node_7 -> node_15; + node_7 -> node_19; + node_7 -> node_20; + node_8 -> node_21; + node_8 -> node_16; + node_9 -> node_12; node_9 -> node_13; - node_10 -> node_26; - node_10 -> node_32; - node_11 -> node_4; - node_12 -> node_14; - node_12 -> node_29; - node_12 -> node_31; - node_13 -> node_32; - node_13 -> node_15; - node_13 -> node_29; - node_13 -> node_33; - node_13 -> node_31; - node_13 -> node_34; - node_13 -> node_35; - node_14 -> node_26; - node_14 -> node_15; - node_14 -> node_32; - node_14 -> node_33; - node_15 -> node_34; - node_16 -> node_15; - node_16 -> node_34; - node_17 -> node_15; - node_17 -> node_33; - node_19 -> node_26; - node_19 -> node_32; - node_19 -> node_36; - node_19 -> node_36; - node_19 -> node_32; - node_19 -> node_37; - node_19 -> node_38; - node_19 -> node_32; - node_19 -> node_33; - node_19 -> node_29; - node_19 -> node_31; - node_20 -> node_26; - node_20 -> node_32; - node_20 -> node_33; - node_20 -> node_32; - node_20 -> node_29; - node_20 -> node_31; - node_20 -> node_21; - node_20 -> node_36; - node_20 -> node_29; - node_20 -> node_31; - node_21 -> node_27; - node_21 -> node_37; - node_22 -> node_26; - node_22 -> node_32; - node_22 -> node_32; - node_22 -> node_36; - node_22 -> node_32; - node_22 -> node_29; - node_22 -> node_31; - node_23 -> node_39; - node_23 -> node_39; - node_23 -> node_40; - node_23 -> node_32; + node_9 -> node_13; + node_9 -> node_15; + node_9 -> node_13; + node_9 -> node_19; + node_9 -> node_20; + node_10 -> node_22; + node_10 -> node_22; + node_10 -> node_23; + node_10 -> node_13; } diff --git a/test/all/sample-jetton.expected.callgraph.json b/test/all/sample-jetton.expected.callgraph.json index 9828ca1d..be183287 100644 --- a/test/all/sample-jetton.expected.callgraph.json +++ b/test/all/sample-jetton.expected.callgraph.json @@ -2,438 +2,246 @@ "nodes": [ { "idx": 1, - "name": "reply", - "inEdges": [ - 13 - ], - "outEdges": [ - 1, - 2 - ] + "name": "SampleJetton::contract_init_1817", + "inEdges": [], + "outEdges": [] }, { "idx": 2, - "name": "notify", + "name": "SampleJetton::receiver_1857", "inEdges": [], "outEdges": [ + 1, + 2, 3, 4 ] }, { "idx": 3, - "name": "forward", - "inEdges": [ - 1, - 3 - ], + "name": "SampleJetton::receiver_1882", + "inEdges": [], "outEdges": [ 5, 6, - 7, - 8, - 9 + 7 ] }, { "idx": 4, - "name": "requireOwner", - "inEdges": [ - 12, - 24 - ], + "name": "SampleJetton::receiver_1905", + "inEdges": [], "outEdges": [ - 10, - 11 + 8, + 9 ] }, { "idx": 5, - "name": "owner", + "name": "JettonDefaultWallet::contract_init_2359", "inEdges": [], "outEdges": [] }, { "idx": 6, - "name": "receiver_1734", + "name": "JettonDefaultWallet::receiver_2517", "inEdges": [], "outEdges": [ + 10, + 11, 12, 13, - 14 - ] - }, - { - "idx": 7, - "name": "contract_init_1817", - "inEdges": [], - "outEdges": [] - }, - { - "idx": 8, - "name": "receiver_1857", - "inEdges": [], - "outEdges": [ + 14, 15, 16, 17, - 18 - ] - }, - { - "idx": 9, - "name": "receiver_1882", - "inEdges": [], - "outEdges": [ + 18, 19, - 20, - 21 + 20 ] }, { - "idx": 10, - "name": "receiver_1905", + "idx": 7, + "name": "JettonDefaultWallet::receiver_2687", "inEdges": [], "outEdges": [ + 21, 22, - 23 - ] - }, - { - "idx": 11, - "name": "receiver_1939", - "inEdges": [], - "outEdges": [ - 24 - ] - }, - { - "idx": 12, - "name": "receiver_2000", - "inEdges": [], - "outEdges": [ + 23, + 24, 25, 26, - 27 + 27, + 28, + 29, + 30 ] }, { - "idx": 13, - "name": "mint", + "idx": 8, + "name": "JettonDefaultWallet::msgValue", "inEdges": [ - 18, - 21 + 27 ], "outEdges": [ - 28, - 29, - 30, 31, - 32, - 33, - 34 + 32 ] }, { - "idx": 14, - "name": "requireWallet", - "inEdges": [ - 25 - ], + "idx": 9, + "name": "JettonDefaultWallet::receiver_2832", + "inEdges": [], "outEdges": [ + 33, + 34, 35, 36, 37, - 38 - ] - }, - { - "idx": 15, - "name": "getJettonWalletInit", - "inEdges": [ - 29, - 36, - 40, - 42 - ], - "outEdges": [ + 38, 39 ] }, { - "idx": 16, - "name": "get_jetton_data", + "idx": 10, + "name": "JettonDefaultWallet::receiver_2876", "inEdges": [], "outEdges": [ 40, - 41 - ] - }, - { - "idx": 17, - "name": "get_wallet_address", - "inEdges": [], - "outEdges": [ + 41, 42, 43 ] }, { - "idx": 18, - "name": "contract_init_2359", - "inEdges": [], - "outEdges": [] - }, - { - "idx": 19, - "name": "receiver_2517", - "inEdges": [], - "outEdges": [ - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54 - ] - }, - { - "idx": 20, - "name": "receiver_2687", - "inEdges": [], - "outEdges": [ - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64 - ] - }, - { - "idx": 21, - "name": "msgValue", - "inEdges": [ - 61 - ], - "outEdges": [ - 65, - 66 - ] - }, - { - "idx": 22, - "name": "receiver_2832", - "inEdges": [], - "outEdges": [ - 67, - 68, - 69, - 70, - 71, - 72, - 73 - ] - }, - { - "idx": 23, - "name": "receiver_2876", - "inEdges": [], - "outEdges": [ - 74, - 75, - 76, - 77 - ] - }, - { - "idx": 24, - "name": "get_wallet_data", + "idx": 11, + "name": "JettonDefaultWallet::get_wallet_data", "inEdges": [], "outEdges": [] }, { - "idx": 25, - "name": "sender", - "inEdges": [ - 2, - 4, - 11 - ], - "outEdges": [] - }, - { - "idx": 26, + "idx": 12, "name": "context", "inEdges": [ + 1, 5, - 15, - 19, - 22, - 35, - 44, - 55, - 67 + 8, + 10, + 21, + 33 ], "outEdges": [] }, { - "idx": 27, - "name": "myBalance", + "idx": 13, + "name": "require", "inEdges": [ + 2, + 3, 6, - 65 - ], - "outEdges": [] - }, - { - "idx": 28, - "name": "nativeReserve", - "inEdges": [ - 7 - ], - "outEdges": [] - }, - { - "idx": 29, - "name": "send", - "inEdges": [ - 8, 9, - 26, - 30, - 53, - 59, - 63, - 72 - ], - "outEdges": [] - }, - { - "idx": 30, - "name": "nativeThrowUnless", - "inEdges": [ - 10 + 11, + 14, + 17, + 22, + 24, + 34, + 35, + 37, + 43 ], "outEdges": [] }, { - "idx": 31, - "name": "toCell", + "idx": 14, + "name": "SampleJetton::mint", "inEdges": [ - 14, - 27, - 32, - 54, - 60, - 64, - 73 + 4, + 7 ], "outEdges": [] }, { - "idx": 32, - "name": "require", + "idx": 15, + "name": "ctx::readForwardFee", "inEdges": [ - 16, - 17, - 20, - 23, + 12, + 13, 28, - 37, - 45, - 48, - 51, - 56, - 58, - 68, - 69, - 71, - 77 + 36 ], "outEdges": [] }, { - "idx": 33, - "name": "contractAddress", + "idx": 16, + "name": "min", "inEdges": [ - 31, - 38, - 43, - 52, - 57 + 15, + 32 ], "outEdges": [] }, { - "idx": 34, - "name": "myAddress", + "idx": 17, + "name": "ton", "inEdges": [ - 33, - 39, - 41 + 16 ], "outEdges": [] }, { - "idx": 35, - "name": "emptySlice", + "idx": 18, + "name": "contractAddress", "inEdges": [ - 34 + 18, + 23 ], "outEdges": [] }, { - "idx": 36, - "name": "readForwardFee", + "idx": 19, + "name": "send", "inEdges": [ - 46, - 47, - 62, - 70 + 19, + 25, + 29, + 38 ], "outEdges": [] }, { - "idx": 37, - "name": "min", + "idx": 20, + "name": "JettonDefaultWallet::toCell", "inEdges": [ - 49, - 66 + 20, + 26, + 30, + 39 ], "outEdges": [] }, { - "idx": 38, - "name": "ton", + "idx": 21, + "name": "myBalance", "inEdges": [ - 50 + 31 ], "outEdges": [] }, { - "idx": 39, - "name": "loadUint", + "idx": 22, + "name": "msg::loadUint", "inEdges": [ - 74, - 75 + 40, + 41 ], "outEdges": [] }, { - "idx": 40, - "name": "loadCoins", + "idx": 23, + "name": "msg::loadCoins", "inEdges": [ - 76 + 42 ], "outEdges": [] } @@ -441,388 +249,218 @@ "edges": [ { "idx": 1, - "src": 1, - "dst": 3 + "src": 2, + "dst": 12 }, { "idx": 2, - "src": 1, - "dst": 25 + "src": 2, + "dst": 13 }, { "idx": 3, "src": 2, - "dst": 3 + "dst": 13 }, { "idx": 4, "src": 2, - "dst": 25 + "dst": 14 }, { "idx": 5, "src": 3, - "dst": 26 + "dst": 12 }, { "idx": 6, "src": 3, - "dst": 27 + "dst": 13 }, { "idx": 7, "src": 3, - "dst": 28 + "dst": 14 }, { "idx": 8, - "src": 3, - "dst": 29 + "src": 4, + "dst": 12 }, { "idx": 9, - "src": 3, - "dst": 29 + "src": 4, + "dst": 13 }, { "idx": 10, - "src": 4, - "dst": 30 + "src": 6, + "dst": 12 }, { "idx": 11, - "src": 4, - "dst": 25 + "src": 6, + "dst": 13 }, { "idx": 12, "src": 6, - "dst": 4 + "dst": 15 }, { "idx": 13, "src": 6, - "dst": 1 + "dst": 15 }, { "idx": 14, "src": 6, - "dst": 31 + "dst": 13 }, { "idx": 15, - "src": 8, - "dst": 26 + "src": 6, + "dst": 16 }, { "idx": 16, - "src": 8, - "dst": 32 + "src": 6, + "dst": 17 }, { "idx": 17, - "src": 8, - "dst": 32 + "src": 6, + "dst": 13 }, { "idx": 18, - "src": 8, - "dst": 13 + "src": 6, + "dst": 18 }, { "idx": 19, - "src": 9, - "dst": 26 + "src": 6, + "dst": 19 }, { "idx": 20, - "src": 9, - "dst": 32 + "src": 6, + "dst": 20 }, { "idx": 21, - "src": 9, - "dst": 13 + "src": 7, + "dst": 12 }, { "idx": 22, - "src": 10, - "dst": 26 + "src": 7, + "dst": 13 }, { "idx": 23, - "src": 10, - "dst": 32 + "src": 7, + "dst": 18 }, { "idx": 24, - "src": 11, - "dst": 4 + "src": 7, + "dst": 13 }, { "idx": 25, - "src": 12, - "dst": 14 + "src": 7, + "dst": 19 }, { "idx": 26, - "src": 12, - "dst": 29 + "src": 7, + "dst": 20 }, { "idx": 27, - "src": 12, - "dst": 31 + "src": 7, + "dst": 8 }, { "idx": 28, - "src": 13, - "dst": 32 + "src": 7, + "dst": 15 }, { "idx": 29, - "src": 13, - "dst": 15 + "src": 7, + "dst": 19 }, { "idx": 30, - "src": 13, - "dst": 29 + "src": 7, + "dst": 20 }, { "idx": 31, - "src": 13, - "dst": 33 + "src": 8, + "dst": 21 }, { "idx": 32, - "src": 13, - "dst": 31 + "src": 8, + "dst": 16 }, { "idx": 33, - "src": 13, - "dst": 34 + "src": 9, + "dst": 12 }, { "idx": 34, - "src": 13, - "dst": 35 + "src": 9, + "dst": 13 }, { "idx": 35, - "src": 14, - "dst": 26 + "src": 9, + "dst": 13 }, { "idx": 36, - "src": 14, + "src": 9, "dst": 15 }, { "idx": 37, - "src": 14, - "dst": 32 + "src": 9, + "dst": 13 }, { "idx": 38, - "src": 14, - "dst": 33 + "src": 9, + "dst": 19 }, { "idx": 39, - "src": 15, - "dst": 34 + "src": 9, + "dst": 20 }, { "idx": 40, - "src": 16, - "dst": 15 + "src": 10, + "dst": 22 }, { "idx": 41, - "src": 16, - "dst": 34 + "src": 10, + "dst": 22 }, { "idx": 42, - "src": 17, - "dst": 15 + "src": 10, + "dst": 23 }, { "idx": 43, - "src": 17, - "dst": 33 - }, - { - "idx": 44, - "src": 19, - "dst": 26 - }, - { - "idx": 45, - "src": 19, - "dst": 32 - }, - { - "idx": 46, - "src": 19, - "dst": 36 - }, - { - "idx": 47, - "src": 19, - "dst": 36 - }, - { - "idx": 48, - "src": 19, - "dst": 32 - }, - { - "idx": 49, - "src": 19, - "dst": 37 - }, - { - "idx": 50, - "src": 19, - "dst": 38 - }, - { - "idx": 51, - "src": 19, - "dst": 32 - }, - { - "idx": 52, - "src": 19, - "dst": 33 - }, - { - "idx": 53, - "src": 19, - "dst": 29 - }, - { - "idx": 54, - "src": 19, - "dst": 31 - }, - { - "idx": 55, - "src": 20, - "dst": 26 - }, - { - "idx": 56, - "src": 20, - "dst": 32 - }, - { - "idx": 57, - "src": 20, - "dst": 33 - }, - { - "idx": 58, - "src": 20, - "dst": 32 - }, - { - "idx": 59, - "src": 20, - "dst": 29 - }, - { - "idx": 60, - "src": 20, - "dst": 31 - }, - { - "idx": 61, - "src": 20, - "dst": 21 - }, - { - "idx": 62, - "src": 20, - "dst": 36 - }, - { - "idx": 63, - "src": 20, - "dst": 29 - }, - { - "idx": 64, - "src": 20, - "dst": 31 - }, - { - "idx": 65, - "src": 21, - "dst": 27 - }, - { - "idx": 66, - "src": 21, - "dst": 37 - }, - { - "idx": 67, - "src": 22, - "dst": 26 - }, - { - "idx": 68, - "src": 22, - "dst": 32 - }, - { - "idx": 69, - "src": 22, - "dst": 32 - }, - { - "idx": 70, - "src": 22, - "dst": 36 - }, - { - "idx": 71, - "src": 22, - "dst": 32 - }, - { - "idx": 72, - "src": 22, - "dst": 29 - }, - { - "idx": 73, - "src": 22, - "dst": 31 - }, - { - "idx": 74, - "src": 23, - "dst": 39 - }, - { - "idx": 75, - "src": 23, - "dst": 39 - }, - { - "idx": 76, - "src": 23, - "dst": 40 - }, - { - "idx": 77, - "src": 23, - "dst": 32 + "src": 10, + "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 aec3dc9d..3cdb21bd 100644 --- a/test/all/sample-jetton.expected.callgraph.mmd +++ b/test/all/sample-jetton.expected.callgraph.mmd @@ -1,118 +1,67 @@ graph TD - node_1["reply"] - node_2["notify"] - node_3["forward"] - node_4["requireOwner"] - node_5["owner"] - node_6["receiver_1734"] - node_7["contract_init_1817"] - node_8["receiver_1857"] - node_9["receiver_1882"] - node_10["receiver_1905"] - node_11["receiver_1939"] - node_12["receiver_2000"] - node_13["mint"] - node_14["requireWallet"] - node_15["getJettonWalletInit"] - node_16["get_jetton_data"] - node_17["get_wallet_address"] - node_18["contract_init_2359"] - node_19["receiver_2517"] - node_20["receiver_2687"] - node_21["msgValue"] - node_22["receiver_2832"] - node_23["receiver_2876"] - node_24["get_wallet_data"] - node_25["sender"] - node_26["context"] - node_27["myBalance"] - node_28["nativeReserve"] - node_29["send"] - node_30["nativeThrowUnless"] - node_31["toCell"] - node_32["require"] - node_33["contractAddress"] - node_34["myAddress"] - node_35["emptySlice"] - node_36["readForwardFee"] - node_37["min"] - node_38["ton"] - node_39["loadUint"] - node_40["loadCoins"] - node_1 --> node_3 - node_1 --> node_25 - node_2 --> node_3 - node_2 --> node_25 - node_3 --> node_26 - node_3 --> node_27 - node_3 --> node_28 - node_3 --> node_29 - node_3 --> node_29 - node_4 --> node_30 - node_4 --> node_25 - node_6 --> node_4 - node_6 --> node_1 - node_6 --> node_31 - node_8 --> node_26 - node_8 --> node_32 - node_8 --> node_32 - node_8 --> node_13 - node_9 --> node_26 - node_9 --> node_32 + 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["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_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_13 + node_2 --> node_14 + node_3 --> node_12 + node_3 --> node_13 + node_3 --> 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_15 + node_6 --> node_13 + node_6 --> node_16 + node_6 --> node_17 + node_6 --> node_13 + node_6 --> node_18 + node_6 --> node_19 + node_6 --> node_20 + node_7 --> node_12 + node_7 --> node_13 + node_7 --> node_18 + node_7 --> node_13 + node_7 --> node_19 + node_7 --> node_20 + node_7 --> node_8 + node_7 --> node_15 + node_7 --> node_19 + node_7 --> node_20 + node_8 --> node_21 + node_8 --> node_16 + node_9 --> node_12 node_9 --> node_13 - node_10 --> node_26 - node_10 --> node_32 - node_11 --> node_4 - node_12 --> node_14 - node_12 --> node_29 - node_12 --> node_31 - node_13 --> node_32 - node_13 --> node_15 - node_13 --> node_29 - node_13 --> node_33 - node_13 --> node_31 - node_13 --> node_34 - node_13 --> node_35 - node_14 --> node_26 - node_14 --> node_15 - node_14 --> node_32 - node_14 --> node_33 - node_15 --> node_34 - node_16 --> node_15 - node_16 --> node_34 - node_17 --> node_15 - node_17 --> node_33 - node_19 --> node_26 - node_19 --> node_32 - node_19 --> node_36 - node_19 --> node_36 - node_19 --> node_32 - node_19 --> node_37 - node_19 --> node_38 - node_19 --> node_32 - node_19 --> node_33 - node_19 --> node_29 - node_19 --> node_31 - node_20 --> node_26 - node_20 --> node_32 - node_20 --> node_33 - node_20 --> node_32 - node_20 --> node_29 - node_20 --> node_31 - node_20 --> node_21 - node_20 --> node_36 - node_20 --> node_29 - node_20 --> node_31 - node_21 --> node_27 - node_21 --> node_37 - node_22 --> node_26 - node_22 --> node_32 - node_22 --> node_32 - node_22 --> node_36 - node_22 --> node_32 - node_22 --> node_29 - node_22 --> node_31 - node_23 --> node_39 - node_23 --> node_39 - node_23 --> node_40 - node_23 --> node_32 + node_9 --> node_13 + node_9 --> node_15 + node_9 --> node_13 + node_9 --> node_19 + node_9 --> node_20 + node_10 --> node_22 + node_10 --> node_22 + node_10 --> node_23 + node_10 --> node_13 diff --git a/test/all/syntax.expected.callgraph.dot b/test/all/syntax.expected.callgraph.dot index 7f7f6bac..565484dc 100644 --- a/test/all/syntax.expected.callgraph.dot +++ b/test/all/syntax.expected.callgraph.dot @@ -2,32 +2,14 @@ digraph "CallGraph" { node [shape=box]; node_1 [label="test_try"]; node_2 [label="test_loops"]; - node_3 [label="reply"]; - node_4 [label="notify"]; - node_5 [label="forward"]; - node_6 [label="getter"]; - node_7 [label="getter"]; - node_8 [label="test"]; - node_9 [label="getA"]; - node_10 [label="test"]; - node_11 [label="receiver_1722"]; - node_12 [label="dump"]; - node_13 [label="emptyMap"]; - node_14 [label="sender"]; - node_15 [label="context"]; - node_16 [label="myBalance"]; - node_17 [label="nativeReserve"]; - node_18 [label="send"]; - node_1 -> node_12; - node_2 -> node_13; - node_3 -> node_5; - node_3 -> node_14; - node_4 -> node_5; - node_4 -> node_14; - node_5 -> node_15; - node_5 -> node_16; - node_5 -> node_17; - node_5 -> node_18; - node_5 -> node_18; - node_10 -> node_9; + 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; } diff --git a/test/all/syntax.expected.callgraph.json b/test/all/syntax.expected.callgraph.json index c84e70d4..346edc0b 100644 --- a/test/all/syntax.expected.callgraph.json +++ b/test/all/syntax.expected.callgraph.json @@ -18,79 +18,32 @@ }, { "idx": 3, - "name": "reply", + "name": "TestContract::getter", "inEdges": [], - "outEdges": [ - 3, - 4 - ] + "outEdges": [] }, { "idx": 4, - "name": "notify", + "name": "TestContractF::test", "inEdges": [], - "outEdges": [ - 5, - 6 - ] + "outEdges": [] }, { "idx": 5, - "name": "forward", - "inEdges": [ - 3, - 5 - ], + "name": "TestContractT::test", + "inEdges": [], "outEdges": [ - 7, - 8, - 9, - 10, - 11 + 3 ] }, { "idx": 6, - "name": "getter", + "name": "TestContractT::receiver_1722", "inEdges": [], "outEdges": [] }, { "idx": 7, - "name": "getter", - "inEdges": [], - "outEdges": [] - }, - { - "idx": 8, - "name": "test", - "inEdges": [], - "outEdges": [] - }, - { - "idx": 9, - "name": "getA", - "inEdges": [ - 12 - ], - "outEdges": [] - }, - { - "idx": 10, - "name": "test", - "inEdges": [], - "outEdges": [ - 12 - ] - }, - { - "idx": 11, - "name": "receiver_1722", - "inEdges": [], - "outEdges": [] - }, - { - "idx": 12, "name": "dump", "inEdges": [ 1 @@ -98,7 +51,7 @@ "outEdges": [] }, { - "idx": 13, + "idx": 8, "name": "emptyMap", "inEdges": [ 2 @@ -106,44 +59,10 @@ "outEdges": [] }, { - "idx": 14, - "name": "sender", - "inEdges": [ - 4, - 6 - ], - "outEdges": [] - }, - { - "idx": 15, - "name": "context", - "inEdges": [ - 7 - ], - "outEdges": [] - }, - { - "idx": 16, - "name": "myBalance", - "inEdges": [ - 8 - ], - "outEdges": [] - }, - { - "idx": 17, - "name": "nativeReserve", - "inEdges": [ - 9 - ], - "outEdges": [] - }, - { - "idx": 18, - "name": "send", + "idx": 9, + "name": "TestContractT::getA", "inEdges": [ - 10, - 11 + 3 ], "outEdges": [] } @@ -152,61 +71,16 @@ { "idx": 1, "src": 1, - "dst": 12 + "dst": 7 }, { "idx": 2, "src": 2, - "dst": 13 + "dst": 8 }, { "idx": 3, - "src": 3, - "dst": 5 - }, - { - "idx": 4, - "src": 3, - "dst": 14 - }, - { - "idx": 5, - "src": 4, - "dst": 5 - }, - { - "idx": 6, - "src": 4, - "dst": 14 - }, - { - "idx": 7, - "src": 5, - "dst": 15 - }, - { - "idx": 8, - "src": 5, - "dst": 16 - }, - { - "idx": 9, - "src": 5, - "dst": 17 - }, - { - "idx": 10, "src": 5, - "dst": 18 - }, - { - "idx": 11, - "src": 5, - "dst": 18 - }, - { - "idx": 12, - "src": 10, "dst": 9 } ] diff --git a/test/all/syntax.expected.callgraph.mmd b/test/all/syntax.expected.callgraph.mmd index 4393d605..720a23bb 100644 --- a/test/all/syntax.expected.callgraph.mmd +++ b/test/all/syntax.expected.callgraph.mmd @@ -1,31 +1,13 @@ graph TD node_1["test_try"] node_2["test_loops"] - node_3["reply"] - node_4["notify"] - node_5["forward"] - node_6["getter"] - node_7["getter"] - node_8["test"] - node_9["getA"] - node_10["test"] - node_11["receiver_1722"] - node_12["dump"] - node_13["emptyMap"] - node_14["sender"] - node_15["context"] - node_16["myBalance"] - node_17["nativeReserve"] - node_18["send"] - node_1 --> node_12 - node_2 --> node_13 - node_3 --> node_5 - node_3 --> node_14 - node_4 --> node_5 - node_4 --> node_14 - node_5 --> node_15 - node_5 --> node_16 - node_5 --> node_17 - node_5 --> node_18 - node_5 --> node_18 - node_10 --> node_9 + 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 diff --git a/test/detectors/SendInLoop.expected.out b/test/detectors/SendInLoop.expected.out index 968e90d5..99b31303 100644 --- a/test/detectors/SendInLoop.expected.out +++ b/test/detectors/SendInLoop.expected.out @@ -1,62 +1,53 @@ [MEDIUM] SendInLoop: Send function called inside a loop -test/detectors/SendInLoop.tact:6:13: - 5 | while (i < 10) { -> 6 | send(SendParameters{ +test/detectors/SendInLoop.tact:5:13: + 4 | while (i < 10) { +> 5 | send(SendParameters{ ^ - 7 | to: sender(), + 6 | to: sender(), Help: Consider refactoring to avoid calling send functions inside loops See: https://nowarp.io/tools/misti/docs/detectors/SendInLoop [MEDIUM] SendInLoop: Send function called inside a loop -test/detectors/SendInLoop.tact:16:13: - 15 | repeat (10) { -> 16 | send(SendParameters{ +test/detectors/SendInLoop.tact:15:13: + 14 | repeat (10) { +> 15 | send(SendParameters{ ^ - 17 | to: sender(), + 16 | to: sender(), Help: Consider refactoring to avoid calling send functions inside loops See: https://nowarp.io/tools/misti/docs/detectors/SendInLoop [MEDIUM] SendInLoop: Send function called inside a loop -test/detectors/SendInLoop.tact:26:13: - 25 | do { -> 26 | send(SendParameters{ +test/detectors/SendInLoop.tact:25:13: + 24 | do { +> 25 | send(SendParameters{ ^ - 27 | to: sender(), + 26 | to: sender(), Help: Consider refactoring to avoid calling send functions inside loops See: https://nowarp.io/tools/misti/docs/detectors/SendInLoop [MEDIUM] SendInLoop: Send function called inside a loop -test/detectors/SendInLoop.tact:37:13: - 36 | foreach (k, v in m) { -> 37 | send(SendParameters{ +test/detectors/SendInLoop.tact:36:13: + 35 | foreach (k, v in m) { +> 36 | send(SendParameters{ ^ - 38 | to: sender(), + 37 | to: sender(), Help: Consider refactoring to avoid calling send functions inside loops See: https://nowarp.io/tools/misti/docs/detectors/SendInLoop [MEDIUM] SendInLoop: Send function called inside a loop -test/detectors/SendInLoop.tact:48:17: - 47 | repeat (10) { -> 48 | send(SendParameters{ +test/detectors/SendInLoop.tact:47:17: + 46 | repeat (10) { +> 47 | send(SendParameters{ ^ - 49 | to: sender(), + 48 | to: sender(), Help: Consider refactoring to avoid calling send functions inside loops See: https://nowarp.io/tools/misti/docs/detectors/SendInLoop -[MEDIUM] SendInLoop: Send function called inside a loop -test/detectors/SendInLoop.tact:80:19: - 79 | i += 1; -> 80 | let a = send(SendParameters{ - ^ - 81 | to: self.owner, -Help: Consider refactoring to avoid calling send functions inside loops -See: https://nowarp.io/tools/misti/docs/detectors/SendInLoop - -[MEDIUM] SendInLoop: Send function called inside a loop -test/detectors/SendInLoop.tact:93:13: - 92 | while (i < 5) { -> 93 | self.reply(Msg{ a: i }.toCell()); - ^ - 94 | i = i + 1; +[MEDIUM] SendInLoop: Method "deepNestSend" called inside a loop leads to a send function +test/detectors/SendInLoop.tact:140:17: + 139 | if (value > 0) { +> 140 | self.deepNestSend(value); + ^ + 141 | } Help: Consider refactoring to avoid calling send functions inside loops See: https://nowarp.io/tools/misti/docs/detectors/SendInLoop \ No newline at end of file diff --git a/test/detectors/SendInLoop.tact b/test/detectors/SendInLoop.tact index cfd14ce1..24a66225 100644 --- a/test/detectors/SendInLoop.tact +++ b/test/detectors/SendInLoop.tact @@ -1,4 +1,3 @@ - contract Test { fun tWhile() { let i: Int = 0; @@ -71,31 +70,75 @@ contract SendInLoop { self.a = 0; } - //Send function inside a while loop + fun sendMessage(i: Int) { + send(SendParameters{ + to: self.owner, + value: 0, + bounce: false, + body: Msg{ a: i }.toCell() + }); + } - fun exampleWhileLoop(limit: Int) { + // Function that calls another function which calls send + fun indirectSend(i: Int) { + self.sendMessage(i); + } + + fun exampleIndirectLoop(limit: Int) { let i = 0; while (i < limit) { i += 1; - let a = send(SendParameters{ - to: self.owner, - value: 0, - bounce: false, - body: Msg{ a: i }.toCell() - }); + self.indirectSend(i); } } +} - // self.reply inside a loop - fun testReply() { - let i: Int = 0; - while (i < 5) { - self.reply(Msg{ a: i }.toCell()); - i = i + 1; - } +//Additional test + +contract ComplexIndirectSend { + owner: Address; + + init(owner: Address) { + self.owner = owner; } -} + // Indirect send via multiple function layers + fun deepNestSend(value: Int) { + send(SendParameters{ + to: self.owner, + value: value + }); + } + + fun intermediateCall(value: Int) { + self.deepNestSend(value); + } + fun outerLoopFunction(limit: Int) { + let i = 0; + while (i < limit) { + self.intermediateCall(i); + i += 1; + } + } + // Recursive indirect send + fun recursiveSend(depth: Int, currentValue: Int) { + if (depth > 0) { + send(SendParameters{ + to: self.owner, + value: currentValue + }); + self.recursiveSend(depth - 1, currentValue + 1); + } + } + // Conditional indirect send within loop + fun conditionalIndirectSend(items: map) { + foreach (key, value in items) { + if (value > 0) { + self.deepNestSend(value); + } + } + } +} \ No newline at end of file