ballpython 2.1.0__py3-none-any.whl → 2.1.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.
@@ -146,53 +146,95 @@ class SyntaxHealer:
146
146
  code = source
147
147
  repairs: list[str] = []
148
148
 
149
- def apply_step(fn: Any, msg_fmt: str) -> bool:
149
+ def apply_step(fn: Any, msg_fmt: str) -> None:
150
150
  nonlocal code
151
- new_code, count = fn(code)
152
- if count > 0:
153
- code = new_code
154
- repairs.append(msg_fmt.format(count=count))
155
- return self._check_syntax(code, filename) is None
156
- return False
151
+ try:
152
+ prev_err = self._check_syntax(code, filename)
153
+ new_code, count = fn(code)
154
+ if count > 0 and new_code != code:
155
+ new_err = self._check_syntax(new_code, filename)
156
+ # If previously valid code was broken by this repair pass, roll back:
157
+ if prev_err is None and new_err is not None:
158
+ logging.getLogger(__name__).warning(
159
+ "Healing step %s introduced a syntax error; rolling back",
160
+ getattr(fn, "__name__", str(fn)),
161
+ )
162
+ return
163
+ if prev_err is not None and new_err is not None:
164
+ if "::" in new_code and "::" not in code:
165
+ cleaned = re.sub(r"(?<!:)::(?!:)", ":", new_code)
166
+ c_err = self._check_syntax(cleaned, filename)
167
+ if c_err is None or c_err != new_err:
168
+ new_code = cleaned
169
+ new_err = c_err
170
+ if prev_err is not None and new_err is not None:
171
+ if new_err[1] < prev_err[1]:
172
+ logging.getLogger(__name__).warning(
173
+ "Healing step %s broke earlier line %d (previous error was line %d); rolling back",
174
+ getattr(fn, "__name__", str(fn)),
175
+ new_err[1],
176
+ prev_err[1],
177
+ )
178
+ return
179
+ code = new_code
180
+ repairs.append(msg_fmt.format(count=count))
181
+ except Exception:
182
+ logging.getLogger(__name__).debug(
183
+ "Suppressed exception in syntax healing step", exc_info=True
184
+ )
157
185
 
158
186
  if "\t" in code:
159
187
  code = code.expandtabs(4)
160
188
  repairs.append("Normalized tab characters to 4 spaces")
161
- if self._check_syntax(code, filename) is None:
162
- return code, repairs
163
189
 
164
190
  if self.fix_py2_syntax:
165
- if apply_step(
191
+ apply_step(
166
192
  self._fix_py2_except,
167
193
  "Converted {count} legacy Python 2 except clause(s) to 'as'",
168
- ):
169
- return code, repairs
170
- if apply_step(
194
+ )
195
+ apply_step(
171
196
  self._fix_py2_print,
172
197
  "Converted {count} legacy print statement(s) to print() calls",
173
- ):
174
- return code, repairs
198
+ )
175
199
 
176
- if self.fix_conditional_assignments and apply_step(
177
- self._fix_conditional_assignments,
178
- "Replaced {count} accidental '=' assignment(s) with '==' in conditionals",
179
- ):
180
- return code, repairs
200
+ if self.fix_conditional_assignments:
201
+ apply_step(
202
+ self._fix_conditional_assignments,
203
+ "Replaced {count} accidental '=' assignment(s) with '==' in conditionals",
204
+ )
205
+
206
+ apply_step(
207
+ self._fix_double_colons,
208
+ "Normalized {count} duplicate colon(s) on compound statement header(s)",
209
+ )
181
210
 
182
- if apply_step(
211
+ apply_step(
183
212
  self._fix_missing_colons,
184
213
  "Appended missing ':' to {count} compound statement header(s)",
185
- ):
186
- return code, repairs
214
+ )
187
215
 
188
- if apply_step(
216
+ apply_step(
189
217
  self._fix_unindented_blocks,
190
218
  "Re-indented {count} under-indented block(s) following compound statement headers",
191
- ):
192
- return code, repairs
219
+ )
220
+
221
+ try:
222
+ prev_err = self._check_syntax(code, filename)
223
+ d_code, d_repairs = self._fix_unbalanced_delimiters(code, filename=filename)
224
+ if d_code != code:
225
+ d_err = self._check_syntax(d_code, filename)
226
+ if prev_err is None and d_err is not None:
227
+ logging.getLogger(__name__).warning(
228
+ "Delimiter healing broke valid syntax; rolling back"
229
+ )
230
+ else:
231
+ code = d_code
232
+ repairs.extend(d_repairs)
233
+ except Exception:
234
+ logging.getLogger(__name__).debug(
235
+ "Suppressed exception closing delimiters", exc_info=True
236
+ )
193
237
 
194
- code, d_repairs = self._fix_unbalanced_delimiters(code, filename=filename)
195
- repairs.extend(d_repairs)
196
238
  return code, repairs
197
239
 
198
240
  def heal(self, source: str, filename: str = "<unknown>") -> SyntaxHealResult:
@@ -497,11 +539,16 @@ class SyntaxHealer:
497
539
  logical_tokens: list[tokenize.TokenInfo] = []
498
540
 
499
541
  def flush() -> None:
542
+ if not logical_tokens:
543
+ return
500
544
  header_idx = self._get_compound_header_idx(logical_tokens)
501
545
  if header_idx is None:
502
546
  return
503
547
  if self._has_colon_at_depth_zero(logical_tokens, header_idx + 1):
504
548
  return
549
+ # If the last token is already a colon, do not append another one
550
+ if logical_tokens[-1].string == ":":
551
+ return
505
552
  last = logical_tokens[-1]
506
553
  insertions.append((last.end[0] - 1, last.end[1]))
507
554
 
@@ -524,28 +571,146 @@ class SyntaxHealer:
524
571
  flush()
525
572
  return insertions
526
573
 
574
+ _DOUBLE_COLON_RE = re.compile(
575
+ r"^([ \t]*(?:async\s+)?(?:def\s+[a-zA-Z_]\w*|class\s+[a-zA-Z_]\w*|if\b|elif\b|else\b|for\b|while\b|try\b|except\b|finally\b|with\b)[^\n#]*?):{2,}(\s*(?:#.*)?)$"
576
+ )
577
+
578
+ def _fix_double_colons(self, code: str) -> tuple[str, int]:
579
+ """Normalize multiple consecutive colons on compound statement headers."""
580
+ if "::" not in code:
581
+ return code, 0
582
+ lines = code.splitlines(keepends=True)
583
+ count = 0
584
+ new_lines: list[str] = []
585
+ for line in lines:
586
+ m = self._DOUBLE_COLON_RE.match(line.rstrip("\r\n"))
587
+ if m:
588
+ header = m.group(1).rstrip()
589
+ suffix = m.group(2)
590
+ ending = (
591
+ "\r\n"
592
+ if line.endswith("\r\n")
593
+ else ("\n" if line.endswith("\n") else "")
594
+ )
595
+ new_lines.append(f"{header}:{suffix}{ending}")
596
+ count += 1
597
+ else:
598
+ new_lines.append(line)
599
+ return "".join(new_lines), count
600
+
601
+ _COMPOUND_HEADER_FALLBACK = re.compile(
602
+ r"^([ \t]*(?:async\s+)?(?:def\s+[a-zA-Z_]\w*|class\s+[a-zA-Z_]\w*|if\b|elif\b|else\b|for\b|while\b|try\b|except\b|finally\b|with\b)[^\n#]*?)(?<!:)(\s*(?:#.*)?)$"
603
+ )
604
+
605
+ def _fix_missing_colons_fallback(self, code: str) -> tuple[str, int]:
606
+ """Line-by-line regex fallback to append missing colons to compound statements."""
607
+ lines = code.splitlines(keepends=True)
608
+ count = 0
609
+ new_lines: list[str] = []
610
+ for line in lines:
611
+ stripped = line.strip()
612
+ if not stripped or stripped.startswith("#"):
613
+ new_lines.append(line)
614
+ continue
615
+
616
+ # If the non-comment portion of the line already ends with ':', skip it
617
+ clean_line = re.sub(r"#.*$", "", line.rstrip("\r\n")).rstrip()
618
+ if clean_line.endswith(":"):
619
+ new_lines.append(line)
620
+ continue
621
+
622
+ m = self._COMPOUND_HEADER_FALLBACK.match(line.rstrip("\r\n"))
623
+ if m:
624
+ header = m.group(1).rstrip()
625
+ # Before appending ':', check that the header does not already end with ':'
626
+ if header.endswith(":"):
627
+ new_lines.append(line)
628
+ continue
629
+
630
+ # If this line already has a colon at depth 0 (e.g. one-liner: if cond: return x, or else: return 0), skip it!
631
+ depth = 0
632
+ has_colon_at_depth_0 = False
633
+ in_quote = False
634
+ quote_char = ""
635
+ for char in header:
636
+ if in_quote:
637
+ if char == quote_char:
638
+ in_quote = False
639
+ elif char in ("'", '"'):
640
+ in_quote = True
641
+ quote_char = char
642
+ elif char in "([{":
643
+ depth += 1
644
+ elif char in ")]}":
645
+ depth = max(0, depth - 1)
646
+ elif char == ":" and depth == 0:
647
+ has_colon_at_depth_0 = True
648
+ break
649
+
650
+ if has_colon_at_depth_0:
651
+ new_lines.append(line)
652
+ continue
653
+
654
+ # If brackets on this line are unbalanced (e.g. def foo(\n), skip appending colon to this line
655
+ open_cnt = sum(header.count(c) for c in "([{")
656
+ close_cnt = sum(header.count(c) for c in ")]}")
657
+ if open_cnt > close_cnt:
658
+ new_lines.append(line)
659
+ continue
660
+
661
+ suffix = m.group(2)
662
+ ending = (
663
+ "\r\n"
664
+ if line.endswith("\r\n")
665
+ else ("\n" if line.endswith("\n") else "")
666
+ )
667
+ new_lines.append(f"{header}:{suffix}{ending}")
668
+ count += 1
669
+ else:
670
+ new_lines.append(line)
671
+ return "".join(new_lines), count
672
+
527
673
  def _fix_missing_colons(self, code: str) -> tuple[str, int]:
528
674
  """Detect compound statement headers without trailing colons and append them."""
529
- tokens: list[tokenize.TokenInfo] | None
675
+ tokens: list[tokenize.TokenInfo] | None = None
530
676
  try:
531
677
  tokens = list(tokenize.generate_tokens(io.StringIO(code).readline))
532
678
  except tokenize.TokenError:
533
679
  tokens = self._tokenize_with_recovered_delimiters(code)
534
- if tokens is None:
535
- return code, 0
536
-
537
- insertions = self._find_missing_colon_insertions(tokens)
538
- if not insertions:
539
- return code, 0
540
-
541
- lines = code.splitlines(keepends=True)
542
- insertions.sort(key=lambda pos: (pos[0], pos[1]), reverse=True)
543
- for line_idx, col in insertions:
544
- if 0 <= line_idx < len(lines):
545
- target = lines[line_idx]
546
- lines[line_idx] = target[:col] + ":" + target[col:]
547
680
 
548
- return "".join(lines), len(insertions)
681
+ cur_code = code
682
+ total_count = 0
683
+
684
+ if tokens is not None:
685
+ insertions = self._find_missing_colon_insertions(tokens)
686
+ if insertions:
687
+ lines = cur_code.splitlines(keepends=True)
688
+ insertions.sort(key=lambda pos: (pos[0], pos[1]), reverse=True)
689
+ applied_insertions = 0
690
+ for line_idx, col in insertions:
691
+ if 0 <= line_idx < len(lines):
692
+ target = lines[line_idx]
693
+ before = target[:col].rstrip()
694
+ after = target[col:].lstrip()
695
+ if before.endswith(":") or after.startswith(":"):
696
+ continue
697
+ clean_line = re.sub(r"#.*$", "", target).rstrip()
698
+ if clean_line.endswith(":"):
699
+ continue
700
+ lines[line_idx] = target[:col] + ":" + target[col:]
701
+ applied_insertions += 1
702
+ cur_code = "".join(lines)
703
+ total_count += applied_insertions
704
+
705
+ # Also run fallback to catch any compound headers missed (e.g. disrupted by unclosed delimiters)
706
+ cur_code, fb_count = self._fix_missing_colons_fallback(cur_code)
707
+ total_count += fb_count
708
+
709
+ # Clean up any accidental double colons created on compound headers
710
+ if "::" in cur_code and "::" not in code:
711
+ cur_code = re.sub(r"(?<!:)::(?!:)", ":", cur_code)
712
+
713
+ return cur_code, total_count
549
714
 
550
715
  def _tokenize_with_recovered_delimiters(
551
716
  self, code: str
pycleaner/taint_engine.py CHANGED
@@ -262,20 +262,8 @@ class TaintEngine:
262
262
  def __init__(self) -> None:
263
263
  self.function_summaries: dict[str, FunctionTaintSummary] = {}
264
264
 
265
- def scan_path(
266
- self, target: str | Path, exclude_patterns: Sequence[str] = ()
267
- ) -> TaintReport:
268
- """Scan a file or directory for dataflow taint vulnerabilities."""
269
- path = Path(target)
270
- if path.is_file():
271
- files = [path] if path.suffix == ".py" else []
272
- elif path.is_dir():
273
- files = collect_project_python_files(
274
- path, exclude_patterns=exclude_patterns
275
- )
276
- else:
277
- return TaintReport()
278
-
265
+ def scan_files(self, files: Sequence[Path]) -> TaintReport:
266
+ """Scan a specific sequence of Python files for dataflow taint vulnerabilities."""
279
267
  report = TaintReport()
280
268
  parsed_files: list[tuple[Path, ast.Module]] = []
281
269
 
@@ -286,7 +274,7 @@ class TaintEngine:
286
274
  tree = ast.parse(code, filename=str(fpath))
287
275
  parsed_files.append((fpath, tree))
288
276
  self._collect_function_summaries(str(fpath), tree)
289
- except SyntaxError:
277
+ except (SyntaxError, OSError):
290
278
  continue
291
279
 
292
280
  # Pass 2: Interprocedural and intraprocedural taint flow analysis
@@ -299,6 +287,22 @@ class TaintEngine:
299
287
 
300
288
  return report
301
289
 
290
+ def scan_path(
291
+ self, target: str | Path, exclude_patterns: Sequence[str] = ()
292
+ ) -> TaintReport:
293
+ """Scan a file or directory for dataflow taint vulnerabilities."""
294
+ path = Path(target)
295
+ if path.is_file():
296
+ files = [path] if path.suffix == ".py" else []
297
+ elif path.is_dir():
298
+ files = collect_project_python_files(
299
+ path, exclude_patterns=exclude_patterns
300
+ )
301
+ else:
302
+ return TaintReport()
303
+
304
+ return self.scan_files(files)
305
+
302
306
  def _inspect_return_child(
303
307
  self, child: ast.AST, summary: FunctionTaintSummary
304
308
  ) -> None:
pycleaner/type_checker.py CHANGED
@@ -32,6 +32,8 @@ class PyType:
32
32
  """Check if this type can be assigned to target type."""
33
33
  if isinstance(target, AnyType) or isinstance(self, AnyType) or target == self:
34
34
  return True
35
+ if getattr(target, "name", None) == "object":
36
+ return True
35
37
  if (
36
38
  isinstance(self, CustomClassType)
37
39
  and isinstance(target, CustomClassType)
@@ -116,7 +118,8 @@ class PyType:
116
118
  )
117
119
 
118
120
  def _check_custom_source(self, target: PyType) -> bool:
119
- if self.name in ("StrPath", "PathLike", "AnyStr"): # type: ignore[attr-defined]
121
+ src_name = getattr(self, "name", "")
122
+ if src_name in ("StrPath", "PathLike", "AnyStr"):
120
123
  if isinstance(target, PrimitiveType) and target.name in ("str", "bytes"):
121
124
  return True
122
125
  if isinstance(target, CustomClassType) and target.name in (
@@ -127,6 +130,31 @@ class PyType:
127
130
  "bytes",
128
131
  ):
129
132
  return True
133
+ if (
134
+ src_name.startswith("Supports")
135
+ or src_name.startswith("_")
136
+ or src_name in ("T", "Any")
137
+ ):
138
+ return True
139
+ if src_name in (
140
+ "zip",
141
+ "enumerate",
142
+ "range",
143
+ "generator",
144
+ "iter",
145
+ "map",
146
+ "filter",
147
+ ):
148
+ if isinstance(target, (ListType, SetType)):
149
+ return True
150
+ if isinstance(target, CustomClassType) and target.name in (
151
+ "list",
152
+ "List",
153
+ "Sequence",
154
+ "Iterable",
155
+ "Collection",
156
+ ):
157
+ return True
130
158
  return False
131
159
 
132
160
  def _check_numeric_promotions(self, target: PyType) -> bool:
@@ -556,25 +584,33 @@ class TypeChecker:
556
584
  except SyntaxError:
557
585
  return 0
558
586
 
559
- def check_project(
560
- self, root_dir: str | Path, exclude_patterns: Sequence[str] = ()
561
- ) -> TypeReport:
562
- """Type-check all Python files across an entire project directory."""
563
- root = Path(root_dir).resolve()
587
+ def check_files(self, target_files: Sequence[Path]) -> TypeReport:
588
+ """Type-check a specific sequence of Python files without walking directories."""
564
589
  findings: list[TypeFinding] = []
565
590
  functions_checked = 0
566
- py_files = self._discover_python_files(root, exclude_patterns=exclude_patterns)
567
-
568
- for fpath in py_files:
569
- findings.extend(self.check_file(fpath))
570
- functions_checked += self._count_functions(fpath)
591
+ scanned_count = 0
592
+ for fpath in target_files:
593
+ try:
594
+ findings.extend(self.check_file(fpath))
595
+ functions_checked += self._count_functions(fpath)
596
+ scanned_count += 1
597
+ except OSError:
598
+ continue
571
599
 
572
600
  return TypeReport(
573
601
  findings=findings,
574
- files_scanned=len(py_files),
602
+ files_scanned=scanned_count,
575
603
  functions_checked=functions_checked,
576
604
  )
577
605
 
606
+ def check_project(
607
+ self, root_dir: str | Path, exclude_patterns: Sequence[str] = ()
608
+ ) -> TypeReport:
609
+ """Type-check all Python files across an entire project directory."""
610
+ root = Path(root_dir).resolve()
611
+ py_files = self._discover_python_files(root, exclude_patterns=exclude_patterns)
612
+ return self.check_files(py_files)
613
+
578
614
  def _fallback_walk(self, root: Path):
579
615
  import os
580
616
 
@@ -593,6 +629,58 @@ class TypeChecker:
593
629
  )
594
630
  return scope
595
631
 
632
+ @staticmethod
633
+ def _is_terminal_statement(stmt: ast.stmt | None) -> bool:
634
+ """Check if an AST statement terminates all control flow paths (returns or raises)."""
635
+ if stmt is None:
636
+ return False
637
+ if isinstance(stmt, (ast.Return, ast.Raise)):
638
+ return True
639
+ if isinstance(stmt, (ast.With, ast.AsyncWith)):
640
+ return bool(stmt.body) and TypeChecker._is_terminal_statement(stmt.body[-1])
641
+ if isinstance(stmt, ast.Try):
642
+ if not (
643
+ bool(stmt.body) and TypeChecker._is_terminal_statement(stmt.body[-1])
644
+ ):
645
+ return False
646
+ for handler in stmt.handlers:
647
+ if not (
648
+ bool(handler.body)
649
+ and TypeChecker._is_terminal_statement(handler.body[-1])
650
+ ):
651
+ return False
652
+ if stmt.orelse and not TypeChecker._is_terminal_statement(stmt.orelse[-1]):
653
+ return False
654
+ return True
655
+ if isinstance(stmt, ast.If):
656
+ if not (
657
+ bool(stmt.body) and TypeChecker._is_terminal_statement(stmt.body[-1])
658
+ ):
659
+ return False
660
+ if not (
661
+ bool(stmt.orelse)
662
+ and TypeChecker._is_terminal_statement(stmt.orelse[-1])
663
+ ):
664
+ return False
665
+ return True
666
+ if isinstance(stmt, ast.While):
667
+ if isinstance(stmt.test, ast.Constant) and bool(stmt.test.value):
668
+ has_return = any(
669
+ isinstance(n, (ast.Return, ast.Raise)) for n in ast.walk(stmt)
670
+ )
671
+ has_break = any(isinstance(n, ast.Break) for n in ast.walk(stmt))
672
+ if has_return and not has_break:
673
+ return True
674
+ if isinstance(stmt, getattr(ast, "Match", ())):
675
+ cases = getattr(stmt, "cases", [])
676
+ if not cases:
677
+ return False
678
+ return all(
679
+ bool(c.body) and TypeChecker._is_terminal_statement(c.body[-1])
680
+ for c in cases
681
+ )
682
+ return False
683
+
596
684
  def _check_return_paths(
597
685
  self,
598
686
  func: ast.FunctionDef | ast.AsyncFunctionDef,
@@ -649,7 +737,7 @@ class TypeChecker:
649
737
  )
650
738
  ):
651
739
  last_stmt = func.body[-1] if func.body else None
652
- ends_with_terminal = isinstance(last_stmt, (ast.Return, ast.Raise))
740
+ ends_with_terminal = self._is_terminal_statement(last_stmt)
653
741
  if not ends_with_terminal:
654
742
  snippet = (
655
743
  ctx.source_lines[func.lineno - 1]
@@ -1009,18 +1097,21 @@ class TypeChecker:
1009
1097
  return parse_type_annotation(cls_sig.methods[func.attr].return_type)
1010
1098
  return None
1011
1099
 
1012
- def _infer_typeshed_lookup(self, target: str, func_name: str) -> PyType:
1100
+ def _infer_typeshed_lookup(
1101
+ self, target: str, func_name: str, is_attribute: bool = False
1102
+ ) -> PyType:
1013
1103
  if target:
1014
1104
  sig = self.typeshed.resolve_function(target)
1015
1105
  if sig and sig.return_type and sig.return_type != "Any":
1016
1106
  return parse_type_annotation(sig.return_type)
1017
1107
 
1018
- builtin_ret = self.typeshed.get_builtin_return_type(func_name)
1019
- if builtin_ret:
1020
- return parse_type_annotation(builtin_ret)
1108
+ if not is_attribute:
1109
+ builtin_ret = self.typeshed.get_builtin_return_type(func_name)
1110
+ if builtin_ret:
1111
+ return parse_type_annotation(builtin_ret)
1021
1112
 
1022
- if func_name and self.typeshed.resolve_class("builtins", func_name):
1023
- return CustomClassType(func_name)
1113
+ if func_name and self.typeshed.resolve_class("builtins", func_name):
1114
+ return CustomClassType(func_name)
1024
1115
 
1025
1116
  return AnyType()
1026
1117
 
@@ -1031,21 +1122,33 @@ class TypeChecker:
1031
1122
  local_functions: dict[str, tuple[dict[str, PyType], PyType]],
1032
1123
  ) -> PyType:
1033
1124
  func_name, dotted_name = self._resolve_call_names(expr)
1034
- ctor = self._infer_constructor_call(
1035
- func_name, expr.args, scope, local_functions
1036
- )
1037
- if ctor is not None:
1038
- return ctor
1125
+ if not isinstance(expr.func, ast.Attribute):
1126
+ ctor = self._infer_constructor_call(
1127
+ func_name, expr.args, scope, local_functions
1128
+ )
1129
+ if ctor is not None:
1130
+ return ctor
1039
1131
 
1040
1132
  if isinstance(expr.func, ast.Attribute):
1041
1133
  meth = self._infer_attribute_method(expr.func, scope, local_functions)
1042
1134
  if meth is not None:
1043
1135
  return meth
1136
+ if isinstance(expr.func.value, ast.Name) and expr.func.value.id in (
1137
+ "self",
1138
+ "cls",
1139
+ ):
1140
+ if func_name in local_functions:
1141
+ return local_functions[func_name][1]
1142
+ return self._infer_typeshed_lookup(
1143
+ dotted_name, func_name, is_attribute=True
1144
+ )
1044
1145
 
1045
1146
  if func_name in local_functions:
1046
1147
  return local_functions[func_name][1]
1047
1148
 
1048
- return self._infer_typeshed_lookup(dotted_name or func_name, func_name)
1149
+ return self._infer_typeshed_lookup(
1150
+ dotted_name or func_name, func_name, is_attribute=False
1151
+ )
1049
1152
 
1050
1153
  def _infer_expr_type(
1051
1154
  self,
pycleaner/verifier.py CHANGED
@@ -99,8 +99,12 @@ def _worker_differential_fuzz(
99
99
  }
100
100
 
101
101
  try:
102
- orig_code = compile(orig_source, "<orig_sandbox>", "exec") # nosec: B102 - intentionally isolated in worker process
103
- exec(orig_code, orig_ns) # nosec: B102 - intentionally isolated in worker process
102
+ orig_code = compile(
103
+ orig_source, "<orig_sandbox>", "exec"
104
+ ) # nosec: B102 - intentionally isolated in worker process
105
+ exec(
106
+ orig_code, orig_ns
107
+ ) # nosec: B102 - intentionally isolated in worker process
104
108
  except BaseException as e:
105
109
  conn.send(
106
110
  {
@@ -111,8 +115,12 @@ def _worker_differential_fuzz(
111
115
  return
112
116
 
113
117
  try:
114
- trans_code = compile(trans_source, "<trans_sandbox>", "exec") # nosec: B102 - intentionally isolated in worker process
115
- exec(trans_code, trans_ns) # nosec: B102 - intentionally isolated in worker process
118
+ trans_code = compile(
119
+ trans_source, "<trans_sandbox>", "exec"
120
+ ) # nosec: B102 - intentionally isolated in worker process
121
+ exec(
122
+ trans_code, trans_ns
123
+ ) # nosec: B102 - intentionally isolated in worker process
116
124
  except BaseException as e:
117
125
  conn.send(
118
126
  {
@@ -1,34 +0,0 @@
1
- ballpython/__init__.py,sha256=GSB-LHdou1Un6DS6Zmh8tv_zrwh6HQHO3Py2z8GO39o,304
2
- ballpython/__main__.py,sha256=aCRr0vnZfKYHDrPHBsYSjX5xTxtFJpTTSPVUSZTBK-I,136
3
- ballpython/cli.py,sha256=LllCLRAQQ-fRR_lIXwmd2e3Q7DLCIvf-5jHBXsAhJWs,125
4
- ballpython/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- pycleaner/__init__.py,sha256=VzmbjUip6i0wpKh-s6XBwgBkIeUgI1duGyKDIiv7Pls,3042
6
- pycleaner/__main__.py,sha256=8QBFrPyxId563F6cipPMNVEBTG8TIZUgxQfp8gk1VSI,135
7
- pycleaner/baseline.py,sha256=ljED3x3akie0SB3fzhVno_1UHlI2id-9vta0NIGC0mw,3857
8
- pycleaner/cache.py,sha256=T3JIxYg8ZezRbSpXYXPD-SyMUkjg4nfKsdO0IuNypOY,10774
9
- pycleaner/cli.py,sha256=NswwV_3cbHiL63Pj6qDtSpV_98zXvGo7UuBHwNAxpQ8,73752
10
- pycleaner/complexity_analyzer.py,sha256=j2ktzgR-isWE4xWMe56N3JFLLuYCi-S84BIlRBe0v-I,14975
11
- pycleaner/config.py,sha256=XhloyaY_9pdwbQuc3QaAPCerLeXz-i1da4xT50h-pjY,8514
12
- pycleaner/dead_code_detector.py,sha256=uufGt7EtGusQdtbX3rUCHIrF8IAu-S16jrViBJdnV-g,52092
13
- pycleaner/dependency_auditor.py,sha256=EmSqr_ayUqmdSZ7Nm4QAT4qfikF9VToLL8eXVecIDpw,23395
14
- pycleaner/discovery.py,sha256=dt3OmHg886hy_Z0p80ccMyDkITQxz4biaJY_zB3MU_g,6151
15
- pycleaner/explanations.py,sha256=2OOU_l3YL5o_Br_6fP6MF8HlY75-1JRmw3eTYT2lu8M,14219
16
- pycleaner/import_resolver.py,sha256=Wgrch335AQVsnDv5-7F0l4Zm2oQPkNebPEgTRa7ayAg,32855
17
- pycleaner/linter_formatter.py,sha256=wdTjwguPWzkJ9KRMcQpFHEirb3ogc9qay69yiphz16Y,21356
18
- pycleaner/modernizer.py,sha256=kd9_G5EzdW_O-9zm1hA6v33Sb__UbpMZzSvopF-UK5w,16820
19
- pycleaner/pipeline.py,sha256=ZG7_9PNz0jBadFrZoiI7juLtUJXglGQhLgQhrLJqEac,20612
20
- pycleaner/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- pycleaner/security_scanner.py,sha256=8nyRtoCjBYAB_sI5azlNsEMm2S0s2zE7fPhc7QCHWR0,23527
22
- pycleaner/syntax_healer.py,sha256=5VYclqkI3WFnm6yfG0zRLMkHN4QMU4aN4ZYCcSUsmZk,27408
23
- pycleaner/taint_engine.py,sha256=-bBq38YoQgbO2FHCDxDctZpXWQTqwuS56CEWFNOY000,26870
24
- pycleaner/test_generator.py,sha256=MM3t0Dx5lyJB5s144IKXq2tXrE8ohKcSvicPR3c9MlU,19680
25
- pycleaner/type_checker.py,sha256=zkUqi-BNLUzJT0tIECNh7_7GP29Y7_6fMXhVuguwMKg,37881
26
- pycleaner/typeshed_resolver.py,sha256=N-kWU_CI5DG59lQMkt7wMW9sHYjNCBdfFS42b4y2Kr4,12742
27
- pycleaner/verifier.py,sha256=QlBwnsrhWWRK3LzvJjpNix474drCNp6LP7FiUgJxMsg,19333
28
- pycleaner/frameworks/__init__.py,sha256=SOuSRl39af86eoQx7Qm8rTJGD4jsnEvgyhhQ6cVmRVA,3462
29
- pycleaner/frameworks/plugins.py,sha256=qunMzS3q7xp1GEGxqkuhrnm-raiSRhYQMzmDxsEhSUg,11452
30
- ballpython-2.1.0.dist-info/METADATA,sha256=98OjKH0i9ih05fQ9JX2eriRljEboAEOVIgk4TwQ9hQ8,3466
31
- ballpython-2.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
32
- ballpython-2.1.0.dist-info/entry_points.txt,sha256=6ux-5UxoVSmlK5p9M-GDVt24sCdwTQq9wgRDkpMIYU8,81
33
- ballpython-2.1.0.dist-info/top_level.txt,sha256=u6lmy8RWZ_saxCZAQTL7EijB4sunegOiVyk5fHjpwLk,21
34
- ballpython-2.1.0.dist-info/RECORD,,