smt

package
v0.14.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 26, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package smt provides SMT-LIB encoding for AILANG contract verification. It translates AILANG Core AST functions with contracts into SMT-LIB format for verification by Z3 or other SMT solvers.

Index

Constants

This section is empty.

Variables

View Source
var BuiltinToSMTOp = map[string]string{

	"add_Int":   "+",
	"add_Float": "+",
	"sub_Int":   "-",
	"sub_Float": "-",
	"mul_Int":   "*",
	"mul_Float": "*",
	"div_Int":   "div",
	"div_Float": "/",
	"mod_Int":   "mod",
	"mod_Float": "mod",

	"eq_Int":   "=",
	"eq_Float": "=",
	"eq_Bool":  "=",
	"ne_Int":   "distinct",
	"ne_Float": "distinct",
	"ne_Bool":  "distinct",
	"lt_Int":   "<",
	"lt_Float": "<",
	"le_Int":   "<=",
	"le_Float": "<=",
	"gt_Int":   ">",
	"gt_Float": ">",
	"ge_Int":   ">=",
	"ge_Float": ">=",

	"and_Bool": "and",
	"or_Bool":  "or",
	"not_Bool": "not",

	"neg_Int":   "-",
	"neg_Float": "-",

	"eq_String": "=",
	"ne_String": "distinct",
	"lt_String": "str.<",
	"le_String": "str.<=",
}

BuiltinToSMTOp maps lowered AILANG builtin names to SMT-LIB operators. After op_lowering, operators become App(VarGlobal($builtin.XXX)) calls. This map reverses that transformation for SMT encoding.

View Source
var ErrUnresolvableTypes = errors.New("unresolvable cross-module types")

ErrUnresolvableTypes indicates that the function references types (ADTs, record aliases) that cannot be fully resolved in Z3. Functions with this error should be SKIPPED, not reported as errors.

View Source
var ListBuiltinSpecial = map[string]ListBuiltinSpec{

	"concat_List": {Op: "seq.++"},

	"::": {Op: "seq.++", ConsMode: true},

	"_list_length": {Op: "seq.len", Unary: true},

	"_list_head": {Op: "seq.nth", Unary: true, AppendZero: true},

	"_list_nth": {Op: "seq.nth"},

	"_list_contains": {Op: "seq.contains", ContainsMode: true},

	"_list_extract": {Op: "seq.extract", TernaryMode: true},

	"_list_tail": {Op: "seq.extract", Unary: true, TailMode: true},
}

ListBuiltinSpecial maps AILANG list builtins that need non-standard encoding.

View Source
var NumericBuiltinSpecial = map[string]NumericBuiltinSpec{
	"intToFloat":    {Op: "to_real", Unary: true},
	"_int_to_float": {Op: "to_real", Unary: true},
	"floatToInt":    {Op: "to_int", Unary: true},
	"_float_to_int": {Op: "to_int", Unary: true},
}

NumericBuiltinSpecial maps AILANG numeric conversion builtins to SMT-LIB operators. intToFloat → Z3 to_real (exact conversion, no semantic gap). floatToInt → Z3 to_int (rounds toward -inf; Go truncates toward zero — document gap).

View Source
var RecursiveListBuiltins = map[string]string{
	"_list_reverse": "reverse",
	"_list_take":    "take",
	"_list_drop":    "drop",
}

RecursiveListBuiltins maps recursive list operation names to their descriptions. These are encoded via bounded unrolling (GenerateListUnrolling) rather than direct Z3 operator mapping.

View Source
var StdlibListToSMT = map[string]string{
	"length":   "_list_length",
	"reverse":  "_list_reverse",
	"take":     "_list_take",
	"drop":     "_list_drop",
	"contains": "_list_contains",
}

StdlibListToSMT maps std/list function names to their builtin equivalents.

View Source
var StdlibStringToSMT = map[string]string{
	"length":     "_str_len",
	"substring":  "_str_slice",
	"find":       "_str_find",
	"startsWith": "_str_startsWith",
	"endsWith":   "_str_endsWith",
	"contains":   "_str_contains",
}

StdlibStringToSMT maps std/string function names to their builtin equivalents. Only trivial wrappers (1:1 forwarding) with Z3 SMT-LIB equivalents are listed.

View Source
var StringBuiltinSpecial = map[string]StringBuiltinSpec{

	"gt_String": {Op: "str.<", FlipArgs: true},

	"ge_String": {Op: "str.<=", FlipArgs: true},

	"concat_String": {Op: "str.++"},

	"_str_len": {Op: "str.len", Unary: true},

	"_str_find": {Op: "str.indexof", AppendZero: true},

	"_str_startsWith": {Op: "str.prefixof", FlipArgs: true},

	"_str_endsWith": {Op: "str.suffixof", FlipArgs: true},

	"_str_slice": {Op: "str.substr", SubstrMode: true},

	"_str_contains": {Op: "str.contains"},
}

StringBuiltinSpecial maps AILANG string builtins that need non-standard encoding. These require special handling (operand flipping, extra args, etc.).

Functions

func AllHigherOrderIsInlinable

func AllHigherOrderIsInlinable(body core.CoreExpr) bool

AllHigherOrderIsInlinable returns true if every lambda-in-arg-position in the body is part of a known HOF call (map_List, filter_List, foldl_List with a literal lambda). Returns true when there is no higher-order at all.

func Assert

func Assert(expr string) string

Assert generates an SMT-LIB assertion.

(assert expr)

func AssertNot

func AssertNot(expr string) string

AssertNot generates a negated SMT-LIB assertion.

(assert (not expr))

func CheckSat

func CheckSat() string

CheckSat returns the check-sat command.

func DeclareConst

func DeclareConst(name, sort string) string

DeclareConst generates an SMT-LIB constant declaration.

(declare-const name sort)

func DeclareDatatype

func DeclareDatatype(typeName string, variants []ADTVariant) string

DeclareDatatype generates an SMT-LIB declare-datatype for an ADT. For enums (all variants have zero fields):

(declare-datatype Season ((LOW_SEASON) (HIGH_SEASON)))

For ADTs with fields:

(declare-datatype Shape ((Circle (radius Int)) (Rect (width Int) (height Int))))

func DeclareEnumDatatype

func DeclareEnumDatatype(typeName string, variants []string) string

DeclareEnumDatatype is a convenience for enums (all nullary constructors).

func DeclareRecordDatatype

func DeclareRecordDatatype(sortName string, fields map[string]string) string

DeclareRecordDatatype generates an SMT-LIB declare-datatype for a record type. Records are modeled as single-constructor datatypes with named field accessors:

(declare-datatype Point ((mk_Point (x Int) (y Int))))

Field order is alphabetical for deterministic output.

func EncodeExpr

func EncodeExpr(expr core.CoreExpr) (string, error)

EncodeExpr translates a Core AST expression to an SMT-LIB expression string.

func FindZ3

func FindZ3() (string, error)

FindZ3 discovers the Z3 binary location. Search order: AILANG_Z3_PATH env, PATH, common locations.

func GenerateListUnrolling

func GenerateListUnrolling(op string, depth int, elemSort string) []string

GenerateListUnrolling generates SMT-LIB declarations for bounded recursive list operations. Returns a slice of SMT-LIB declaration strings, or nil if the operation is not recognized.

For depth N, produces N+1 declarations:

  • Level 0: (declare-fun op_0 ...) — uninterpreted function
  • Levels 1..N: (define-fun op_k ...) where recursive calls use op_(k-1)

Supported operations: _list_reverse, _list_take, _list_drop

func GetModel

func GetModel() string

GetModel returns the get-model command.

func IsRecursiveFunc

func IsRecursiveFunc(body core.CoreExpr, funcName string) bool

IsRecursiveFunc is the exported version of isRecursive for use by CLI code.

func IsUserDefinedCall

func IsUserDefinedCall(app *core.App, prog *core.Program) (string, bool)

IsUserDefinedCall checks whether an App expression represents a call to a user-defined function (not a builtin or ADT constructor). Handles both VarGlobal (cross-module) and plain Var (same-module) references.

func MapRecordFields

func MapRecordFields(rec *types.TRecord) (map[string]string, error)

MapRecordFields maps all fields of a record type to their SMT-LIB sorts. Returns the mapping and an error if any field type is not encodable.

func MapRecordSortName

func MapRecordSortName(rec *types.TRecord) string

MapRecordSortName returns the SMT-LIB sort name for a record type. Named records use their TypeName directly; anonymous records get a deterministic name based on their fields (sorted alphabetically).

func MapType

func MapType(t types.Type) (string, error)

MapType maps an AILANG type to its SMT-LIB sort name. Returns the SMT-LIB sort string and an error if the type is not encodable.

func RecordConstructorName

func RecordConstructorName(sortName string) string

RecordConstructorName returns the SMT-LIB constructor name for a record.

(mk_Point ...) for record type Point

func ReplaceSelfCalls

func ReplaceSelfCalls(expr core.CoreExpr, funcName, replacement string) core.CoreExpr

ReplaceSelfCalls walks the Core AST and replaces references to funcName with replacement. Returns a new AST (does not mutate the original). Respects Lambda/Let/LetRec shadowing.

func ResolveStdlibToBuiltin

func ResolveStdlibToBuiltin(module, name string) (string, bool)

ResolveStdlibToBuiltin checks if a module/function pair refers to a stdlib function with a known SMT mapping, and returns the equivalent builtin name.

func SortedFieldNamesStr

func SortedFieldNamesStr(fields map[string]string) []string

SortedFieldNamesStr returns field names from a string map sorted alphabetically.

func SpecializeFilter

func SpecializeFilter(funcName string, lambda *core.Lambda, listParam string) (core.CoreExpr, string)

SpecializeFilter transforms filter(\x -> pred, xs) into a recursive function:

filter_spec(xs) =
  if seq.len(xs) == 0 then []
  else let h = head(xs) in
       let t = tail(xs) in
       if pred[x -> h] then h :: filter_spec(t)
       else filter_spec(t)

func SpecializeFoldl

func SpecializeFoldl(funcName string, lambda *core.Lambda, listParam string) (core.CoreExpr, string)

SpecializeFoldl transforms foldl(\acc x -> body, init, xs) into a recursive function:

fold_spec(acc, xs) =
  if seq.len(xs) == 0 then acc
  else let h = head(xs) in
       let t = tail(xs) in
       fold_spec(body[acc -> acc, x -> h], t)

Note: The specialized function takes (acc, xs) as parameters. The initial value is passed as the acc argument at the call site.

func SpecializeMap

func SpecializeMap(funcName string, lambda *core.Lambda, listParam string) (core.CoreExpr, string)

SpecializeMap transforms map(\x -> body, xs) into a recursive function:

map_spec(xs) =
  if seq.len(xs) == 0 then (as seq.empty (Seq Int))
  else let h = seq.nth(xs, 0) in
       let t = seq.extract(xs, 1, seq.len(xs) - 1) in
       (seq.++ (seq.unit body[x -> h]) map_spec(t))

Returns the specialized body and the specialized function name.

func SubstituteLambdaVar

func SubstituteLambdaVar(body core.CoreExpr, paramName string, replacement core.CoreExpr) core.CoreExpr

SubstituteLambdaVar replaces all free occurrences of paramName in body with the replacement expression. Respects variable shadowing by Lambda/Let.

func TopLevelUnrolledName

func TopLevelUnrolledName(op string, depth int) string

TopLevelUnrolledName returns the name of the top-level (deepest) unrolled function for a given operation and depth.

func Z3Available

func Z3Available() bool

Z3Available returns true if Z3 is installed and accessible.

func Z3Version

func Z3Version() string

Z3Version returns the installed Z3 version string, or empty if not found.

Types

type ADTField

type ADTField struct {
	Name string // Accessor name (auto-generated if not specified)
	Sort string // SMT-LIB sort name
}

ADTField describes a field within an ADT variant.

type ADTVariant

type ADTVariant struct {
	Name   string
	Fields []ADTField
}

ADTVariant describes a single variant of an algebraic data type.

type CalleeDef

type CalleeDef struct {
	Name    string
	SMTLib  string // The (define-fun ...) declaration
	ADTDecl string // Any ADT declarations needed (may be empty)
}

CalleeDef holds a ready-to-emit SMT-LIB define-fun for a callee.

func ResolveCallees

func ResolveCallees(
	funcName string,
	body core.CoreExpr,
	prog *core.Program,
	surfaceParams map[string][]FunctionParam,
	surfaceReturnSorts map[string]string,
	adtTypes map[string][]ADTVariant,
) ([]CalleeDef, error)

ResolveCallees finds all user-defined function calls in the body, resolves their Core bodies from the program, and returns ordered define-fun declarations for SMT-LIB emission.

The returned definitions are topologically ordered: if A calls B, B's definition appears before A's. Circular calls are detected and produce an error.

type CalleeInfo

type CalleeInfo struct {
	Name       string
	Params     []FunctionParam
	Body       core.CoreExpr
	ReturnSort string
	ADTTypes   map[string][]ADTVariant
}

CalleeInfo describes a resolved callee function for SMT encoding.

type EncodeFunctionOpts

type EncodeFunctionOpts struct {
	// Program is the full Core program (needed for resolving cross-function calls).
	Program *core.Program
	// SurfaceParams maps function names to their parameter info.
	SurfaceParams map[string][]FunctionParam
	// SurfaceReturnSorts maps function names to their return sort strings.
	SurfaceReturnSorts map[string]string
	// ReturnType is the function's return type (used to discover record types in return position).
	ReturnType types.Type
	// Body is the function body expression (used to discover record types constructed in the body).
	Body core.CoreExpr
	// Contracts are the function's contracts (used to discover record types in ensures clauses).
	Contracts []*core.Contract
	// RecursiveDepth enables bounded recursion unrolling at the given depth (1-10).
	// When > 0 and the function is self-recursive, generates a define-fun chain
	// instead of rejecting. 0 means no unrolling (default behavior).
	RecursiveDepth int
	// HOFInlineDepth controls the unrolling depth for HOF specializations
	// (map, filter, foldl with literal lambda arguments). When > 0, inlinable
	// HOF calls are specialized into recursive functions and unrolled.
	// Default: 3 if not specified and HOF calls are detected.
	HOFInlineDepth int
	// ExtraDeclarations are additional SMT-LIB declarations to prepend
	// (e.g., record types found in ADT constructor fields that need
	// to be declared before the ADT itself).
	ExtraDeclarations []string
	// RecordTypeAliases maps named record type aliases (e.g., "TableCell", "Point")
	// to their TRecord types. These are pre-registered during record type collection
	// so that named sorts take priority over anonymous Record_field1_field2 sorts.
	RecordTypeAliases map[string]*types.TRecord
}

EncodeFunctionOpts holds optional parameters for cross-function call resolution.

type EncodeResult

type EncodeResult struct {
	// SMTLib is the complete SMT-LIB program text.
	SMTLib string
	// Declarations are the type/constant declarations.
	Declarations []string
	// Assertions are the assert statements.
	Assertions []string
	// BodyExpr is the encoded function body expression.
	BodyExpr string
}

EncodeResult holds the generated SMT-LIB program for a function.

func EncodeFunction

func EncodeFunction(
	funcName string,
	params []FunctionParam,
	body core.CoreExpr,
	returnSort string,
	meta *core.DeclMeta,
	adtTypes map[string][]ADTVariant,
	opts ...EncodeFunctionOpts,
) (*EncodeResult, error)

EncodeFunction generates a complete SMT-LIB program for verifying a function's contracts.

The encoding follows the standard pattern for bounded verification:

  1. Declare types (datatypes for ADTs)
  2. Declare symbolic variables (function parameters)
  3. Assert preconditions (requires clauses)
  4. Define function body as "result"
  5. Assert negation of postconditions (ensures clauses)
  6. Check satisfiability

If the result is "unsat", all postconditions hold for all inputs satisfying preconditions. If "sat", the model provides a counterexample.

type FunctionParam

type FunctionParam struct {
	Name string
	Type types.Type
}

FunctionParam describes a function parameter for SMT encoding.

type HOFKind

type HOFKind int

HOFKind identifies the type of higher-order function.

const (
	HOFMap    HOFKind = iota // map(lambda, list)
	HOFFilter                // filter(lambda, list)
	HOFFoldl                 // foldl(lambda, init, list)
)

type InlinableHOFCall

type InlinableHOFCall struct {
	Kind    HOFKind
	Lambda  *core.Lambda  // The literal lambda argument
	ListArg core.CoreExpr // The list argument
	InitVal core.CoreExpr // Only for foldl: the initial accumulator value
	// Original is the original App node for replacement
	Original *core.App
}

InlinableHOFCall describes a detected higher-order function call that can be specialized (lambda argument is a literal, not a variable).

func IsInlinableHOF

func IsInlinableHOF(body core.CoreExpr) []InlinableHOFCall

IsInlinableHOF walks the Core AST looking for HOF calls (map, filter, foldl) where the function argument is a literal lambda (not a variable). Returns info about each inlinable call found.

type InlineResult

type InlineResult struct {
	// NewBody is the modified function body with HOF calls replaced
	// by calls to specialized recursive functions.
	NewBody core.CoreExpr
	// Specializations are the unrolled recursive function declarations.
	Specializations []SpecializationResult
}

InlineResult holds the result of HOF inlining for a function body.

func InlineHOFCalls

func InlineHOFCalls(funcName string, body core.CoreExpr, depth int) *InlineResult

InlineHOFCalls detects inlinable HOF calls in the body, generates specialized recursive functions, replaces the HOF calls with calls to the specialized functions, and returns the unrolled declarations. Returns nil if no HOF calls are found.

type ListBuiltinSpec

type ListBuiltinSpec struct {
	Op           string // SMT-LIB operator name
	Unary        bool   // Single argument
	AppendZero   bool   // Append literal 0 as extra argument
	ConsMode     bool   // First arg wrapped in (seq.unit ...)
	ContainsMode bool   // Second arg wrapped in (seq.unit ...) for element containment
	TernaryMode  bool   // Three arguments (e.g., seq.extract xs offset length)
	TailMode     bool   // _list_tail: (seq.extract xs 1 (- (seq.len xs) 1))
}

ListBuiltinSpec describes how to encode a list builtin in SMT-LIB.

type ModelBinding

type ModelBinding struct {
	Name  string `json:"name"`
	Sort  string `json:"sort"`
	Value string `json:"value"`
}

ModelBinding represents a variable assignment from a counterexample.

type NumericBuiltinSpec

type NumericBuiltinSpec struct {
	Op    string // SMT-LIB operator name (to_real, to_int)
	Unary bool   // Always true for conversions
}

NumericBuiltinSpec describes how to encode a numeric conversion builtin in SMT-LIB.

type RecordTypeInfo

type RecordTypeInfo struct {
	SortName   string            // SMT-LIB sort name (e.g., "Point")
	CtorName   string            // Constructor name (e.g., "mk_Point")
	FieldNames []string          // Sorted field names
	FieldSorts map[string]string // Field name → SMT-LIB sort
}

RecordTypeInfo describes a record type for SMT encoding.

type RejectionCode

type RejectionCode string

RejectionCode identifies why a function cannot be SMT-verified.

const (
	RejectNoContracts  RejectionCode = "NO_CONTRACTS"
	RejectNotPure      RejectionCode = "NOT_PURE"
	RejectRecursive    RejectionCode = "RECURSIVE"
	RejectHigherOrder  RejectionCode = "HIGHER_ORDER"
	RejectDeepPatterns RejectionCode = "DEEP_PATTERNS"
	RejectUnencodable  RejectionCode = "UNENCODABLE_TYPE"
)

type SMTContext

type SMTContext struct {
	// Declarations accumulates SMT-LIB type declarations.
	Declarations []string
	// Variables maps AILANG parameter names to their SMT-LIB types.
	Variables map[string]string
	// DeclaredTypes tracks which ADT types have been declared.
	DeclaredTypes map[string]bool
	// ResolvedCallees tracks function names that have been resolved
	// as define-fun declarations for cross-function call support.
	ResolvedCallees map[string]bool
}

SMTContext holds the state needed for SMT-LIB generation.

func NewSMTContext

func NewSMTContext() *SMTContext

NewSMTContext creates a new SMT encoding context.

type SMTRejectionReason

type SMTRejectionReason struct {
	Code     RejectionCode
	Message  string
	Location string // Source location if available
	Hint     string // Suggestion to make it encodable
}

SMTRejectionReason describes why a function cannot be SMT-verified.

func IsSMTEncodable

func IsSMTEncodable(funcName string, meta *core.DeclMeta, body core.CoreExpr) (bool, []SMTRejectionReason)

IsSMTEncodable checks whether a function can be SMT-verified. Returns true with empty reasons if the function is in the decidable fragment. Returns false with rejection reasons otherwise.

func IsSMTEncodableForCallee

func IsSMTEncodableForCallee(funcName string, meta *core.DeclMeta, body core.CoreExpr) (bool, []SMTRejectionReason)

IsSMTEncodableForCallee checks if a function can be used as a callee in cross-function verification. Similar to IsSMTEncodable but doesn't require contracts (callees don't need their own contracts to be inlined).

func (SMTRejectionReason) String

func (r SMTRejectionReason) String() string

type SolverConfig

type SolverConfig struct {
	Z3Path  string        // Path to z3 binary (auto-discovered if empty)
	Timeout time.Duration // Per-function timeout (default 5s)
}

SolverConfig holds configuration for the Z3 solver.

func DefaultSolverConfig

func DefaultSolverConfig() SolverConfig

DefaultSolverConfig returns a SolverConfig with sensible defaults.

type SolverResult

type SolverResult struct {
	Status    SolverStatus
	Model     []ModelBinding // Populated when Status == StatusCounterexample
	Duration  time.Duration
	SMTLib    string // The input SMT-LIB program (for --verbose)
	RawOutput string // Raw Z3 stdout
	Error     string // Error message if Status == StatusError
}

SolverResult holds the result of running Z3 on an SMT-LIB program.

func Solve

func Solve(smtlib string, config SolverConfig) (*SolverResult, error)

Solve runs Z3 on an SMT-LIB program and parses the result.

func SolveFile

func SolveFile(path string, config SolverConfig) (*SolverResult, error)

SolveFile runs Z3 on an SMT-LIB file.

type SolverStatus

type SolverStatus int

SolverStatus represents the result of an SMT solver invocation.

const (
	StatusVerified       SolverStatus = iota // unsat — postconditions hold for all inputs
	StatusCounterexample                     // sat — found inputs that violate postconditions
	StatusUnknown                            // unknown — solver could not determine
	StatusError                              // error — solver error or not found
)

func (SolverStatus) String

func (s SolverStatus) String() string

type SpecializationResult

type SpecializationResult struct {
	// Declarations are the SMT-LIB declare-fun/define-fun chain.
	Declarations []string
	// TopLevelName is the top-level unrolled function name.
	TopLevelName string
	// Kind is which HOF was specialized.
	Kind HOFKind
}

SpecializationResult holds the unrolled declarations for one specialization.

type StringBuiltinSpec

type StringBuiltinSpec struct {
	Op         string // SMT-LIB operator name
	FlipArgs   bool   // Swap arg order (e.g., gt → lt with flipped args)
	Unary      bool   // Single argument
	AppendZero bool   // Append literal 0 as extra argument
	SubstrMode bool   // Convert (s, start, end) → (str.substr s start (- end start))
}

StringBuiltinSpec describes how to encode a string builtin in SMT-LIB.

type UnrollConfig

type UnrollConfig struct {
	FuncName   string          // Name of the recursive function
	Params     []FunctionParam // Function parameters
	Body       core.CoreExpr   // Original function body
	ReturnSort string          // SMT-LIB return sort
	Depth      int             // Unrolling depth (1-10)
}

UnrollConfig holds parameters for bounded recursive function unrolling.

type UnrollResult

type UnrollResult struct {
	// Declarations contains the declare-fun (level 0) and define-fun (levels 1..N).
	Declarations []string
	// TopLevelName is the name of the deepest unrolling (funcName_N).
	TopLevelName string
}

UnrollResult holds the generated SMT-LIB declarations for a bounded unrolling.

func UnrollRecursiveFunction

func UnrollRecursiveFunction(cfg UnrollConfig) (*UnrollResult, error)

UnrollRecursiveFunction generates a chain of define-fun declarations for bounded recursive function verification (Dafny-style unrolling).

For depth N, it produces N+1 declarations:

  • Level 0: (declare-fun funcName_0 ...) — uninterpreted function
  • Levels 1..N: (define-fun funcName_k ...) where self-calls are replaced with funcName_(k-1)

The top-level name funcName_N is used in verification conditions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL