Skip to content

Commit

Permalink
feat(cg): Add asm functions (#277)
Browse files Browse the repository at this point in the history
Closes #240
  • Loading branch information
jubnzv authored Feb 18, 2025
1 parent 7eba780 commit 994eda7
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 213 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- File-scoped CFG dumps: Issue [#241](https://github.com/nowarp/misti/issues/241)
- CLI option to disable Soufflé: Issue [#260](https://github.com/nowarp/misti/issues/260)
- Save logs to JSON output: PR [#275](https://github.com/nowarp/misti/pull/275)
- Callgraph: Add `asm` functions: PR [#277](https://github.com/nowarp/misti/pull/277)

### Changed
- Display `warn` logger messages to `stderr` instead of `stdout`: Issue [#259](https://github.com/nowarp/misti/issues/259)
Expand Down
76 changes: 36 additions & 40 deletions src/internals/ir/callGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,25 @@ import {
AstExpression,
AstMethodCall,
AstStaticCall,
AstContract,
AstId,
AstContractDeclaration,
AstNode,
AstFieldAccess,
AstStatement,
idText,
AstModule,
AstAsmFunctionDef,
} from "@tact-lang/compiler/dist/grammar/ast";
import { SrcInfo } from "@tact-lang/compiler/dist/grammar/grammar";
import { prettyPrint } from "@tact-lang/compiler/dist/prettyPrinter";

export type CGNodeId = number & { readonly brand: unique symbol };
export type CGEdgeId = number & { readonly brand: unique symbol };

type SupportedFunDefs =
| AstFunctionDef
| AstReceiver
| AstContractInit
| AstAsmFunctionDef;

/**
* Effects flags for callgraph functions
*/
Expand Down Expand Up @@ -232,13 +236,13 @@ export class CallGraph {
public build(astStore: AstStore): CallGraph {
astStore.getProgramEntries({ includeStdlib: true }).forEach((entry) => {
if (entry.kind === "contract") {
const contract = entry as AstContract;
const contract = entry;
const contractName = contract.name.text;
contract.declarations.forEach((declaration) => {
this.addContractDeclarationToGraph(declaration, contractName);
});
} else if (entry.kind === "function_def") {
const func = entry as AstFunctionDef;
const func = entry;
this.addFunctionToGraph(func);
}
});
Expand All @@ -255,52 +259,47 @@ export class CallGraph {
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);
switch (declaration.kind) {
case "asm_function_def":
case "function_def":
case "contract_init":
case "receiver":
this.addFunctionToGraph(declaration, contractName);
default:
break; // do nothing
}
}

/**
* 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,
) {
private addFunctionToGraph(func: SupportedFunDefs, contractName?: string) {
const funcName = this.getFunctionName(func, contractName);
if (funcName) {
const node = new CGNode(func, funcName, this.logger);
this.nodeMap.set(node.idx, node);
this.nameToNodeId.set(funcName, node.idx);
this.astIdToNodeId.set(func.id, node.idx);
} else {
this.logger.error(
`Function with id ${func.id} has no name and will be skipped.`,
);
}
const node = new CGNode(func, funcName, this.logger);
this.nodeMap.set(node.idx, node);
this.nameToNodeId.set(funcName, node.idx);
this.astIdToNodeId.set(func.id, node.idx);
}

/**
* 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,
func: SupportedFunDefs,
contractName?: string,
): string | undefined {
): string | never {
switch (func.kind) {
case "asm_function_def":
return contractName
? `asm_${contractName}::${func.name.text}`
: `asm_${func.name.text}`;
case "function_def":
return contractName
? `${contractName}::${func.name?.text}`
: func.name?.text;
? `${contractName}::${func.name.text}`
: func.name.text;
case "contract_init":
return contractName
? `${contractName}::contract_init_${func.id}`
Expand All @@ -322,7 +321,7 @@ export class CallGraph {
private analyzeFunctionCalls(astStore: AstStore) {
for (const entry of astStore.getProgramEntries()) {
if (entry.kind === "contract") {
const contract = entry as AstContract;
const contract = entry;
for (const declaration of contract.declarations) {
if (
declaration.kind === "function_def" ||
Expand All @@ -344,7 +343,7 @@ export class CallGraph {
}
}
} else if (entry.kind === "function_def") {
const func = entry as AstFunctionDef;
const func = entry;
const funcNodeId = this.astIdToNodeId.get(func.id);
if (funcNodeId !== undefined) {
const funcNode = this.getNode(funcNodeId);
Expand Down Expand Up @@ -398,10 +397,7 @@ export class CallGraph {
) {
// Connect CG nodes
if (expr.kind === "static_call" || expr.kind === "method_call") {
const functionName = this.getFunctionCallName(
expr as AstStaticCall | AstMethodCall,
currentContractName,
);
const functionName = this.getFunctionCallName(expr, currentContractName);
if (functionName) {
const calleeId = this.findOrAddFunction(functionName);
this.addEdge(callerId, calleeId);
Expand Down Expand Up @@ -448,7 +444,7 @@ export class CallGraph {
if (methodName) {
let contractName = currentContractName;
if (expr.self.kind === "id") {
const idExpr = expr.self as AstId;
const idExpr = expr.self;
if (idExpr.text !== "self") {
contractName = idExpr.text;
}
Expand Down Expand Up @@ -501,9 +497,9 @@ export class CallGraph {
*/
function isContractStateRead(expr: AstExpression): boolean {
if (expr.kind === "field_access") {
const fieldAccess = expr as AstFieldAccess;
const fieldAccess = expr;
if (fieldAccess.aggregate.kind === "id") {
const idExpr = fieldAccess.aggregate as AstId;
const idExpr = fieldAccess.aggregate;
if (idExpr.text === "self") {
return true;
}
Expand Down
67 changes: 35 additions & 32 deletions test/all/syntax.expected.callgraph.dot
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,48 @@ digraph "CallGraph" {
node_18 [label="fun test_loops()"];
node_19 [label="fun testTryCatch(a: Int)"];
node_20 [label="fun testLoops()"];
node_21 [label="override get fun getter(): Int"];
node_22 [label="fun test()"];
node_23 [label="fun test(): Int"];
node_24 [label="external()"];
node_25 [label="init()
node_21 [label="fun callAsm(s: Slice): Cell"];
node_22 [label="override get fun getter(): Int"];
node_23 [label="fun test()"];
node_24 [label="fun test(): Int"];
node_25 [label="external()"];
node_26 [label="init()
[StateWrite]"];
node_26 [label="fun funcWithSend()
node_27 [label="fun funcWithSend()
[Send,StateRead]"];
node_27 [label="fun funcWithStateRead()
node_28 [label="fun funcWithStateRead()
[StateRead]"];
node_28 [label="fun funcWithStateWrite()
node_29 [label="fun funcWithStateWrite()
[StateWrite]"];
node_29 [label="fun funcWithMultipleEffects()
node_30 [label="fun funcWithMultipleEffects()
[AccessDatetime,PrgUse,PrgSeedInit]"];
node_30 [label="receive()"];
node_31 [label="dump"];
node_32 [label="emptyMap"];
node_33 [label="m::set"];
node_34 [label="getA"];
node_35 [label="sender"];
node_36 [label="newAddress"];
node_37 [label="now"];
node_38 [label="nativeRandomizeLt"];
node_39 [label="beginString"];
node_40 [label="a::append"];
node_17 -> node_31;
node_18 -> node_32;
node_19 -> node_31;
node_20 -> node_32;
node_31 [label="receive()"];
node_32 [label="dump"];
node_33 [label="emptyMap"];
node_34 [label="m::set"];
node_35 [label="s::loadRefEx"];
node_36 [label="getA"];
node_37 [label="sender"];
node_38 [label="newAddress"];
node_39 [label="now"];
node_40 [label="nativeRandomizeLt"];
node_41 [label="beginString"];
node_42 [label="a::append"];
node_17 -> node_32;
node_18 -> node_33;
node_19 -> node_32;
node_20 -> node_33;
node_20 -> node_33;
node_20 -> node_33;
node_23 -> node_34;
node_25 -> node_35;
node_26 -> node_15;
node_28 -> node_36;
node_29 -> node_37;
node_29 -> node_12;
node_20 -> node_34;
node_20 -> node_34;
node_20 -> node_34;
node_21 -> node_35;
node_24 -> node_36;
node_26 -> node_37;
node_27 -> node_15;
node_29 -> node_38;
node_30 -> node_39;
node_30 -> node_12;
node_30 -> node_40;
node_31 -> node_41;
node_31 -> node_42;
}
Loading

0 comments on commit 994eda7

Please sign in to comment.