Run every path
your ArkTS code
could take.
A symbolic execution engine for ArkTS and TypeScript. It replaces concrete inputs with symbolic ones, collects a path constraint at every branch, and asks an SMT solver which paths are actually reachable — built on the USVM core.
Concrete and symbolic, working together.
The engine keeps two representations of every value at once. Concrete values run the program forward like normal; symbolic values stand in for "any input" and accumulate the constraints that would make a path possible.
The value you actually pass in
Pinning some variables to real values shrinks the search space and keeps the
solver fast. When arr.length is known, the loop bound is known —
no branching needed.
The value the solver gets to choose
A symbolic α represents every input at once. Each branch records a
constraint on it; the SMT solver then decides which combinations are
satisfiable and reports a concrete witness for each.
A four-stage pipeline.
Each stage hands a cleaner representation to the next — from human-readable ArkTS down to SMT formulas a solver can reason about.
ArkTS source
Your code, with its TypeScript type annotations recovered from the official grammar so argument types survive compilation.
▸ ANTLR · TypeScript grammarThree-address code
The control-flow graph is lowered to a flat instruction set — loops, branches and try/catch made explicit and easy to walk.
▸ JacoDB · ArkTS IRSymbolic interpreter
Each instruction steps the symbolic state, models implicit type coercion, and forks a new state at every reachable branch.
▸ USVM · PandaMachineSMT decision
Path constraints become SMT formulas. The solver discards unsatisfiable paths and returns a concrete input for every state that remains.
▸ KSMT · SMT solverBuilt for the awkward parts of the language.
The hard cases in ArkTS and TypeScript aren't the arithmetic — they're dynamic typing, loops without explicit jumps, and exception flow. Those are modeled directly.
Concolic execution
Mix concrete and symbolic values to cut the state space without losing coverage.
Bidirectional search
Explore paths forward and backward to reach a target state with the right inputs.
TypeScript type model
Argument types are parsed back from source and fed into the constraints as bounds.
Implicit coercion
All sixteen pairs of number · bool · string · object handled via nested ite.
Loops without jumps
Cycles are recovered from the basic-block graph, with phantom gotos pruned away.
Nested try / catch
A traversal-strategy stack models exception scopes, including catches inside catches.
Watch a function split.
Feed in findMinValue with a symbolic array. The engine walks every reachable path and reports the input that triggers each one.
function findMinValue(arr: number[]): number { if (arr.length === 0) throw new Error("Array is empty!"); let minValue = arr[0]; for (let i = 1; i < arr.length; i++) { if (arr[i] < minValue) { minValue = arr[i]; } } return minValue; }
Each state carries its inputs, its path, and its result — enough to flag dead code or generate a covering unit test.
From a bachelor's thesis to a working engine.
This engine grew out of a graduation project at Saint Petersburg State University: extending the USVM symbolic-analysis core with a full ArkTS interpreter, plus the JacoDB changes needed to make it run.