0
# Behavioral Analysis and Comparison
1
2
CrossHair provides sophisticated tools for analyzing and comparing the behavior of different function implementations. These capabilities enable developers to find behavioral differences, validate refactoring efforts, and generate comprehensive behavior descriptions.
3
4
## Capabilities
5
6
### Function Behavior Comparison
7
8
Compare the behavior of two function implementations to identify differences in outputs, exceptions, or side effects.
9
10
```python { .api }
11
def diff_behavior(fn1, fn2, *, sig=None, options=None):
12
"""
13
Compare behaviors of two functions.
14
15
Parameters:
16
- fn1: First function to compare
17
- fn2: Second function to compare
18
- sig: Optional signature specification for comparison
19
- options: Optional AnalysisOptions for controlling comparison
20
21
Returns:
22
Generator yielding BehaviorDiff instances describing differences
23
24
Uses symbolic execution to explore input spaces and find cases
25
where the two functions exhibit different behavior. Compares
26
return values, exception types, and execution characteristics.
27
"""
28
29
def diff_behavior_with_signature(fn1, fn2, sig, options):
30
"""
31
Compare behaviors with specific signature constraints.
32
33
Parameters:
34
- fn1: First function to compare
35
- fn2: Second function to compare
36
- sig: Signature object defining input constraints
37
- options: AnalysisOptions configuration
38
39
Returns:
40
Generator yielding detailed behavioral differences
41
42
Performs targeted comparison using a specific function signature,
43
enabling more focused analysis of particular input patterns.
44
"""
45
```
46
47
**Usage Examples:**
48
49
```python
50
from crosshair.diff_behavior import diff_behavior
51
52
def old_sort(items, reverse=False):
53
"""Original implementation"""
54
result = list(items)
55
result.sort(reverse=reverse)
56
return result
57
58
def new_sort(items, reverse=False):
59
"""New implementation with bug"""
60
result = list(items)
61
result.sort() # Bug: ignores reverse parameter
62
return result
63
64
# Find behavioral differences
65
for diff in diff_behavior(old_sort, new_sort):
66
print(f"Difference found:")
67
print(f" Input: {diff.args}")
68
print(f" old_sort: {diff.result1}")
69
print(f" new_sort: {diff.result2}")
70
print(f" Reason: {diff.message}")
71
```
72
73
### Behavior Description and Analysis
74
75
Generate comprehensive descriptions of function behavior for documentation and verification.
76
77
```python { .api }
78
def describe_behavior(fn, *, sig=None, options=None):
79
"""
80
Describe function behavior comprehensively.
81
82
Parameters:
83
- fn: Function to analyze
84
- sig: Optional signature specification
85
- options: Optional AnalysisOptions configuration
86
87
Returns:
88
Detailed behavior description including:
89
- Input/output relationships
90
- Exception conditions
91
- Edge case handling
92
- Performance characteristics
93
94
Performs exhaustive symbolic execution to characterize
95
the complete behavior space of a function.
96
"""
97
```
98
99
**Usage Example:**
100
101
```python
102
from crosshair.diff_behavior import describe_behavior
103
104
def calculate_discount(price, discount_percent, customer_type="standard"):
105
"""
106
Calculate discount for a purchase.
107
108
pre: price >= 0
109
pre: 0 <= discount_percent <= 100
110
post: 0 <= __return__ <= price
111
"""
112
if customer_type == "premium":
113
discount_percent *= 1.2
114
115
discount_amount = price * (discount_percent / 100)
116
return min(discount_amount, price)
117
118
# Generate behavior description
119
behavior = describe_behavior(calculate_discount)
120
print(behavior.summary)
121
print("Edge cases found:", behavior.edge_cases)
122
print("Exception conditions:", behavior.exception_conditions)
123
```
124
125
### Behavioral Difference Analysis
126
127
Detailed analysis of differences found between function implementations.
128
129
```python { .api }
130
class BehaviorDiff:
131
"""
132
Represents a difference between function behaviors.
133
134
Contains detailed information about inputs that cause different
135
behaviors, including return values, exceptions, and execution
136
characteristics.
137
"""
138
139
def __init__(self, args, result1, result2, message, exception_diff=None):
140
"""
141
Initialize behavior difference.
142
143
Parameters:
144
- args: Input arguments that cause the difference
145
- result1: Result from first function
146
- result2: Result from second function
147
- message: Human-readable description of difference
148
- exception_diff: Optional exception difference details
149
"""
150
151
@property
152
def args(self):
153
"""Input arguments that trigger the behavioral difference."""
154
155
@property
156
def result1(self):
157
"""Result from the first function."""
158
159
@property
160
def result2(self):
161
"""Result from the second function."""
162
163
@property
164
def message(self):
165
"""Human-readable description of the difference."""
166
167
@property
168
def exception_diff(self):
169
"""Details about exception-related differences."""
170
171
class Result:
172
"""
173
Result of behavior analysis containing outcomes and metadata.
174
175
Encapsulates the results of function execution during behavioral
176
analysis, including return values, exceptions, and execution context.
177
"""
178
179
def __init__(self, return_value=None, exception=None, execution_log=None):
180
"""
181
Initialize analysis result.
182
183
Parameters:
184
- return_value: Value returned by function (if any)
185
- exception: Exception raised by function (if any)
186
- execution_log: Optional log of execution steps
187
"""
188
```
189
190
**Advanced Comparison Example:**
191
192
```python
193
from crosshair.diff_behavior import diff_behavior, BehaviorDiff
194
195
def safe_divide(a, b):
196
"""Safe division with error handling"""
197
if b == 0:
198
return float('inf') if a > 0 else float('-inf') if a < 0 else float('nan')
199
return a / b
200
201
def unsafe_divide(a, b):
202
"""Unsafe division that raises exceptions"""
203
return a / b
204
205
# Compare with exception analysis
206
for diff in diff_behavior(safe_divide, unsafe_divide):
207
if diff.exception_diff:
208
print(f"Exception handling difference:")
209
print(f" Input: a={diff.args[0]}, b={diff.args[1]}")
210
print(f" safe_divide: returns {diff.result1}")
211
print(f" unsafe_divide: raises {diff.result2}")
212
```
213
214
### Exception Equivalence Configuration
215
216
Configure how exceptions are compared during behavioral analysis.
217
218
```python { .api }
219
class ExceptionEquivalenceType(enum.Enum):
220
"""
221
Types of exception equivalence for behavioral comparison.
222
223
Controls how strictly exceptions are compared when analyzing
224
behavioral differences between functions.
225
"""
226
227
SAME_TYPE = "same_type"
228
"""Consider exceptions equivalent if they have the same type."""
229
230
SAME_TYPE_AND_MESSAGE = "same_type_and_message"
231
"""Consider exceptions equivalent if type and message match."""
232
233
EXACT = "exact"
234
"""Consider exceptions equivalent only if completely identical."""
235
```
236
237
**Usage with Different Equivalence Types:**
238
239
```python
240
from crosshair.diff_behavior import diff_behavior, ExceptionEquivalenceType
241
from crosshair.options import AnalysisOptions
242
243
def func1(x):
244
if x < 0:
245
raise ValueError("Negative input")
246
return x * 2
247
248
def func2(x):
249
if x < 0:
250
raise ValueError("Input cannot be negative") # Different message
251
return x * 2
252
253
# Compare with different exception equivalence settings
254
options = AnalysisOptions()
255
256
# Strict comparison (different messages = different behavior)
257
options.exception_equivalence = ExceptionEquivalenceType.EXACT
258
strict_diffs = list(diff_behavior(func1, func2, options=options))
259
260
# Lenient comparison (same type = equivalent)
261
options.exception_equivalence = ExceptionEquivalenceType.SAME_TYPE
262
lenient_diffs = list(diff_behavior(func1, func2, options=options))
263
264
print(f"Strict comparison found {len(strict_diffs)} differences")
265
print(f"Lenient comparison found {len(lenient_diffs)} differences")
266
```
267
268
### Performance and Behavior Profiling
269
270
Analyze performance characteristics alongside behavioral properties.
271
272
```python { .api }
273
class ExecutionProfile:
274
"""
275
Profile of function execution including performance metrics.
276
277
Contains timing information, resource usage, and execution
278
path statistics gathered during symbolic execution.
279
"""
280
281
def __init__(self, execution_time=None, path_count=None, solver_calls=None):
282
"""
283
Initialize execution profile.
284
285
Parameters:
286
- execution_time: Time spent in symbolic execution
287
- path_count: Number of execution paths explored
288
- solver_calls: Number of SMT solver queries made
289
"""
290
```
291
292
**Performance-Aware Comparison:**
293
294
```python
295
def optimized_fibonacci(n, memo={}):
296
"""Optimized fibonacci with memoization"""
297
if n in memo:
298
return memo[n]
299
if n <= 1:
300
return n
301
memo[n] = optimized_fibonacci(n-1, memo) + optimized_fibonacci(n-2, memo)
302
return memo[n]
303
304
def naive_fibonacci(n):
305
"""Naive recursive fibonacci"""
306
if n <= 1:
307
return n
308
return naive_fibonacci(n-1) + naive_fibonacci(n-2)
309
310
# Compare behavior with performance implications
311
from crosshair.options import AnalysisOptions
312
313
options = AnalysisOptions(per_path_timeout=1.0) # Short timeout
314
315
# The analysis might find that both produce same results
316
# but optimized version completes within timeout while naive doesn't
317
for diff in diff_behavior(optimized_fibonacci, naive_fibonacci, options=options):
318
print(f"Performance difference: {diff.message}")
319
```
320
321
### Integration with Testing Frameworks
322
323
Generate behavioral tests based on discovered differences and edge cases.
324
325
**Generating Regression Tests:**
326
327
```python
328
from crosshair.diff_behavior import diff_behavior
329
330
def generate_regression_tests(old_func, new_func, test_name_prefix="test"):
331
"""Generate regression tests from behavioral differences"""
332
test_cases = []
333
334
for i, diff in enumerate(diff_behavior(old_func, new_func)):
335
test_code = f"""
336
def {test_name_prefix}_difference_{i}():
337
'''Test case for behavioral difference {i}'''
338
args = {diff.args}
339
340
# Expected behavior from old function
341
expected = {repr(diff.result1)}
342
343
# Actual behavior from new function
344
actual = new_func(*args)
345
346
assert actual == expected, f"Behavioral difference: {{actual}} != {{expected}}"
347
"""
348
test_cases.append(test_code)
349
350
return test_cases
351
352
# Generate tests for refactored function
353
test_cases = generate_regression_tests(original_implementation, refactored_implementation)
354
for test in test_cases:
355
print(test)
356
```
357
358
### Behavioral Invariant Discovery
359
360
Discover and verify behavioral invariants across function implementations.
361
362
```python
363
def discover_invariants(functions, input_generator):
364
"""
365
Discover behavioral invariants across multiple function implementations.
366
367
Parameters:
368
- functions: List of function implementations to compare
369
- input_generator: Generator providing test inputs
370
371
Returns:
372
List of discovered invariants that hold across all implementations
373
"""
374
invariants = []
375
376
for inputs in input_generator:
377
results = [f(*inputs) for f in functions]
378
379
# Check if all functions produce same result
380
if all(r == results[0] for r in results):
381
invariant = f"For input {inputs}, all functions return {results[0]}"
382
invariants.append(invariant)
383
384
return invariants
385
```
386
387
**Example Invariant Discovery:**
388
389
```python
390
def math_sqrt_wrapper(x):
391
"""Wrapper for math.sqrt"""
392
import math
393
return math.sqrt(x)
394
395
def newton_sqrt(x, precision=1e-10):
396
"""Newton's method square root"""
397
if x < 0:
398
raise ValueError("Cannot compute square root of negative number")
399
if x == 0:
400
return 0
401
402
guess = x / 2
403
while abs(guess * guess - x) > precision:
404
guess = (guess + x / guess) / 2
405
return guess
406
407
# Discover invariants between implementations
408
def input_gen():
409
for x in [0, 1, 4, 9, 16, 25, 100]:
410
yield (x,)
411
412
invariants = discover_invariants(
413
[math_sqrt_wrapper, newton_sqrt],
414
input_gen()
415
)
416
417
for invariant in invariants:
418
print(invariant)
419
```