# CrossHair: symbolic execution behind a contract annotation in Python

> CrossHair is an analysis tool for Python that repeatedly calls your functions with symbolic inputs and uses an SMT solver to explore viable execution paths and find counterexamples. You add type annotations and a contract, and it tries to prove the contract holds, and it can also generate unit tests and find behavioural differences between two functions.

**pschanely/CrossHair** — An analysis tool for Python that blurs the line between testing and type systems.

- Repository: https://github.com/pschanely/CrossHair
- Stars: 1,341 · Forks: 102
- Language: Python
- License: NOASSERTION
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/pschanely-crosshair

## The mechanism is symbolic inputs, not test generation

CrossHair is an analysis tool for Python that blurs the line between testing and type systems. The description of how it works is short and precise: CrossHair works by repeatedly calling your functions with symbolic inputs. It uses an SMT solver, a kind of theorem prover, to explore viable execution paths and find counterexamples for you.

That is a concolic execution approach, and the difference from ordinary property-based testing is worth spelling out because it is the whole point of the tool.

Property-based testing generates random inputs and checks properties. It finds a counterexample when the generator happens to produce a bad one. CrossHair instead treats the inputs as symbolic. Your function runs with a value that is not a number but a variable the solver is tracking, and the branch conditions inside your function become constraints on that variable rather than decisions the runtime has already made. The solver then explores the paths that are still viable under the constraints, and when a path reaches a violation it can produce the concrete input that causes it.

So the difference in what you get back is large. A random test failure gives you a failing input and a stack trace. A CrossHair failure gives you a failing input and, in principle, a path through the code that explains why it is a counterexample. That is the difference between finding a bug and understanding one.

The project is careful to say this is not a new idea, pointing at a paper describing an earlier Python approach, and to claim only that to the author's knowledge CrossHair is the most complete implementation, since it can use symbolic reasoning for the built-in types, user-defined classes, and much of the standard library.

That last claim is the ambitious part of the project. Symbolic reasoning over the standard library means the tool can follow a value through, say, a list operation and a dict lookup, and reason about what would have to be true for the branch to be taken.

## You write the contract, the tool tries to prove it

The entry point is a function with type annotations plus a contract in a supported syntax. Given that, CrossHair will attempt to find counterexamples.

The type annotations and the contract do different jobs, and the division is what makes the tool usable on ordinary code. The annotations tell the tool what the inputs and outputs are, so it knows the domain to search over and can pick sensible symbolic values. The contract states the property you want, typically a precondition on the arguments and a postcondition on the result, and the tool's job is to find inputs that satisfy the precondition and violate the postcondition.

The documentation has a page for the kinds of contracts that are supported and a separate page with hints for your classes, and the existence of the hints page is a good sign about the state of the project. Symbolic execution over user-defined classes is where most such tools stop working, because a class method's behaviour has to be modelled rather than merely typed. A tool that ships hints for your classes is a tool that expects you to help it model your own code.

Two other capabilities are worth noting, and both are unusual for a verification tool.

The first is unit test generation. CrossHair can generate unit tests, and the documentation has a page for it under cover. A tool that can find a counterexample can usually turn that exploration into a regression test, and generating tests as a by-product of analysis means the tests come from the paths that are actually interesting rather than from a generator's imagination.

The second is behavioural differencing. CrossHair can find behavioural differences between functions, with a page dedicated to diff_behavior. Given two implementations, it can look for an input where they disagree. That is a very direct answer to a question teams ask constantly, which implementation is right, and it is a question that a differential test written by hand answers only for the cases its author imagined.

## Hypothesis can drive it, and there is a browser version

The most significant piece of news in the project summary is an integration with the property-based testing world.

Python's most popular property-based testing tool, Hypothesis, now supports running CrossHair as an optional backend. The project links the Hypothesis documentation section on alternative backends.

That integration changes who uses the tool. Without it, CrossHair is a separate command with its own contracts, its own configuration, and its own place in a workflow. With it, CrossHair becomes a backend that the Hypothesis strategy machinery can drive, so the generators you already wrote for Hypothesis can produce the inputs that the symbolic engine reasons about. For a project that has already invested in property-based testing, that is the difference between trying the tool and not trying it.

There is also a browser version, at crosshair-web.org, described as a way to try CrossHair right now, in your browser. For a tool whose setup involves a solver, contracts, and a particular function under analysis, having a hosted instance removes the setup barrier entirely for evaluation.

The IDE integrations are for VS Code, PyCharm, and more, with the marketplace entries linked for both VS Code and PyCharm. That matters more than it might sound, because the workflow of a verification tool is iterative, you add a contract, you run it, it tells you a counterexample, you fix the code or the contract, you run it again, and that loop only works if the tool is one keystroke away from the editor you are already in.

The plugin mechanism is documented separately, and the documentation index also covers a limitations page, a case studies page, and a how does it work page, which is a well-organised set of docs for a project that has been developing for a while.

## The dependency list explains what it is built on

The packaging metadata is where the practical requirements of the tool show up.

The package is `crosshair-tool` at version 0.0.111, described as analyzing Python code for correctness using symbolic execution, and the version number is still in the zero series, which is consistent with the Development Status of 3 - Alpha. The version history is dense, with v0.0.109 in July 2026, v0.0.110 in August 2026, and v0.0.111 in September 2026, and the last push was on 2026-10-13.

The runtime dependencies are six. `z3-solver` at 4.13.0.0 or newer is the SMT solver, and it is the heaviest of them by a wide margin, since it ships a solver binary rather than being pure Python. `typing-inspect` and `typing_extensions` are for reading and extending type information at runtime, which is how the tool knows what to feed a function. `typeshed-client` is the client for the typeshed stubs, which is where it gets the signature and type information for the standard library. `pygls` is the language server library, which is what the IDE integrations are built on. `packaging` handles versions, and `importlib_metadata` is pulled in only on Python older than 3.10.

The Python floor is 3.9, with classifiers for 3.9 through 3.15, and the operating systems are Windows, Linux, and macOS.

There is a comment in the dev extra that is worth reading, because it is the kind of note that saves an hour. The runtime package still supports Python 3.9, but the dev tooling below, black, isort, mypy, pytest, requires 3.10 or newer, so contributing to CrossHair requires Python 3.10 or newer. The runtime floor and the contributor floor are different, and a contributor on 3.9 will hit an error in a place that does not obviously say what is wrong.

The dev tooling itself is a good list for what the project considers its standards: black, isort, mypy, pytest, flake8 with the Flake8-pyproject plugin that lets flake8 read its configuration from pyproject.toml, and deal, which is a linter for Python code that flags common mistakes. The build backend is plain setuptools with wheel, and the repository root carries a `.devcontainer/`, a `.pre-commit-config.yaml`, and a `.readthedocs.yml`.

## Where CrossHair is the wrong tool

The limits here are real and they are the standard limits of symbolic execution, though the project is more upfront about them than most.

First, it needs annotations and contracts, so it does nothing for code that has neither. A Python codebase with no type hints gives the tool no domain to search over, and a codebase with type hints but no stated invariants gives it nothing to disprove. That is a large amount of upfront work for a team that has to write contracts for the functions they want checked, and a contract written to satisfy the tool rather than to state a real invariant is worse than no contract, because it produces false confidence.

Second, the solver is the bottleneck. Symbolic execution explores a path space that grows with the branching in the function, and the practical limit is the solver's timeout on functions with deep conditional logic or large data structures. The project is candid that it is an analysis tool rather than a proof assistant, and the practical experience is that it finds counterexamples quickly and does not complete proofs for complicated functions.

Third, the interaction with the outside world is the classic problem. A function that opens a file, reads the clock, makes a network call, or touches the environment cannot be reasoned about symbolically in a meaningful way, and the tool either refuses it or models it approximately. That rules out a large fraction of real application code, and it is why the most productive use of such a tool is on pure functions in the middle of a data pipeline.

Fourth, the state of the project. Development Status 3 - Alpha, a version number at 0.0.111, and a release every few weeks, all of which says the tool is moving fast and the interfaces may change. That is fine for an individual trying it and uncomfortable as a hard dependency in a CI pipeline that others rely on.

Fifth, and this is about how to use it rather than about the tool, contract checking finds counterexamples to what you wrote, not bugs in what you did not write. A function with no contract is unexamined, and a contract that captures the wrong invariant is examined to no purpose. The value of the tool scales with the quality of the contracts, which is a human process the tool cannot substitute for.

## Conclusion

CrossHair fits a Python team that wants its type annotations checked for more than they usually are, a library author who wants a counterexample to a failing input rather than a stack trace, and a codebase that already documents its invariants in type hints. It is the wrong choice for a project with no annotations, for a team that will not add contracts, and for code that calls out to the operating system or the network. Check three things before you adopt it: that your target functions are annotated and their invariants expressible as preconditions and postconditions, that the solver's timeout is set for your function sizes, and that the project is still at the alpha stage, since the package metadata records Development Status 3 - Alpha.

## FAQ

### How does CrossHair find bugs in Python code?

It works by repeatedly calling your functions with symbolic inputs, using an SMT solver to explore viable execution paths and find counterexamples. You add type annotations and a contract in a supported syntax, and CrossHair attempts to find inputs that satisfy the contract's preconditions and violate its postconditions.

### Can I run CrossHair with Hypothesis?

Yes. Hypothesis, described as Python's most popular property-based testing tool, now supports running CrossHair as an optional backend, through its strategies documentation on alternative backends. The project also offers a browser version at crosshair-web.org and IDE integrations for VS Code and PyCharm.

### What does CrossHair need to be installed?

The package is `crosshair-tool` and needs Python 3.9 or newer. Its runtime dependencies include z3-solver at 4.13.0.0 or newer for the SMT solving, typing-inspect and typing_extensions for type information at runtime, typeshed-client for standard library stubs, and pygls for the language server support the IDE integrations use.

### What can CrossHair do besides checking contracts?

It can generate unit tests, and it can find behavioral differences between functions, which means it can look for an input on which two implementations disagree. The project documents these separately under the cover and diff_behavior pages.

## Sources

- [Issues](https://github.com/pschanely/CrossHair/issues)
- [pschanely/CrossHair on GitHub](https://github.com/pschanely/CrossHair)
- [README](https://github.com/pschanely/CrossHair/blob/main/README.md)
- [Releases](https://github.com/pschanely/CrossHair/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pschanely-crosshair
