Skip to main content
TL;DR: CrossHair symbolic execution has real-world limitations. QWED addresses these with bounded model checking, depth limits, and graceful fallbacks.

The path explosion problem

Symbolic execution explores all possible execution paths. This works great for small code, but real-world code has:

When CrossHair works

When CrossHair fails

QWED’s bounded model checking solution

We implemented depth limits to prevent path explosion:

Fail-closed verification results

verify_code() is fail-closed: absence of proof is never reported as success. is_verified is only True when at least one function was actually checked, every checked function was proven, and no functions were skipped, unverifiable, or produced counterexamples. The result dictionary always includes the following counters:
boolean
True only if symbolic proof actually succeeded for every discovered function. Defaults to False whenever proof was incomplete. The result also exposes is_safe and verified as aliases for the same boolean.
string
One of:
  • verified — all functions were proven symbolically.
  • counterexamples_found — CrossHair produced concrete counterexamples.
  • no_verifiable_functions — code had no functions, or no typed functions could be checked.
  • unverifiable — at least one function was skipped or could not be proven (e.g. missing type annotations).
  • verification_error — verification did not complete cleanly.
  • crosshair_not_available — CrossHair is not installed in the runtime.
  • syntax_error — the submitted code could not be parsed.
string
Human-readable explanation of the status.
integer
Total functions found in the submitted code.
integer
Functions that were actually run through symbolic execution. A skipped function does not count as checked.
integer
Functions that were proven by CrossHair without counterexamples.
integer
Functions skipped because they cannot be analyzed (for example, no type annotations).
integer
Functions that could not be proven, including skipped functions and functions whose verification raised an error.
integer
Number of concrete counterexamples produced by CrossHair.
array
List of issue objects with type (unverifiable, counterexample, or error), function name, and description.

Why untyped code fails closed

CrossHair requires type annotations to perform symbolic execution. Functions without type hints are reported as skipped and unverifiable, and the overall result fails closed:
Mixed code that contains both typed and untyped functions also fails closed — a single skipped function is enough to set is_verified=False and status="unverifiable". Code that contains no functions at all returns status="no_verifiable_functions" rather than a pass-like result. Pre-verification exits (crosshair_not_available, syntax_error) return the same fail-closed shape — is_verified=False and zeroed counters — so callers can rely on the same fields regardless of how verification ended.

Configuration guide

Conservative (fast, less coverage)

Balanced (default)

Thorough (slow, more coverage)

Fallback strategy

When symbolic execution fails, QWED falls back to:

Honest benchmarks

We tested CrossHair on different code types:

Best practices

Do use symbolic execution for

  • ✅ LLM-generated utility functions
  • ✅ Mathematical calculations
  • ✅ Validation logic
  • ✅ Simple transformations
  • ✅ Code with clear contracts

Don’t use symbolic execution for

  • ❌ Entire applications
  • ❌ Code with external dependencies
  • ❌ Deep recursion algorithms
  • ❌ Real-time systems
  • ❌ Code with I/O operations

API reference

Bounded model checking

verify_bounded() applies loop and recursion bounds to the code before verification. If the bounds transform itself fails (e.g., the AST cannot be unparsed after transformation), the method returns a bounds_transform_error status instead of silently falling back to the original code:

Fail-closed result semantics

SymbolicVerifier.verify_code() treats absence of proof as failure. A result is only is_verified: true when CrossHair actually proved every function it checked. Code that contains no functions, contains untyped functions, or mixes typed and untyped functions cannot pass.

Result fields

The result dictionary distinguishes how each function was handled, so you can tell the difference between “verified” and “not actually checked”:

Status values

Typed function — passes

Untyped function — fails closed

CrossHair requires type hints. Untyped functions are now reported as skipped and unverifiable rather than silently passing:

Mixed typed and untyped — fails closed

Even if some functions verify, a single skipped function prevents an overall pass:

Code with no functions — fails closed

Source that contains only top-level statements has nothing to prove and is no longer reported as is_verified: true:
Do not gate downstream behavior on is_verified alone without also inspecting functions_checked. If functions_checked == 0, no symbolic proof was performed regardless of any other field.

Summary


“My guess would be that technique falls apart at depths required in real world coding environments.” — Reddit criticism. We agree. That’s why we have bounds and fallbacks.