From 30b2cbd7ffd47dba5d5efb66e974b665316b8d33 Mon Sep 17 00:00:00 2001 From: Byakuren Hijiri Date: Thu, 30 May 2024 08:13:13 -0300 Subject: [PATCH 01/10] chore(readOnlyVariables): Refactor --- src/detectors/builtin/readOnlyVariables.ts | 25 +++++++++------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/detectors/builtin/readOnlyVariables.ts b/src/detectors/builtin/readOnlyVariables.ts index a029e914..8e515552 100644 --- a/src/detectors/builtin/readOnlyVariables.ts +++ b/src/detectors/builtin/readOnlyVariables.ts @@ -106,6 +106,13 @@ export class ReadOnlyVariables extends Detector { * @param ctx The Souffle program to which the facts are added. */ addConstraints(cu: CompilationUnit, ctx: Context) { + const addUses = (funName: string, node: ASTStatement | ASTExpression) => { + forEachExpression(node, (expr: ASTExpression) => { + if (expr.kind === "id") { + ctx.addFact("varUse", Fact.from([expr.value, funName], expr.ref)); + } + }); + }; cu.forEachCFG(cu.ast, (cfg: CFG, _: Node, stmt: ASTStatement) => { if (cfg.origin === "stdlib") { return; @@ -114,11 +121,7 @@ export class ReadOnlyVariables extends Detector { switch (stmt.kind) { case "statement_let": ctx.addFact("varDecl", Fact.from([stmt.name, funName], stmt.ref)); - forEachExpression(stmt.expression, (expr: ASTExpression) => { - if (expr.kind === "id") { - ctx.addFact("varUse", Fact.from([expr.value, funName], expr.ref)); - } - }); + addUses(funName, stmt.expression); break; case "statement_assign": case "statement_augmentedassign": @@ -126,18 +129,10 @@ export class ReadOnlyVariables extends Detector { "varAssign", Fact.from([stmt.path[0].name, funName], stmt.ref), ); - forEachExpression(stmt.expression, (expr: ASTExpression) => { - if (expr.kind === "id") { - ctx.addFact("varUse", Fact.from([expr.value, funName], expr.ref)); - } - }); + addUses(funName, stmt.expression); break; default: - forEachExpression(stmt, (expr: ASTExpression) => { - if (expr.kind === "id") { - ctx.addFact("varUse", Fact.from([expr.value, funName], stmt.ref)); - } - }); + addUses(funName, stmt); break; } }); From b21b3fc0d5ca1d8c1077455f0b21e523a494a887 Mon Sep 17 00:00:00 2001 From: Byakuren Hijiri Date: Mon, 24 Jun 2024 03:59:56 +0000 Subject: [PATCH 02/10] feat(solver): Draft a solver for fixpoint evaluation --- src/internals/ir.ts | 48 ++++++++++++++++++++++ src/internals/solver.ts | 90 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 src/internals/solver.ts diff --git a/src/internals/ir.ts b/src/internals/ir.ts index 78dd2a75..0c38626f 100644 --- a/src/internals/ir.ts +++ b/src/internals/ir.ts @@ -422,6 +422,54 @@ export class CFG { return this.edges[edgesIdx]; } + private traverseNodes( + edgeIdxs: Set, + isSrc: boolean, + ): Node[] | undefined { + return Array.from(edgeIdxs).reduce( + (acc, srcIdx: NodeIdx) => { + if (acc === undefined) { + return undefined; + } + const edge = this.getEdge(srcIdx); + if (edge === undefined) { + return undefined; + } + const targetNode = this.getNode(isSrc ? edge.src : edge.dst); + if (targetNode === undefined) { + return undefined; + } + acc.push(targetNode); + return acc; + }, + [] as Node[] | undefined, + ); + } + + /** + * Returns successors for the given node. + * @returns A list of predecessor nodes or `undefined` if any of the node indexes cannot be found in this CFG. + */ + public getSuccessors(nodeIdx: NodeIdx): Node[] | undefined { + const node = this.getNode(nodeIdx); + if (node === undefined) { + return undefined; + } + return this.traverseNodes(node.dstEdges, false); + } + + /** + * Returns predecessors for the given node. + * @returns A list of predecessor nodes or `undefined` if any of the node indexes cannot be found in this CFG. + */ + public getPredecessors(nodeIdx: NodeIdx): Node[] | undefined { + const node = this.getNode(nodeIdx); + if (node === undefined) { + return undefined; + } + return this.traverseNodes(node.srcEdges, true); + } + /** * Iterates over all nodes in a CFG, applying a callback to each node. * The callback can perform any operation, such as analyzing or transforming the node. diff --git a/src/internals/solver.ts b/src/internals/solver.ts new file mode 100644 index 00000000..1e8fc8fc --- /dev/null +++ b/src/internals/solver.ts @@ -0,0 +1,90 @@ +import { JoinSemilattice } from "./lattice"; +import { CFG, Node } from "./ir"; + +/** + * Results of solving a generic dataflow problem. + * @template State The type representing the state in the dataflow analysis. + */ +export class SolverResults { + private stateMap: Map; + + constructor() { + this.stateMap = new Map(); + } + + public getState(node: Node): State | undefined { + return this.stateMap.get(node); + } + + public setState(node: Node, state: State): void { + this.stateMap.set(node, state); + } + + public getStates(): Map { + return this.stateMap; + } +} + +/** + * Solver for generic dataflow problems. + */ +export class Solver { + private cfg: CFG; + private transfer: (node: Node, state: State) => State; + private lattice: JoinSemilattice; + + /** + * @param transfer A function that defines the transfer operation for a node and its state. + * @param lattice An instance of a lattice that defines the join, bottom, and leq operations. + */ + constructor( + cfg: CFG, + transfer: (node: Node, state: State) => State, + lattice: JoinSemilattice, + ) { + this.cfg = cfg; + this.transfer = transfer; + this.lattice = lattice; + } + + private getPredecessors(node: Node): Node[] { + const predecessors = this.cfg.getPredecessors(node.idx); + if (predecessors === undefined) { + throw new Error( + `Incorrect definition in the CFG: Node #${node.idx} has an undefined predecessor`, + ); + // return []; + } + return predecessors; + } + + /** + * Finds a fixpoint using the worklist algorithm. + * @returns The results of solving the dataflow problem. + */ + public findFixpoint(): SolverResults { + const results = new SolverResults(); + const worklist: Node[] = [...this.cfg.nodes]; + + const nodes: Node[] = this.cfg.nodes; + nodes.forEach((node) => { + results.setState(node, this.lattice.bottom()); + }); + + while (worklist.length > 0) { + const node = worklist.pop()!; + const inState = this.getPredecessors(node).reduce((acc, pred) => { + return this.lattice.join(acc, results.getState(pred)!); + }, this.lattice.bottom()); + + const outState = this.transfer(node, inState); + + if (!this.lattice.leq(outState, results.getState(node)!)) { + results.setState(node, outState); + worklist.push(...this.getPredecessors(node)); + } + } + + return results; + } +} From 73eb22c60e286f1d2406b0222757ee55370feaca Mon Sep 17 00:00:00 2001 From: Byakuren Hijiri Date: Mon, 24 Jun 2024 04:00:35 +0000 Subject: [PATCH 03/10] fix(ir): `get{Edge,Node}s` when idx==0 --- src/internals/ir.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internals/ir.ts b/src/internals/ir.ts index 0c38626f..493d33a2 100644 --- a/src/internals/ir.ts +++ b/src/internals/ir.ts @@ -403,7 +403,7 @@ export class CFG { */ public getNode(idx: NodeIdx): Node | undefined { const nodesIdx = this.nodesMap.get(idx); - if (!nodesIdx) { + if (nodesIdx === undefined) { return undefined; } return this.nodes[nodesIdx]; @@ -416,7 +416,7 @@ export class CFG { */ public getEdge(idx: EdgeIdx): Edge | undefined { const edgesIdx = this.edgesMap.get(idx); - if (!edgesIdx) { + if (edgesIdx === undefined) { return undefined; } return this.edges[edgesIdx]; From 7dea405939120452dbd48ab9e3e7d8899597dfba Mon Sep 17 00:00:00 2001 From: Byakuren Hijiri Date: Mon, 24 Jun 2024 04:03:24 +0000 Subject: [PATCH 04/10] feat(ir): Getters for AST elements --- src/internals/ir.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/internals/ir.ts b/src/internals/ir.ts index 493d33a2..43af275b 100644 --- a/src/internals/ir.ts +++ b/src/internals/ir.ts @@ -70,6 +70,48 @@ export class TactASTStore { }, [] as ASTNode[]); } + /** + * Returns all the functions and methods defined within the program. + */ + getFunctions(): IterableIterator { + return this.functions.values(); + } + + /** + * Returns all the constants defined within the program, including top-level constants + * and contract constants. + */ + getConstants(): IterableIterator { + return this.constants.values(); + } + + getContracts(): IterableIterator { + return this.contracts.values(); + } + + getNativeFunctions(): IterableIterator { + return this.nativeFunctions.values(); + } + + getPrimitives(): IterableIterator { + return this.primitives.values(); + } + + getStructs(): IterableIterator { + return this.structs.values(); + } + + getTraits(): IterableIterator { + return this.traits.values(); + } + + /** + * Returns all the statements defined within the program. + */ + getStatements(): IterableIterator { + return this.statements.values(); + } + /** * Retrieves a function or method by its ID. * @param id The unique identifier of the function or method. From 37d280af470882f91045630ff487ef78fd1ae996 Mon Sep 17 00:00:00 2001 From: Byakuren Hijiri Date: Mon, 24 Jun 2024 04:06:04 +0000 Subject: [PATCH 05/10] chore(solver): Use node indexes instead of nodes --- src/internals/solver.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/internals/solver.ts b/src/internals/solver.ts index 1e8fc8fc..4b53736b 100644 --- a/src/internals/solver.ts +++ b/src/internals/solver.ts @@ -1,26 +1,26 @@ import { JoinSemilattice } from "./lattice"; -import { CFG, Node } from "./ir"; +import { CFG, Node, NodeIdx } from "./ir"; /** * Results of solving a generic dataflow problem. * @template State The type representing the state in the dataflow analysis. */ export class SolverResults { - private stateMap: Map; + private stateMap: Map; constructor() { this.stateMap = new Map(); } - public getState(node: Node): State | undefined { - return this.stateMap.get(node); + public getState(idx: NodeIdx): State | undefined { + return this.stateMap.get(idx); } - public setState(node: Node, state: State): void { - this.stateMap.set(node, state); + public setState(idx: NodeIdx, state: State): void { + this.stateMap.set(idx, state); } - public getStates(): Map { + public getStates(): Map { return this.stateMap; } } @@ -68,19 +68,19 @@ export class Solver { const nodes: Node[] = this.cfg.nodes; nodes.forEach((node) => { - results.setState(node, this.lattice.bottom()); + results.setState(node.idx, this.lattice.bottom()); }); while (worklist.length > 0) { const node = worklist.pop()!; const inState = this.getPredecessors(node).reduce((acc, pred) => { - return this.lattice.join(acc, results.getState(pred)!); + return this.lattice.join(acc, results.getState(pred.idx)!); }, this.lattice.bottom()); const outState = this.transfer(node, inState); - if (!this.lattice.leq(outState, results.getState(node)!)) { - results.setState(node, outState); + if (!this.lattice.leq(outState, results.getState(node.idx)!)) { + results.setState(node.idx, outState); worklist.push(...this.getPredecessors(node)); } } From f184bea1dcb494861e345a5a0cf0936ea0c00529 Mon Sep 17 00:00:00 2001 From: Byakuren Hijiri Date: Mon, 24 Jun 2024 04:22:57 +0000 Subject: [PATCH 06/10] feat: Add the `neverAccessedVariables` lint --- .../builtin/neverAccessedVariables.ts | 175 ++++++++++++++++++ src/detectors/detector.ts | 4 + src/internals/config.ts | 1 + test/contracts/never-accessed-1.tact | 7 + 4 files changed, 187 insertions(+) create mode 100644 src/detectors/builtin/neverAccessedVariables.ts create mode 100644 test/contracts/never-accessed-1.tact diff --git a/src/detectors/builtin/neverAccessedVariables.ts b/src/detectors/builtin/neverAccessedVariables.ts new file mode 100644 index 00000000..5ed2ab7e --- /dev/null +++ b/src/detectors/builtin/neverAccessedVariables.ts @@ -0,0 +1,175 @@ +import { ASTStatement, ASTRef } from "@tact-lang/compiler/dist/grammar/ast"; +import { Solver } from "../../internals/solver"; +import { Detector } from "../detector"; +import { JoinSemilattice } from "../../internals/lattice"; +import { MistiContext } from "../../internals/context"; +import { CompilationUnit, Node, CFG } from "../../internals/ir"; +import { createError, MistiTactError, Severity } from "../../internals/errors"; +import { forEachExpression } from "../../internals/tactASTUtil"; + +type FieldName = string; +type ConstantName = string; + +interface VariableState { + declared: Set<[string, ASTRef]>; + used: Set; +} + +/** + * A powerset lattice that keeps state of local varialbes within control flow. + */ +class VariableUsageLattice implements JoinSemilattice { + bottom(): VariableState { + return { declared: new Set(), used: new Set() }; + } + + join(a: VariableState, b: VariableState): VariableState { + const joinedDeclared = new Set([...a.declared, ...b.declared]); + const joinedUsed = new Set([...a.used, ...b.used]); + return { declared: joinedDeclared, used: joinedUsed }; + } + + leq(a: VariableState, b: VariableState): boolean { + return ( + [...a.declared].every((x) => b.declared.has(x)) && + [...a.used].every((x) => b.used.has(x)) + ); + } +} + +/** + * A detector that identifies write-only or unused variables, fields and constants. + * + * These variables are either assigned but never used in any meaningful computation, + * or they are declared and never used at all, which may indicate redundant code + * or an incomplete implementation of the intended logic. + */ +export class NeverAccessedVariables extends Detector { + check(_ctx: MistiContext, cu: CompilationUnit): MistiTactError[] { + return [ + ...this.checkFields(cu), + ...this.checkConstants(cu), + ...this.checkVariables(cu), + ]; + } + + checkFields(cu: CompilationUnit): MistiTactError[] { + const definedFields = this.collectDefinedFields(cu); + const usedFields = this.collectUsedFields(cu); + return Array.from( + new Set( + [...definedFields].filter(([name, _ref]) => !usedFields.has(name)), + ), + ).map(([name, ref]) => + createError(`Field ${name} is never used`, Severity.MEDIUM, ref), + ); + } + + collectDefinedFields(cu: CompilationUnit): Set<[FieldName, ASTRef]> { + return Array.from(cu.ast.getContracts()).reduce((acc, contract) => { + contract.declarations.forEach((decl) => { + if (decl.kind === "def_field") { + acc.add([decl.name, decl.ref]); + } + }); + return acc; + }, new Set<[FieldName, ASTRef]>()); + } + + collectUsedFields(cu: CompilationUnit): Set { + return Array.from(cu.ast.getFunctions()).reduce((acc, fun) => { + forEachExpression(fun, (expr) => { + if (expr.kind === "op_field" && expr.src.kind === "id") { + acc.add(expr.src.value); + } + }); + return acc; + }, new Set()); + } + + checkConstants(cu: CompilationUnit): MistiTactError[] { + const definedConstants = this.collectDefinedConstants(cu); + const usedConstants = this.collectUsedNames(cu); + return Array.from( + new Set( + [...definedConstants].filter( + ([name, _ref]) => !usedConstants.has(name), + ), + ), + ).map(([name, ref]) => + createError(`Constant ${name} is never used`, Severity.MEDIUM, ref), + ); + } + + collectDefinedConstants(cu: CompilationUnit): Set<[ConstantName, ASTRef]> { + return Array.from(cu.ast.getConstants()).reduce((acc, constant) => { + acc.add([constant.name, constant.ref]); + return acc; + }, new Set<[ConstantName, ASTRef]>()); + } + + /** + * Collects all the identifiers using withing all the statements. + */ + collectUsedNames(cu: CompilationUnit): Set { + return Array.from(cu.ast.getStatements()).reduce((acc, stmt) => { + forEachExpression(stmt, (expr) => { + if (expr.kind === "id") { + acc.add(expr.value); + } + }); + return acc; + }, new Set()); + } + + /** + * Checks never accessed local variables in all the functions leveraging the + * monotonic framework and the fixpoint dataflow solver. + */ + checkVariables(cu: CompilationUnit): MistiTactError[] { + const errors: MistiTactError[] = []; + cu.forEachCFG(cu.ast, (cfg: CFG, _: Node, stmt: ASTStatement) => { + if (cfg.origin === "stdlib") { + return; + } + const lattice = new VariableUsageLattice(); + const transfer = (_node: Node, inState: VariableState) => { + const outState = { ...inState }; + if (stmt.kind === "statement_let") { + outState.declared.add([stmt.name, stmt.ref]); + } else { + forEachExpression(stmt, (expr) => { + if (expr.kind === "id") { + outState.used.add(expr.value); + } + }); + } + return outState; + }; + const solver = new Solver(cfg, transfer, lattice); + const results = solver.findFixpoint(); + + const declaredVariables = new Map(); + const usedVariables = new Set(); + results.getStates().forEach((state) => { + state.declared.forEach(([name, ref]) => + declaredVariables.set(name, ref), + ); + state.used.forEach((name) => usedVariables.add(name)); + }); + Array.from(declaredVariables.keys()).forEach((name) => { + if (!usedVariables.has(name)) { + errors.push( + createError( + `Variable ${name} is never accessed`, + Severity.MEDIUM, + declaredVariables.get(name)!, + ), + ); + } + }); + }); + + return errors; + } +} diff --git a/src/detectors/detector.ts b/src/detectors/detector.ts index b9a2aaa0..a4160019 100644 --- a/src/detectors/detector.ts +++ b/src/detectors/detector.ts @@ -32,6 +32,10 @@ const BuiltInDetectors: Record Promise> = { import("./builtin/readOnlyVariables").then( (module) => new module.ReadOnlyVariables(), ), + NeverAccessedVariables: () => + import("./builtin/neverAccessedVariables").then( + (module) => new module.NeverAccessedVariables(), + ), ZeroAddress: () => import("./builtin/zeroAddress").then((module) => new module.ZeroAddress()), }; diff --git a/src/internals/config.ts b/src/internals/config.ts index c544f987..75282b9f 100644 --- a/src/internals/config.ts +++ b/src/internals/config.ts @@ -23,6 +23,7 @@ const ConfigSchema = z.object({ /** Built-in detectors enabled by default, if no user configuration is provided. */ export const BUILTIN_DETECTORS: DetectorConfig[] = [ { className: "ReadOnlyVariables" }, + { className: "NeverAccessedVariables" }, { className: "ZeroAddress" }, ]; diff --git a/test/contracts/never-accessed-1.tact b/test/contracts/never-accessed-1.tact new file mode 100644 index 00000000..c253bacb --- /dev/null +++ b/test/contracts/never-accessed-1.tact @@ -0,0 +1,7 @@ +fun test(): Int { + let a: Int = 20; + if (true) { // error: write-only variable + a = 42; + } + return a; +} From 8bd43a55d31b2b42c8d822e4301790a19f80ce17 Mon Sep 17 00:00:00 2001 From: Byakuren Hijiri Date: Mon, 24 Jun 2024 04:29:55 +0000 Subject: [PATCH 07/10] chore(ir): Warning message --- src/internals/ir.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internals/ir.ts b/src/internals/ir.ts index 43af275b..757a9ae7 100644 --- a/src/internals/ir.ts +++ b/src/internals/ir.ts @@ -527,7 +527,7 @@ export class CFG { if (astNode) { callback(astNode, cfgNode); } else { - throw new Error(`Cannot find a statement: id=${cfgNode.stmtID}`); + throw new Error(`Cannot find a statement: #${cfgNode.stmtID}`); } }); } From 652dc18365eb009d6cdbd49cf4dcf20260109bf6 Mon Sep 17 00:00:00 2001 From: Byakuren Hijiri Date: Fri, 28 Jun 2024 01:55:42 +0000 Subject: [PATCH 08/10] feat(ir): Save IDs of constants defined in stdlib --- src/internals/ir.ts | 17 ++++++++++++++--- src/internals/tactIRBuilder.ts | 30 +++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/internals/ir.ts b/src/internals/ir.ts index 757a9ae7..e59e3e01 100644 --- a/src/internals/ir.ts +++ b/src/internals/ir.ts @@ -16,12 +16,15 @@ import { export type ProjectName = string; +export type EntryOrigin = "user" | "stdlib"; + /** * Provides storage and access to various AST components of a Tact project. */ export class TactASTStore { /** * Constructs a TactASTStore with mappings to all major AST components. + * @param stdlibConstants Identifiers of constants defined in stdlib. * @param programEntries Identifiers of AST elements defined on the top-level. * @param functions Functions and methods including user-defined and special methods. * @param constants Constants defined across the compilation unit. @@ -33,6 +36,7 @@ export class TactASTStore { * @param statements All executable statements within all functions of the project. */ constructor( + private stdlibConstants = new Set(), private programEntries: Set, private functions: Map, private constants: Map, @@ -80,9 +84,16 @@ export class TactASTStore { /** * Returns all the constants defined within the program, including top-level constants * and contract constants. + * @param includeStdlib If true, includes constants defined in stdlib. */ - getConstants(): IterableIterator { - return this.constants.values(); + getConstants(allowStdlib: boolean = true): IterableIterator { + if (allowStdlib) { + return this.constants.values(); + } + const userConstants = Array.from(this.constants.values()).filter( + (c) => !this.stdlibConstants.has(c.id), + ); + return userConstants.values(); } getContracts(): IterableIterator { @@ -416,7 +427,7 @@ export class CFG { constructor( public name: FunctionName, public kind: FunctionKind, - public origin: "user" | "stdlib", + public origin: EntryOrigin, public nodes: Node[], public edges: Edge[], public ref: ASTRef, diff --git a/src/internals/tactIRBuilder.ts b/src/internals/tactIRBuilder.ts index 84cd56d9..8b71aa23 100644 --- a/src/internals/tactIRBuilder.ts +++ b/src/internals/tactIRBuilder.ts @@ -77,11 +77,30 @@ function generateReceiveName(receive: ASTReceive): string { } } +/** + * A mandatory part of the file path to stdlib. + */ +const STDLIB_PATH_ELEMENTS = [ + "node_modules", + "@tact-lang", + "compiler", + "stdlib", +]; + +/** + * Checks if there are subdirectories present in the absolute path. + */ +function hasSubdirs(filePath: string, subdirs: string[]): boolean { + const splitPath = filePath.split(path.sep); + return subdirs.every((dir) => splitPath.includes(dir)); +} + /** * Transforms the TactAST imported from the tact compiler to a representation more suitable for analysis. */ export class ASTMapper { private programEntries = new Set(); + private stdlibConstants = new Set(); private functions = new Map< number, ASTFunction | ASTReceive | ASTInitFunction @@ -104,6 +123,12 @@ export class ASTMapper { } }); this.ast.constants.forEach((constant) => { + if ( + constant.ref.file !== null && + hasSubdirs(constant.ref.file, STDLIB_PATH_ELEMENTS) + ) { + this.stdlibConstants.add(constant.id); + } this.programEntries.add(constant.id); this.constants.set(constant.id, constant); }); @@ -115,6 +140,7 @@ export class ASTMapper { public getASTStore(): TactASTStore { return new TactASTStore( + this.stdlibConstants, this.programEntries, this.functions, this.constants, @@ -714,9 +740,7 @@ class TactConfigManager { const stdlibPath = path.resolve( __dirname, distPathPrefix, - "node_modules", - "@tact-lang/compiler", - "stdlib", + ...STDLIB_PATH_ELEMENTS, ); const stdlib = createNodeFileSystem(stdlibPath, false); return this.config.projects.reduce( From 51c40fcef3643073ca3a7b16d49162364a5a70d9 Mon Sep 17 00:00:00 2001 From: Byakuren Hijiri Date: Fri, 28 Jun 2024 01:55:57 +0000 Subject: [PATCH 09/10] fix(nav): Exclude warnings on stdlib constants --- src/detectors/builtin/neverAccessedVariables.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/detectors/builtin/neverAccessedVariables.ts b/src/detectors/builtin/neverAccessedVariables.ts index 5ed2ab7e..93dd66f7 100644 --- a/src/detectors/builtin/neverAccessedVariables.ts +++ b/src/detectors/builtin/neverAccessedVariables.ts @@ -102,7 +102,7 @@ export class NeverAccessedVariables extends Detector { } collectDefinedConstants(cu: CompilationUnit): Set<[ConstantName, ASTRef]> { - return Array.from(cu.ast.getConstants()).reduce((acc, constant) => { + return Array.from(cu.ast.getConstants(false)).reduce((acc, constant) => { acc.add([constant.name, constant.ref]); return acc; }, new Set<[ConstantName, ASTRef]>()); From 800a4237cc98a53fd29fbc39df2a95541146fe0d Mon Sep 17 00:00:00 2001 From: Byakuren Hijiri Date: Fri, 28 Jun 2024 02:00:00 +0000 Subject: [PATCH 10/10] feat(tests): Bless outputs --- test/contracts/never-accessed-1.cfg.json | 71 +++++++++++++++++ test/contracts/never-accessed-1.expected.out | 6 ++ test/contracts/readonly-1.cfg.json | 82 ++++++++++---------- test/contracts/readonly-2.cfg.json | 32 ++++---- test/contracts/zero-address.cfg.json | 4 +- 5 files changed, 136 insertions(+), 59 deletions(-) create mode 100644 test/contracts/never-accessed-1.cfg.json create mode 100644 test/contracts/never-accessed-1.expected.out diff --git a/test/contracts/never-accessed-1.cfg.json b/test/contracts/never-accessed-1.cfg.json new file mode 100644 index 00000000..0e3d052f --- /dev/null +++ b/test/contracts/never-accessed-1.cfg.json @@ -0,0 +1,71 @@ +{ + "projectName": "never-accessed-1", + "functions": [ + { + "name": "test", + "cfg": { + "nodes": [ + { + "id": 606, + "stmtID": 8043, + "srcEdges": [], + "dstEdges": [ + 608 + ] + }, + { + "id": 607, + "stmtID": 8048, + "srcEdges": [ + 608 + ], + "dstEdges": [ + 610, + 611, + 613 + ] + }, + { + "id": 609, + "stmtID": 8047, + "srcEdges": [ + 610 + ], + "dstEdges": [] + }, + { + "id": 612, + "stmtID": 8050, + "srcEdges": [ + 613 + ], + "dstEdges": [] + } + ], + "edges": [ + { + "id": 608, + "src": 606, + "dst": 607 + }, + { + "id": 610, + "src": 607, + "dst": 609 + }, + { + "id": 611, + "src": 607, + "dst": 8050 + }, + { + "id": 613, + "src": 607, + "dst": 612 + } + ] + } + } + ], + "contracts": [] +} \ No newline at end of file diff --git a/test/contracts/never-accessed-1.expected.out b/test/contracts/never-accessed-1.expected.out new file mode 100644 index 00000000..59884c3f --- /dev/null +++ b/test/contracts/never-accessed-1.expected.out @@ -0,0 +1,6 @@ +test/contracts/never-accessed-1.tact:2:5: + 1 | fun test(): Int { +> 2 | let a: Int = 20; + ^ + 3 | if (true) { // error: write-only variable +Variable a is never accessed diff --git a/test/contracts/readonly-1.cfg.json b/test/contracts/readonly-1.cfg.json index 0f23d763..cd4eed88 100644 --- a/test/contracts/readonly-1.cfg.json +++ b/test/contracts/readonly-1.cfg.json @@ -6,92 +6,92 @@ "cfg": { "nodes": [ { - "id": 606, - "stmtID": 8051, + "id": 758, + "stmtID": 10053, "srcEdges": [], "dstEdges": [ - 608 + 760 ] }, { - "id": 607, - "stmtID": 8054, + "id": 759, + "stmtID": 10056, "srcEdges": [ - 608 + 760 ], "dstEdges": [ - 610 + 762 ] }, { - "id": 609, - "stmtID": 8057, + "id": 761, + "stmtID": 10059, "srcEdges": [ - 610 + 762 ], "dstEdges": [ - 612 + 764 ] }, { - "id": 611, - "stmtID": 8064, + "id": 763, + "stmtID": 10066, "srcEdges": [ - 612 + 764 ], "dstEdges": [ - 614, - 615, - 617 + 766, + 767, + 769 ] }, { - "id": 613, - "stmtID": 8063, + "id": 765, + "stmtID": 10065, "srcEdges": [ - 614 + 766 ], "dstEdges": [] }, { - "id": 616, - "stmtID": 8066, + "id": 768, + "stmtID": 10068, "srcEdges": [ - 617 + 769 ], "dstEdges": [] } ], "edges": [ { - "id": 608, - "src": 606, - "dst": 607 + "id": 760, + "src": 758, + "dst": 759 }, { - "id": 610, - "src": 607, - "dst": 609 + "id": 762, + "src": 759, + "dst": 761 }, { - "id": 612, - "src": 609, - "dst": 611 + "id": 764, + "src": 761, + "dst": 763 }, { - "id": 614, - "src": 611, - "dst": 613 + "id": 766, + "src": 763, + "dst": 765 }, { - "id": 615, - "src": 611, - "dst": 8066 + "id": 767, + "src": 763, + "dst": 10068 }, { - "id": 617, - "src": 611, - "dst": 616 + "id": 769, + "src": 763, + "dst": 768 } ] } diff --git a/test/contracts/readonly-2.cfg.json b/test/contracts/readonly-2.cfg.json index d579de64..d7078478 100644 --- a/test/contracts/readonly-2.cfg.json +++ b/test/contracts/readonly-2.cfg.json @@ -6,42 +6,42 @@ "cfg": { "nodes": [ { - "id": 762, - "stmtID": 10059, + "id": 914, + "stmtID": 12061, "srcEdges": [], "dstEdges": [ - 764 + 916 ] }, { - "id": 763, - "stmtID": 10062, + "id": 915, + "stmtID": 12064, "srcEdges": [ - 764 + 916 ], "dstEdges": [ - 766 + 918 ] }, { - "id": 765, - "stmtID": 10064, + "id": 917, + "stmtID": 12066, "srcEdges": [ - 766 + 918 ], "dstEdges": [] } ], "edges": [ { - "id": 764, - "src": 762, - "dst": 763 + "id": 916, + "src": 914, + "dst": 915 }, { - "id": 766, - "src": 763, - "dst": 765 + "id": 918, + "src": 915, + "dst": 917 } ] } diff --git a/test/contracts/zero-address.cfg.json b/test/contracts/zero-address.cfg.json index be422be8..c4cbf9ad 100644 --- a/test/contracts/zero-address.cfg.json +++ b/test/contracts/zero-address.cfg.json @@ -10,8 +10,8 @@ "cfg": { "nodes": [ { - "id": 911, - "stmtID": 12053, + "id": 1063, + "stmtID": 14055, "srcEdges": [], "dstEdges": [] }