Hints for Your Classes

CrossHair can reason about classes that you write, and may likely do the right thing out of the box.

To be sure it does, though, add types for your __init__() arguments. When needed, CrossHair will attempt to construct your class with symbolic arguments. To do this, it looks up types on the parameters to the __init__ method.

If you use Python’s dataclass module, your generated __init__ arguments will be already typed. That means CrossHair will also work out-of-the-box with definitions like this:

import dataclasses
@dataclasses.dataclass
class Person:
  name: str
  age: int

Customizing Creation

There are a variety of times where you may need to customize the way CrossHair constructs instances of your class. For example,

  • You are using some sort of class wrapper that hides the types on the constructor arguments.

  • You don’t have control over the class that you want CrossHair to reason about.

  • The class has valid/reachable states that aren’t directly constructable.

  • The type is implemented in C.

Note

This capability and the interface to it is under active development and unstable. That said, if you are willing to try it out, please ask questions and let us know how it goes.

In such cases, you may register a creation callback with crosshair.register_type(). Your creation callback will be given a crosshair.SymbolicFactory instance that can create other kinds of symbolic objects, which you should use to initialize your instance.

Example

Suppose you had a Counter class that could have any count, but is only construct-able at zero:

class Counter:
  def __init__(self):
    self.count = 0
  # ... more methods ...

By default, CrossHair would assume that the count must always be zero, even though methods or direct assignments could change it. To ensure CrossHair knows that any integer count is possible, we will tell CrossHair to use a custom function when creating Counter instances, like so:

import crosshair
def symbolic_counter(factory: crosshair.SymbolicFactory) -> Counter:
  counter = Counter()
  counter.count = factory(int)  # count is now a symbolic holding any integer value
  return counter
crosshair.register_type(Counter, symbolic_counter)

Note that we might want to further ensure that the count is greater than zero. The best way to do this is to add an invariant to the class. (e.g. “self.count >= 0”) CrossHair will make sure all invariants hold for symbolic instances after your creation function produces them.