ballpython 2.0.0__py3-none-any.whl → 2.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pycleaner/verifier.py ADDED
@@ -0,0 +1,495 @@
1
+ """
2
+ pycleaner.verifier
3
+ ==================
4
+
5
+ Proof-Carrying Differential Equivalence Verification Engine.
6
+
7
+ Validates source code transformations (Tier A: Proven, Tier B: Suggested, Tier C: Refused)
8
+ by dynamically fuzzing baseline and transformed callables in isolated worker processes
9
+ under deterministic input vectors.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import ast
15
+ import dataclasses
16
+ import enum
17
+ import inspect
18
+ import multiprocessing as mp
19
+ import random
20
+ import time
21
+ from typing import Any
22
+
23
+
24
+ class VerificationTier(enum.Enum):
25
+ TIER_A_PROVEN = "TIER_A_PROVEN"
26
+ TIER_B_SUGGESTED = "TIER_B_SUGGESTED"
27
+ TIER_C_REFUSED = "TIER_C_REFUSED"
28
+
29
+
30
+ @dataclasses.dataclass(frozen=True)
31
+ class CounterExample:
32
+ callable_name: str
33
+ arguments: tuple[Any, ...]
34
+ keyword_arguments: dict[str, Any]
35
+ original_result: str | None
36
+ original_error: str | None
37
+ transformed_result: str | None
38
+ transformed_error: str | None
39
+ seed: int
40
+
41
+
42
+ @dataclasses.dataclass(frozen=True)
43
+ class ProofReceipt:
44
+ filepath: str
45
+ callable_name: str
46
+ tier: VerificationTier
47
+ iterations_run: int
48
+ seed: int
49
+ duration_ms: float
50
+ counterexample: CounterExample | None = None
51
+ reason: str = ""
52
+
53
+
54
+ @dataclasses.dataclass(frozen=True)
55
+ class ExecutionResult:
56
+ is_success: bool
57
+ return_repr: str | None = None
58
+ error_type: str | None = None
59
+ error_message: str | None = None
60
+
61
+
62
+ def _worker_differential_fuzz(
63
+ orig_source: str,
64
+ trans_source: str,
65
+ callable_name: str,
66
+ inputs: list[tuple[tuple[Any, ...], dict[str, Any]]],
67
+ conn: Any,
68
+ ) -> None:
69
+ """Child process worker: executes differential equivalence loop across all synthesized inputs."""
70
+ try:
71
+ orig_ns: dict[str, Any] = {
72
+ "__name__": "__verifier__",
73
+ "__builtins__": __builtins__,
74
+ }
75
+ trans_ns: dict[str, Any] = {
76
+ "__name__": "__verifier__",
77
+ "__builtins__": __builtins__,
78
+ }
79
+
80
+ try:
81
+ orig_code = compile(
82
+ orig_source, "<orig_sandbox>", "exec"
83
+ ) # nosec: B102 - intentionally isolated in worker process
84
+ exec(
85
+ orig_code, orig_ns
86
+ ) # nosec: B102 - intentionally isolated in worker process
87
+ except BaseException as e:
88
+ conn.send(
89
+ {
90
+ "status": "unresolvable",
91
+ "reason": f"Original module execution failed in sandbox: {type(e).__name__}: {e}",
92
+ }
93
+ )
94
+ return
95
+
96
+ try:
97
+ trans_code = compile(
98
+ trans_source, "<trans_sandbox>", "exec"
99
+ ) # nosec: B102 - intentionally isolated in worker process
100
+ exec(
101
+ trans_code, trans_ns
102
+ ) # nosec: B102 - intentionally isolated in worker process
103
+ except BaseException as e:
104
+ conn.send(
105
+ {
106
+ "status": "unresolvable",
107
+ "reason": f"Transformed module execution failed in sandbox: {type(e).__name__}: {e}",
108
+ }
109
+ )
110
+ return
111
+
112
+ orig_fn = orig_ns.get(callable_name)
113
+ trans_fn = trans_ns.get(callable_name)
114
+
115
+ if not callable(orig_fn) or not callable(trans_fn):
116
+ conn.send(
117
+ {
118
+ "status": "unresolvable",
119
+ "reason": f"Callable '{callable_name}' not found or not callable in sandbox.",
120
+ }
121
+ )
122
+ return
123
+
124
+ for index, (args, kwargs) in enumerate(inputs):
125
+ try:
126
+ orig_res = orig_fn(*args, **kwargs)
127
+ orig_success = True
128
+ orig_repr = repr(orig_res)
129
+ orig_err_type = None
130
+ orig_err_msg = None
131
+ except BaseException as e:
132
+ orig_success = False
133
+ orig_repr = None
134
+ orig_err_type = type(e).__name__
135
+ orig_err_msg = str(e)
136
+
137
+ try:
138
+ trans_res = trans_fn(*args, **kwargs)
139
+ trans_success = True
140
+ trans_repr = repr(trans_res)
141
+ trans_err_type = None
142
+ trans_err_msg = None
143
+ except BaseException as e:
144
+ trans_success = False
145
+ trans_repr = None
146
+ trans_err_type = type(e).__name__
147
+ trans_err_msg = str(e)
148
+
149
+ diverged = False
150
+ if orig_success != trans_success:
151
+ diverged = True
152
+ elif orig_success and trans_success:
153
+ if orig_repr != trans_repr:
154
+ diverged = True
155
+ else:
156
+ if orig_err_type != trans_err_type:
157
+ diverged = True
158
+
159
+ if diverged:
160
+ conn.send(
161
+ {
162
+ "status": "diverged",
163
+ "iteration": index + 1,
164
+ "args": args,
165
+ "kwargs": kwargs,
166
+ "orig_result": orig_repr,
167
+ "orig_error": orig_err_msg or orig_err_type,
168
+ "trans_result": trans_repr,
169
+ "trans_error": trans_err_msg or trans_err_type,
170
+ }
171
+ )
172
+ return
173
+
174
+ conn.send(
175
+ {
176
+ "status": "proven",
177
+ "iterations_run": len(inputs),
178
+ }
179
+ )
180
+ except BaseException as e:
181
+ try:
182
+ conn.send(
183
+ {
184
+ "status": "unresolvable",
185
+ "reason": f"Worker crashed: {type(e).__name__}: {e}",
186
+ }
187
+ )
188
+ except Exception:
189
+ pass # Suppress secondary exceptions if IPC pipe fails to send crash reason
190
+ finally:
191
+ try:
192
+ conn.close()
193
+ except Exception:
194
+ pass # Best-effort resource cleanup for IPC connection
195
+
196
+
197
+ class DeterministicInputSynthesizer:
198
+ """Synthesizes deterministic boundary values and typed inputs from inspect.Signature."""
199
+
200
+ def __init__(self, seed: int) -> None:
201
+ self.seed = seed
202
+ self._rng = random.Random(seed)
203
+
204
+ def generate_for_param(self, param: inspect.Parameter) -> Any:
205
+ annotation = param.annotation
206
+
207
+ if annotation is int or annotation == "int":
208
+ return self._rng.choice([0, 1, -1, 42, -999, 2**31 - 1, -(2**31)])
209
+ if annotation is float or annotation == "float":
210
+ return self._rng.choice(
211
+ [0.0, 1.0, -1.0, 3.14159, float("inf"), float("-inf")]
212
+ )
213
+ if annotation is str or annotation == "str":
214
+ return self._rng.choice(
215
+ ["", "test", " ", "a" * 100, "\n\t", "null", "123", "utf8_🐍"]
216
+ )
217
+ if annotation is bool or annotation == "bool":
218
+ return self._rng.choice([True, False])
219
+ if (
220
+ annotation is list
221
+ or annotation == "list"
222
+ or getattr(annotation, "__origin__", None) is list
223
+ ):
224
+ return self._rng.choice([[], [0], [1, 2, 3], ["a", "b"]])
225
+ if (
226
+ annotation is dict
227
+ or annotation == "dict"
228
+ or getattr(annotation, "__origin__", None) is dict
229
+ ):
230
+ return self._rng.choice([{}, {"key": "value"}, {"0": 0}])
231
+ if (
232
+ annotation is tuple
233
+ or annotation == "tuple"
234
+ or getattr(annotation, "__origin__", None) is tuple
235
+ ):
236
+ return self._rng.choice([(), (1,), (1, "a")])
237
+ if annotation is None or annotation is type(None):
238
+ return None
239
+
240
+ # Fallback for untyped or complex parameters: cycle through primitive boundaries
241
+ return self._rng.choice([0, "", None, [], {}, False, 1.0])
242
+
243
+ def generate_arguments(
244
+ self, sig: inspect.Signature
245
+ ) -> tuple[tuple[Any, ...], dict[str, Any]]:
246
+ pos_args: list[Any] = []
247
+ kw_args: dict[str, Any] = {}
248
+
249
+ for param in sig.parameters.values():
250
+ if param.kind in (
251
+ inspect.Parameter.POSITIONAL_ONLY,
252
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
253
+ ):
254
+ pos_args.append(self.generate_for_param(param))
255
+ elif param.kind == inspect.Parameter.KEYWORD_ONLY:
256
+ kw_args[param.name] = self.generate_for_param(param)
257
+ elif param.kind == inspect.Parameter.VAR_POSITIONAL:
258
+ pos_args.extend([self.generate_for_param(param) for _ in range(2)])
259
+ elif param.kind == inspect.Parameter.VAR_KEYWORD:
260
+ kw_args["extra_kw"] = self.generate_for_param(param)
261
+
262
+ return tuple(pos_args), kw_args
263
+
264
+
265
+ class IsolatedDifferentialVerifier:
266
+ """
267
+ Executes differential verification of source transformations in isolated processes.
268
+ Enforces time bounds, memory isolation, and determinism.
269
+ """
270
+
271
+ def __init__(
272
+ self,
273
+ iterations: int = 100,
274
+ timeout_seconds: float = 2.0,
275
+ base_seed: int | None = None,
276
+ ) -> None:
277
+ self.iterations = iterations
278
+ self.timeout_seconds = timeout_seconds
279
+ self.base_seed = base_seed if base_seed is not None else 1337
280
+
281
+ def _extract_signature_from_source(
282
+ self, source: str, callable_name: str
283
+ ) -> inspect.Signature | None:
284
+ try:
285
+ tree = ast.parse(source)
286
+ for node in ast.walk(tree):
287
+ if (
288
+ isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
289
+ and node.name == callable_name
290
+ ):
291
+ params: list[inspect.Parameter] = []
292
+ for arg in node.args.posonlyargs:
293
+ ann = (
294
+ ast.unparse(arg.annotation)
295
+ if arg.annotation
296
+ else inspect.Parameter.empty
297
+ )
298
+ params.append(
299
+ inspect.Parameter(
300
+ arg.arg,
301
+ inspect.Parameter.POSITIONAL_ONLY,
302
+ annotation=ann,
303
+ )
304
+ )
305
+ for arg in node.args.args:
306
+ ann = (
307
+ ast.unparse(arg.annotation)
308
+ if arg.annotation
309
+ else inspect.Parameter.empty
310
+ )
311
+ params.append(
312
+ inspect.Parameter(
313
+ arg.arg,
314
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
315
+ annotation=ann,
316
+ )
317
+ )
318
+ if node.args.vararg:
319
+ ann = (
320
+ ast.unparse(node.args.vararg.annotation)
321
+ if node.args.vararg.annotation
322
+ else inspect.Parameter.empty
323
+ )
324
+ params.append(
325
+ inspect.Parameter(
326
+ node.args.vararg.arg,
327
+ inspect.Parameter.VAR_POSITIONAL,
328
+ annotation=ann,
329
+ )
330
+ )
331
+ for arg in node.args.kwonlyargs:
332
+ ann = (
333
+ ast.unparse(arg.annotation)
334
+ if arg.annotation
335
+ else inspect.Parameter.empty
336
+ )
337
+ params.append(
338
+ inspect.Parameter(
339
+ arg.arg, inspect.Parameter.KEYWORD_ONLY, annotation=ann
340
+ )
341
+ )
342
+ if node.args.kwarg:
343
+ ann = (
344
+ ast.unparse(node.args.kwarg.annotation)
345
+ if node.args.kwarg.annotation
346
+ else inspect.Parameter.empty
347
+ )
348
+ params.append(
349
+ inspect.Parameter(
350
+ node.args.kwarg.arg,
351
+ inspect.Parameter.VAR_KEYWORD,
352
+ annotation=ann,
353
+ )
354
+ )
355
+ return inspect.Signature(parameters=params)
356
+ return None
357
+ except Exception:
358
+ return None
359
+
360
+ def verify_transformation(
361
+ self,
362
+ filepath: str,
363
+ original_source: str,
364
+ transformed_source: str,
365
+ target_callable: str,
366
+ ) -> ProofReceipt:
367
+ start_time = time.perf_counter()
368
+
369
+ # Step 1: Pre-flight AST Syntax and Signature Extraction
370
+ try:
371
+ ast.parse(original_source)
372
+ ast.parse(transformed_source)
373
+ except SyntaxError as err:
374
+ return ProofReceipt(
375
+ filepath=filepath,
376
+ callable_name=target_callable,
377
+ tier=VerificationTier.TIER_C_REFUSED,
378
+ iterations_run=0,
379
+ seed=self.base_seed,
380
+ duration_ms=(time.perf_counter() - start_time) * 1000.0,
381
+ reason=f"Transformed source failed AST syntax parsing: {err}",
382
+ )
383
+
384
+ sig = self._extract_signature_from_source(original_source, target_callable)
385
+ if sig is None:
386
+ return ProofReceipt(
387
+ filepath=filepath,
388
+ callable_name=target_callable,
389
+ tier=VerificationTier.TIER_B_SUGGESTED,
390
+ iterations_run=0,
391
+ seed=self.base_seed,
392
+ duration_ms=(time.perf_counter() - start_time) * 1000.0,
393
+ reason="Callable could not be isolated for signature-guided dynamic execution.",
394
+ )
395
+
396
+ synthesizer = DeterministicInputSynthesizer(seed=self.base_seed)
397
+ inputs = [synthesizer.generate_arguments(sig) for _ in range(self.iterations)]
398
+
399
+ # Step 2: Isolated Process Execution via Pipe
400
+ ctx = mp.get_context("spawn")
401
+ parent_conn, child_conn = ctx.Pipe(duplex=False)
402
+ process = ctx.Process(
403
+ target=_worker_differential_fuzz,
404
+ args=(
405
+ original_source,
406
+ transformed_source,
407
+ target_callable,
408
+ inputs,
409
+ child_conn,
410
+ ),
411
+ )
412
+ process.daemon = True
413
+ process.start()
414
+ child_conn.close()
415
+
416
+ has_data = parent_conn.poll(timeout=self.timeout_seconds)
417
+ if not has_data:
418
+ process.terminate()
419
+ process.join(timeout=0.2)
420
+ if process.is_alive() and hasattr(process, "kill"):
421
+ process.kill()
422
+ elapsed = (time.perf_counter() - start_time) * 1000.0
423
+ return ProofReceipt(
424
+ filepath=filepath,
425
+ callable_name=target_callable,
426
+ tier=VerificationTier.TIER_B_SUGGESTED,
427
+ iterations_run=0,
428
+ seed=self.base_seed,
429
+ duration_ms=elapsed,
430
+ reason=f"Execution exceeded timeout limit of {self.timeout_seconds}s",
431
+ )
432
+
433
+ try:
434
+ msg = parent_conn.recv()
435
+ except EOFError:
436
+ process.join(timeout=0.2)
437
+ elapsed = (time.perf_counter() - start_time) * 1000.0
438
+ return ProofReceipt(
439
+ filepath=filepath,
440
+ callable_name=target_callable,
441
+ tier=VerificationTier.TIER_B_SUGGESTED,
442
+ iterations_run=0,
443
+ seed=self.base_seed,
444
+ duration_ms=elapsed,
445
+ reason="Worker process terminated or closed pipe unexpectedly.",
446
+ )
447
+ finally:
448
+ parent_conn.close()
449
+ process.join(timeout=0.5)
450
+
451
+ elapsed = (time.perf_counter() - start_time) * 1000.0
452
+
453
+ if msg.get("status") == "diverged":
454
+ counterexample = CounterExample(
455
+ callable_name=target_callable,
456
+ arguments=msg["args"],
457
+ keyword_arguments=msg["kwargs"],
458
+ original_result=msg.get("orig_result"),
459
+ original_error=msg.get("orig_error"),
460
+ transformed_result=msg.get("trans_result"),
461
+ transformed_error=msg.get("trans_error"),
462
+ seed=self.base_seed + msg["iteration"] - 1,
463
+ )
464
+ return ProofReceipt(
465
+ filepath=filepath,
466
+ callable_name=target_callable,
467
+ tier=VerificationTier.TIER_C_REFUSED,
468
+ iterations_run=msg["iteration"],
469
+ seed=self.base_seed,
470
+ duration_ms=elapsed,
471
+ counterexample=counterexample,
472
+ reason=f"Behavioral divergence detected on input iteration {msg['iteration']}.",
473
+ )
474
+ elif msg.get("status") == "proven":
475
+ return ProofReceipt(
476
+ filepath=filepath,
477
+ callable_name=target_callable,
478
+ tier=VerificationTier.TIER_A_PROVEN,
479
+ iterations_run=msg.get("iterations_run", self.iterations),
480
+ seed=self.base_seed,
481
+ duration_ms=elapsed,
482
+ reason=f"Proved invariant-preserving across {self.iterations} synthesized input permutations.",
483
+ )
484
+ else:
485
+ return ProofReceipt(
486
+ filepath=filepath,
487
+ callable_name=target_callable,
488
+ tier=VerificationTier.TIER_B_SUGGESTED,
489
+ iterations_run=0,
490
+ seed=self.base_seed,
491
+ duration_ms=elapsed,
492
+ reason=msg.get(
493
+ "reason", "Callable could not be verified dynamically in sandbox."
494
+ ),
495
+ )
@@ -1,24 +0,0 @@
1
- ballpython/__init__.py,sha256=d8ONxgaz9jml_fHPVvg867Vt5jGRhGTqzWPtux3WJOc,206
2
- ballpython/__main__.py,sha256=aCRr0vnZfKYHDrPHBsYSjX5xTxtFJpTTSPVUSZTBK-I,136
3
- ballpython/cli.py,sha256=LllCLRAQQ-fRR_lIXwmd2e3Q7DLCIvf-5jHBXsAhJWs,125
4
- pycleaner/__init__.py,sha256=MfmUvfwNaguO_pYaRVKYAXHHKYgPpZmc6n0DPEzMOXA,1940
5
- pycleaner/__main__.py,sha256=8QBFrPyxId563F6cipPMNVEBTG8TIZUgxQfp8gk1VSI,135
6
- pycleaner/cli.py,sha256=1lmOLOiQz7AEABcV2WwcS_5UHkfaSlyjSqX-KHz64tg,50779
7
- pycleaner/complexity_analyzer.py,sha256=esmBAwoC5XDgTKG_HVIXu82kFnJw6LYdbNJIShueSV8,15176
8
- pycleaner/config.py,sha256=Mcp_1uTkIvxQtKKD1Y1xZeUmQVaHKf9PmqB6mqmMzCc,8401
9
- pycleaner/dead_code_detector.py,sha256=yIa_RRCWyQMZLGweJsl41MGbqDE_j0teXoFW5ir8uNE,17598
10
- pycleaner/dependency_auditor.py,sha256=wep0KcLYpHPy0bnuunMgkaIA9HODHTH6WQ8UhSX8oAQ,11589
11
- pycleaner/import_resolver.py,sha256=JnAysZeYtXlmmM0LEnb9HBdkHnEsNwwp4QlxebRxGNE,32189
12
- pycleaner/linter_formatter.py,sha256=LB5j66OCASU8WG8QgRFpSX7C2c7_UNMBgkJh2hSWxAY,20857
13
- pycleaner/pipeline.py,sha256=o9rQUF4Jf8pcLicCPvAjAYHUTgCSAs2rzo_3FM8U-kY,12753
14
- pycleaner/security_scanner.py,sha256=Ycz61UEuvSXcBZ49p5RwQXnBzS7x4NJMCrK8pWZeJBY,20630
15
- pycleaner/syntax_healer.py,sha256=5yJ2WHjHjZ4i_046Xu_F5syDixf4J69ZrlofUHBVeXw,19823
16
- pycleaner/taint_engine.py,sha256=bQc_nT63SgTMKIr7YsgbpW3USRNmw33AUiclzGlqb3c,26483
17
- pycleaner/test_generator.py,sha256=Nu2i7qZtMZHz30h7lk-tef3VT-jAI6m4_jyheo2Abh0,16127
18
- pycleaner/type_checker.py,sha256=CgnYRBTxDCaj2isGaFUdZQb0YxIATrEpElgSTg-Hwgc,33793
19
- pycleaner/typeshed_resolver.py,sha256=N-kWU_CI5DG59lQMkt7wMW9sHYjNCBdfFS42b4y2Kr4,12742
20
- ballpython-2.0.0.dist-info/METADATA,sha256=_gm1hvFBL42IWw8wkloJ7XJ7GWg2bxs4c2mUy6B_sRM,11728
21
- ballpython-2.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
22
- ballpython-2.0.0.dist-info/entry_points.txt,sha256=6ux-5UxoVSmlK5p9M-GDVt24sCdwTQq9wgRDkpMIYU8,81
23
- ballpython-2.0.0.dist-info/top_level.txt,sha256=u6lmy8RWZ_saxCZAQTL7EijB4sunegOiVyk5fHjpwLk,21
24
- ballpython-2.0.0.dist-info/RECORD,,