| name | specification-generator |
| description | Generate formal specifications including preconditions, postconditions, invariants, and contracts from code or requirements. Use this skill when documenting APIs, creating formal verification annotations, defining function contracts, specifying class invariants, writing design-by-contract code, or preparing code for formal verification. Supports multiple specification languages including JML, ACSL, Dafny, Eiffel contracts, and documentation annotations. |
Specification Generator
Systematically generate formal specifications from code and requirements. Produces preconditions, postconditions, invariants, and behavioral contracts that enable verification, documentation, and design-by-contract programming.
Core Capabilities
1. Precondition Generation
Define required conditions before execution:
- Parameter validation - Valid ranges, types, states
- Object state requirements - Required object states
- Resource availability - Files exist, connections open
- Authorization checks - Permission requirements
- Null safety - Non-null parameter requirements
- Invariant satisfaction - Class invariants must hold
2. Postcondition Generation
Specify guaranteed outcomes after execution:
- Return value constraints - Valid return ranges and properties
- State modifications - How object state changes
- Side effects - File writes, database updates
- Exception conditions - When exceptions are thrown
- Resource management - Resources properly released
- Invariant preservation - Invariants still hold
3. Invariant Generation
Identify properties that always hold:
- Class invariants - Properties true for all instances
- Loop invariants - Properties preserved by iterations
- Data structure invariants - Consistency conditions
- Representation invariants - Internal consistency
- Temporal invariants - Time-based properties
4. Contract Generation
Create complete behavioral specifications:
- Method contracts - Full pre/post conditions
- Interface contracts - API behavioral guarantees
- Type contracts - Type-level properties
- Protocol contracts - Valid call sequences
- Frame conditions - What doesn't change
Specification Generation Workflow
Step 1: Analyze Code or Requirements
Understand what needs to be specified:
For existing code:
- Read function/method implementation
- Identify parameters and return type
- Find state modifications
- Detect resource usage
- Locate error conditions
For requirements:
- Extract functional requirements
- Identify constraints and rules
- Find boundary conditions
- Determine error scenarios
- Specify expected behavior
Example code:
def withdraw(account, amount):
if account.balance < amount:
raise InsufficientFundsError()
account.balance -= amount
return account.balance
Analysis:
- Parameters: account (object), amount (number)
- Return: new balance (number)
- State change: account.balance decreases
- Error: raises exception if insufficient funds
- Constraints: amount should be positive, account must exist
Step 2: Identify Contract Elements
Determine what to specify:
Preconditions (requires/assume):
- What must be true before execution?
- Parameter validity requirements
- Object state requirements
Postconditions (ensures/guarantee):
- What is guaranteed after execution?
- Return value properties
- State changes
Invariants:
- What is always true?
- Class-level properties
- Loop properties
Example identification:
Preconditions:
- account is not None
- amount > 0
- account.balance >= amount (to succeed without exception)
Postconditions:
- If successful: account.balance == old(account.balance) - amount
- If successful: return value == new balance
- If insufficient: raises InsufficientFundsError
- account object still valid
Invariants (Account class):
- balance >= 0 (always)
- account_number is immutable
Step 3: Choose Specification Language
Select appropriate format:
Specification languages:
| Language | Use Case | Example |
|---|
| JML (Java) | Java programs | //@ requires, //@ ensures |
| ACSL (C) | C programs | /*@ requires, /*@ ensures |
| Dafny | Verified code | requires, ensures |
| Eiffel | Design-by-contract | require, ensure |
| Python docstring | Documentation | :param:, :raises:, :returns: |
| TypeScript | Type annotations | Type predicates, assertions |
| Hoare logic | Formal proofs | {P} S {Q} notation |
Example language choice:
- Java method → Use JML
- C function → Use ACSL
- Python function → Use docstring assertions
- Verification task → Use Dafny
Step 4: Write Specifications
Generate formal contracts:
Format depends on language:
JML (Java):
public int withdraw(Account account, int amount) {
}
ACSL (C):
int withdraw(Account *account, int amount) {
}
Dafny:
method Withdraw(account: Account, amount: int) returns (newBalance: int)
requires amount > 0
requires account.balance >= amount
ensures account.balance == old(account.balance) - amount
ensures newBalance == account.balance
modifies account
{
// implementation
}
Python (docstring):
def withdraw(account: Account, amount: float) -> float:
"""
Withdraw amount from account.
:param account: The account to withdraw from
:param amount: The amount to withdraw (must be positive)
:raises InsufficientFundsError: If balance < amount
:raises ValueError: If amount <= 0
:returns: The new account balance
Preconditions:
- account is not None
- amount > 0
- account.balance >= amount (for successful withdrawal)
Postconditions:
- account.balance == old_balance - amount
- return value == account.balance
- If insufficient funds: InsufficientFundsError raised
- account.balance >= 0 (invariant preserved)
"""
Step 5: Verify Specifications
Ensure specifications are correct:
Verification steps:
- Check preconditions are sufficient (not too strong)
- Verify postconditions are achievable (not too weak)
- Confirm invariants always hold
- Test specifications with examples
- Use formal verification tools if available
Example verification:
assert amount > 0
assert account is not None
assert account.balance >= amount
old_balance = account.balance
result = withdraw(account, 100)
assert account.balance == old_balance - 100
assert result == account.balance
assert account.balance >= 0
Specification Patterns
Pattern 1: Simple Function Contract
Code:
def sqrt(x):
"""Compute square root."""
return x ** 0.5
Generated specification:
def sqrt(x: float) -> float:
"""
Compute the square root of x.
:param x: The number to compute square root of
:returns: The square root of x
:raises ValueError: If x < 0
Preconditions:
- x >= 0
Postconditions:
- result >= 0
- abs(result * result - x) < 1e-10 # Precision tolerance
- If x == 0, then result == 0
- If x == 1, then result == 1
"""
if x < 0:
raise ValueError("Cannot compute square root of negative number")
return x ** 0.5
JML version:
public double sqrt(double x) {
if (x < 0) throw new IllegalArgumentException();
return Math.sqrt(x);
}
Pattern 2: Array/Collection Modification
Code:
def sort_list(items):
"""Sort list in-place."""
items.sort()
Generated specification:
def sort_list(items: List[int]) -> None:
"""
Sort list in ascending order (in-place).
:param items: List to sort (modified in-place)
Preconditions:
- items is not None
- All elements in items are comparable
Postconditions:
- len(items) == len(old(items)) # Same length
- set(items) == set(old(items)) # Same elements
- For all i, j: i < j implies items[i] <= items[j] # Sorted
- items is sorted in ascending order
Frame conditions:
- Only items is modified
- No other data structures affected
"""
if items is None:
raise ValueError("items cannot be None")
items.sort()
ACSL version:
void sort_array(int *arr, int len) {
}
Pattern 3: Class Invariants
Code:
class BankAccount:
def __init__(self, account_number, initial_balance):
self.account_number = account_number
self.balance = initial_balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if self.balance < amount:
raise InsufficientFundsError()
self.balance -= amount
Generated specification:
class BankAccount:
"""
Bank account with balance and account number.
Class Invariants:
- balance >= 0 (balance never negative)
- account_number is immutable (never changes after init)
- account_number is unique (within system)
"""
def __init__(self, account_number: str, initial_balance: float) -> None:
"""
Create new bank account.
Preconditions:
- account_number is not None and not empty
- initial_balance >= 0
- account_number not already in use
Postconditions:
- self.account_number == account_number
- self.balance == initial_balance
- Invariants established
"""
if not account_number:
raise ValueError("Account number required")
if initial_balance < 0:
raise ValueError("Initial balance cannot be negative")
self.account_number = account_number
self.balance = initial_balance
def deposit(self, amount: float) -> None:
"""
Deposit amount into account.
Preconditions:
- amount > 0
- Invariants hold
Postconditions:
- self.balance == old(self.balance) + amount
- Invariants still hold
Frame conditions:
- Only self.balance modified
- account_number unchanged
"""
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self.balance += amount
assert self.balance >=
() -> :
amount <= :
ValueError()
.balance < amount:
InsufficientFundsError()
.balance -= amount
.balance >=
JML version:
public class BankAccount {
private double balance;
private final String account_number;
public BankAccount(String account_number, double initial_balance) {
}
public void deposit(double amount) {
}
public void withdraw(double amount) throws InsufficientFundsError {
}
}
Pattern 4: Loop Invariants
Code:
def sum_array(arr):
total = 0
for i in range(len(arr)):
total += arr[i]
return total
Generated specification with loop invariants:
def sum_array(arr: List[int]) -> int:
"""
Compute sum of all elements in array.
Preconditions:
- arr is not None
Postconditions:
- result == sum of all elements in arr
"""
total = 0
for i in range(len(arr)):
total += arr[i]
return total
Dafny version:
method SumArray(arr: array<int>) returns (total: int)
requires arr != null
ensures total == sum(arr[..])
{
total := 0;
var i := 0;
while i < arr.Length
invariant 0 <= i <= arr.Length
invariant total == sum(arr[..i])
{
total := total + arr[i];
i := i + 1;
}
}
Pattern 5: Binary Search Contract
Code:
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
Generated specification:
def binary_search(arr: List[int], target: int) -> int:
"""
Search for target in sorted array using binary search.
Preconditions:
- arr is not None
- arr is sorted in ascending order:
For all i, j: 0 <= i < j < len(arr) implies arr[i] <= arr[j]
Postconditions:
- If result >= 0: arr[result] == target
- If result >= 0: 0 <= result < len(arr)
- If result == -1: target not in arr
- If multiple occurrences: returns any valid index
"""
if arr is None:
raise ValueError("Array cannot be None")
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
ACSL version:
int binary_search(int *arr, int len, int target) {
int left = 0, right = len - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
Pattern 6: Linked List Operations
Code:
class Node:
def __init__(self, data):
self.data = data
self.next = None
def reverse_list(head):
prev = None
current = head
while current:
next_node = current.next
current.next = prev
prev = current
current = next_node
return prev
Generated specification:
class Node:
"""
Linked list node.
Invariants:
- data can be any value
- next is either None or a Node
"""
def __init__(self, data: Any) -> None:
"""
Create new node.
Postconditions:
- self.data == data
- self.next == None
"""
self.data = data
self.next = None
def reverse_list(head: Optional[Node]) -> Optional[Node]:
"""
Reverse a singly linked list in-place.
Preconditions:
- head is None or points to valid linked list
- List is acyclic (no cycles)
Postconditions:
- Returns new head of reversed list
- If head is None, returns None
- Number of nodes unchanged
- All data values preserved
- Order of data values is reversed
- Original list structure modified
- If original: A -> B -> C -> None
Then result: C -> B -> A -> None
Frame conditions:
- Only next pointers modified
- data values unchanged
- No new nodes created
- No nodes deleted
"""
prev = None
current = head
while current is not None:
next_node = current.next
current.next = prev
prev = current
current = next_node
prev
Pattern 7: Concurrency Specifications
Code:
from threading import Lock
class Counter:
def __init__(self):
self.value = 0
self.lock = Lock()
def increment(self):
with self.lock:
self.value += 1
Generated specification:
class Counter:
"""
Thread-safe counter.
Class Invariants:
- value >= 0
- lock is always valid
- value reflects total increments
Concurrency Properties:
- increment is atomic
- Multiple threads can safely call increment
- No race conditions
- Each increment increases value by exactly 1
"""
def __init__(self) -> None:
"""
Create new counter.
Postconditions:
- self.value == 0
- self.lock is initialized
- Thread-safe operations enabled
"""
self.value = 0
self.lock = Lock()
def increment(self) -> None:
"""
Atomically increment counter by 1.
Preconditions:
- Counter is initialized
Postconditions:
- self.value == old(self.value) + 1
- Operation is atomic (thread-safe)
Concurrency guarantees:
- Mutual exclusion: Only one thread in critical section
- Progress: Non-blocking (eventually acquires lock)
- No deadlock: Lock always released
- Visibility: Changes visible to all threads
"""
with self.lock:
old_value = self.value
self.value += 1
Specification Templates
Template 1: Simple Function
def function_name(param1: Type1, param2: Type2) -> ReturnType:
"""
[Brief description]
:param param1: [Description]
:param param2: [Description]
:returns: [Description]
:raises ExceptionType: [When]
Preconditions:
- [Condition on param1]
- [Condition on param2]
- [State requirement]
Postconditions:
- [Property of return value]
- [State change]
- [Side effect]
"""
Template 2: Class with Invariants
class ClassName:
"""
[Description]
Class Invariants:
- [Property 1 always true]
- [Property 2 always true]
"""
def __init__(self, ...):
"""
Preconditions: [...]
Postconditions: [Invariants established]
"""
def method(self, ...):
"""
Preconditions: [Invariants hold, ...]
Postconditions: [Invariants still hold, ...]
"""
Template 3: Loop with Invariant
while condition:
Best Practices
- Be precise - Specifications should be unambiguous
- Be complete - Cover all cases (success and failure)
- Be minimal - Don't over-specify implementation details
- Be verifiable - Specifications should be checkable
- Document assumptions - Make preconditions explicit
- Specify side effects - Document state changes
- Include invariants - Especially for classes and loops
- Handle exceptions - Specify exceptional postconditions
- Consider concurrency - Add thread-safety guarantees if needed
- Keep readable - Specifications are documentation
Common Specification Elements
Quantifiers
- For all (∀):
for all i: 0 <= i < len(arr) implies arr[i] >= 0
- Exists (∃):
exists i: 0 <= i < len(arr) and arr[i] == target
Old values
old(variable) - Value before execution
\old(x) (JML/ACSL) - Previous value of x
Comparison operators
== - Equality
!= - Inequality
<, <=, >, >= - Ordering
==> - Implication
&&, || - Logical and/or
Set operations
set(arr) - Set of elements
len(arr) - Length/size
arr[i:j] - Slice/range
Verification Considerations
Static verification
- Use tools like Dafny, Why3, Frama-C
- Specifications must be formal and complete
- Proofs may require additional lemmas
Runtime verification
- Add assertions to check contracts
- Use design-by-contract libraries
- Enable in development, optional in production
Testing
- Specifications guide test generation
- Each precondition → boundary test
- Each postcondition → assertion in test