codesuture 0.5.0__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.
@@ -0,0 +1,86 @@
1
+
2
+ import sys
3
+ import traceback
4
+
5
+ class CodeSutureMiddleware:
6
+
7
+ def __init__(self, app):
8
+ self.app = app
9
+ self._retried_exc_types = set()
10
+
11
+ def __call__(self, environ, start_response):
12
+
13
+ try:
14
+ return self.app(environ, start_response)
15
+ except Exception as exc:
16
+ return self._handle_exception(exc, environ, start_response)
17
+
18
+ def _handle_exception(self, exc, environ, start_response):
19
+
20
+ exc_type = type(exc)
21
+
22
+ if exc_type in self._retried_exc_types:
23
+ raise exc
24
+
25
+ tb = sys.exc_info()[2]
26
+ if tb is None:
27
+ raise exc
28
+
29
+ inner_tb = tb
30
+ while inner_tb.tb_next:
31
+ inner_tb = inner_tb.tb_next
32
+ frame = inner_tb.tb_frame
33
+
34
+ try:
35
+ from codesuture.pattern_matcher import analyze_exception
36
+ from codesuture.guard_synthesizer import synthesize_guarded_code
37
+ from codesuture.code_replacer import replace_function_code, get_function_from_frame
38
+
39
+ spec = analyze_exception(frame, exc_type, exc, tb)
40
+ if spec is None:
41
+ raise exc
42
+
43
+ func = get_function_from_frame(frame)
44
+ if func is None:
45
+ raise exc
46
+
47
+ new_bc = synthesize_guarded_code(frame.f_code, spec)
48
+ new_code = new_bc.to_code()
49
+ replace_function_code(func, new_code)
50
+
51
+ guard_type = spec.strategy
52
+ target_name = spec.var_name
53
+ if spec.key_name:
54
+ target_name = spec.key_name if isinstance(spec.key_name, str) else spec.key_name[-1]
55
+
56
+ self._retried_exc_types.add(exc_type)
57
+
58
+ try:
59
+ patched_start_called = []
60
+
61
+ def patched_start_response(status, headers, exc_info=None):
62
+
63
+ headers = list(headers) + [
64
+ ("X-CodeSuture", f"patched=1;guard={guard_type};target={target_name}")
65
+ ]
66
+ patched_start_called.append(True)
67
+ return start_response(status, headers, exc_info)
68
+
69
+ result = self.app(environ, patched_start_response)
70
+
71
+ if not patched_start_called:
72
+ start_response("200 OK", [
73
+ ("X-CodeSuture", f"patched=1;guard={guard_type};target={target_name}")
74
+ ])
75
+
76
+ return result
77
+
78
+ except Exception:
79
+
80
+ raise exc
81
+
82
+ except Exception as inner:
83
+ if inner is exc:
84
+ raise
85
+
86
+ raise exc
@@ -0,0 +1,555 @@
1
+ from types import FrameType
2
+ from typing import Optional, NamedTuple
3
+ import dis
4
+ import re
5
+
6
+ class PatchSpec(NamedTuple):
7
+ strategy: str
8
+ var_name: str
9
+ default_value: object
10
+ key_name: Optional[str] = None
11
+ list_len_var: Optional[str] = None
12
+ is_async: bool = False
13
+ inside_loop: bool = False
14
+ target_func: Optional[object] = None
15
+ target_name: Optional[str] = None
16
+
17
+ def analyze_exception(frame: FrameType, exc_type, exc_value, exc_tb) -> Optional[PatchSpec]:
18
+ from codesuture.code_replacer import get_function_from_frame
19
+ func = get_function_from_frame(frame)
20
+ if func is not None:
21
+ func_name = getattr(func, '__qualname__', func.__name__)
22
+ exc_type_name = getattr(exc_type, '__name__', str(exc_type))
23
+ spec = check_learned_rules(func_name, exc_type_name, str(exc_value))
24
+ if spec:
25
+
26
+ if frame.f_code.co_flags & 0x100:
27
+ spec = spec._replace(is_async=True)
28
+
29
+ instructions = list(dis.get_instructions(frame.f_code))
30
+ crash_instr = _find_target_instr(instructions, frame.f_lasti)
31
+ if crash_instr is not None:
32
+ inside_loop = False
33
+ for instr in instructions:
34
+ if instr.offset >= crash_instr.offset:
35
+ break
36
+ if instr.opname == 'FOR_ITER':
37
+
38
+ if getattr(instr, 'argval', 0) > crash_instr.offset:
39
+ inside_loop = True
40
+
41
+ if inside_loop:
42
+ spec = spec._replace(inside_loop=True)
43
+ print(f"[CodeSuture WARNING] Crash detected inside a for-loop at offset {crash_instr.offset}.\n The patched function will restart from the beginning.\n If the loop has side effects (DB writes, API calls, counters),\n items processed before the crash will be reprocessed.\n Consider adding idempotency checks in the loop body.")
44
+
45
+ return spec
46
+
47
+ msg = str(exc_value)
48
+ type_name = getattr(exc_type, '__name__', str(exc_type))
49
+
50
+ spec = None
51
+
52
+ if type_name == 'AttributeError' and "'NoneType' object has no attribute" in msg:
53
+ spec = _null_guard_spec(frame)
54
+
55
+ elif type_name == 'ZeroDivisionError':
56
+ spec = _division_guard_spec(frame)
57
+
58
+ elif type_name == 'TypeError':
59
+ if "'NoneType' object is not subscriptable" in msg:
60
+ spec = _none_subscript_spec(frame)
61
+ elif "'NoneType' object is not callable" in msg:
62
+ spec = _callable_guard_spec(frame)
63
+ elif "can only concatenate str" in msg:
64
+ spec = _str_concat_spec(frame, msg)
65
+
66
+ if spec is None and type_name in ('TypeError', 'ValueError'):
67
+
68
+ coerce = _type_coercion_spec(frame, msg)
69
+ if coerce is not None:
70
+ spec = coerce
71
+
72
+ if spec is None and type_name == 'IndexError' and "list index out of range" in msg:
73
+ spec = _index_bound_spec(frame)
74
+
75
+ if spec is None and type_name == 'KeyError':
76
+ spec = _dict_get_spec(frame, msg)
77
+
78
+ if spec is None and type_name == 'FileNotFoundError':
79
+ spec = _file_guard_spec(frame)
80
+
81
+ if spec is not None and frame.f_code.co_flags & 0x100:
82
+ spec = spec._replace(is_async=True)
83
+
84
+ return spec
85
+
86
+ def check_learned_rules(func_name, exc_type_name, exc_message):
87
+ from codesuture.knowledge import load_learned_rules
88
+ rules = load_learned_rules()
89
+ for rule in rules:
90
+ if rule["func_name"] == func_name and rule["exc_type_name"] == exc_type_name:
91
+ return PatchSpec(
92
+ strategy='autonomous_rule',
93
+ var_name=func_name,
94
+ default_value=rule["new_source"]
95
+ )
96
+ return None
97
+
98
+ def _find_target_instr(instructions, lasti):
99
+ target = None
100
+ for instr in instructions:
101
+ if instr.offset <= lasti:
102
+ target = instr
103
+ else:
104
+ break
105
+ return target
106
+
107
+ def _check_property_origin(frame, instructions, crash_idx):
108
+ load_attr_instr = None
109
+ load_attr_idx = -1
110
+ for i in range(crash_idx - 1, -1, -1):
111
+ if instructions[i].opname in ('LOAD_ATTR', 'LOAD_METHOD'):
112
+ load_attr_instr = instructions[i]
113
+ load_attr_idx = i
114
+ break
115
+ elif instructions[i].opname in ('LOAD_CONST', 'LOAD_FAST', 'LOAD_GLOBAL', 'LOAD_DEREF'):
116
+ pass
117
+ else:
118
+ break
119
+ if not load_attr_instr:
120
+ return None
121
+ attr_name = load_attr_instr.argval
122
+ obj_instr = None
123
+ for i in range(load_attr_idx - 1, -1, -1):
124
+ if instructions[i].opname in ('LOAD_FAST', 'LOAD_DEREF', 'LOAD_GLOBAL'):
125
+ obj_instr = instructions[i]
126
+ break
127
+ elif instructions[i].opname in ('LOAD_ATTR', 'LOAD_METHOD'):
128
+ pass
129
+ else:
130
+ break
131
+ if not obj_instr:
132
+ return None
133
+ obj = None
134
+ if obj_instr.opname in ('LOAD_FAST', 'LOAD_DEREF'):
135
+ var_name = frame.f_code.co_varnames[obj_instr.arg] if obj_instr.opname == 'LOAD_FAST' else ""
136
+ if obj_instr.opname == 'LOAD_DEREF':
137
+ names = frame.f_code.co_freevars + frame.f_code.co_cellvars
138
+ var_name = names[obj_instr.arg] if obj_instr.arg < len(names) else ""
139
+ obj = frame.f_locals.get(var_name)
140
+ elif obj_instr.opname == 'LOAD_GLOBAL':
141
+ var_name = obj_instr.argval
142
+ obj = frame.f_globals.get(var_name, frame.f_builtins.get(var_name))
143
+ if obj is None:
144
+ return None
145
+ prop = getattr(type(obj), attr_name, None)
146
+ if isinstance(prop, property) and prop.fget is not None:
147
+ return {'target_func': prop.fget, 'target_name': f"{type(obj).__name__}.{attr_name}"}
148
+ return None
149
+
150
+ def _infer_default(var_name, instructions=None, crash_idx=None):
151
+ if instructions is not None and crash_idx is not None:
152
+ for i in range(crash_idx + 1, min(crash_idx + 11, len(instructions))):
153
+ instr = instructions[i]
154
+ if instr.opname in ('LOAD_ATTR', 'LOAD_METHOD'):
155
+ string_methods = {'upper', 'lower', 'strip', 'replace', 'split', 'join', 'format', 'startswith', 'endswith', 'capitalize', 'lstrip', 'rstrip', 'title', 'swapcase', 'center', 'ljust', 'rjust', 'zfill', 'encode'}
156
+ list_dict_methods = {'append', 'extend', 'pop', 'remove', 'get', 'keys', 'values', 'items', 'update'}
157
+ if instr.argval in string_methods:
158
+ return ""
159
+ if instr.argval in list_dict_methods:
160
+ return []
161
+ elif instr.opname in ('BINARY_OP', 'BINARY_ADD', 'BINARY_SUBTRACT', 'BINARY_MULTIPLY', 'BINARY_TRUE_DIVIDE'):
162
+ return 0
163
+ elif instr.opname in ('POP_JUMP_IF_FALSE', 'POP_JUMP_FORWARD_IF_FALSE', 'POP_JUMP_IF_TRUE', 'POP_JUMP_FORWARD_IF_TRUE'):
164
+ return False
165
+ elif instr.opname == 'IS_OP':
166
+ return None
167
+ elif instr.opname in ('FORMAT_VALUE', 'BUILD_STRING'):
168
+ return ""
169
+ elif instr.opname == 'BINARY_SUBSCR':
170
+ return {}
171
+ elif instr.opname in ('LOAD_CONST', 'LOAD_FAST', 'PRECALL', 'CALL'):
172
+ continue
173
+ elif instr.opname in ('RETURN_VALUE', 'STORE_FAST'):
174
+ break
175
+
176
+ name = var_name.lower()
177
+ if any(kw in name for kw in ('count','num','age','len','size','index','price','amount','discount')):
178
+ return 0
179
+ if any(kw in name for kw in ('name','text','string','msg','email','path','filename')):
180
+ return ""
181
+ if any(kw in name for kw in ('list','items','keys','values','array')):
182
+ return []
183
+ return ""
184
+
185
+ def _infer_attribute_default(attr_name, fallback_name, instructions=None, crash_idx=None):
186
+ string_methods = {
187
+ 'capitalize', 'casefold', 'center', 'encode', 'expandtabs', 'format',
188
+ 'format_map', 'join', 'ljust', 'lower', 'lstrip', 'removeprefix',
189
+ 'removesuffix', 'replace', 'rjust', 'strip', 'swapcase', 'title',
190
+ 'translate', 'upper', 'zfill',
191
+ }
192
+ if attr_name in string_methods:
193
+ return ""
194
+ return _infer_default(fallback_name, instructions, crash_idx)
195
+
196
+ _ORIGINAL_INFER_DEFAULT = _infer_default
197
+
198
+ _KNOWN_CALLABLES = {
199
+ '_infer_default': _ORIGINAL_INFER_DEFAULT,
200
+ }
201
+
202
+ def _callable_guard_spec(frame):
203
+
204
+ instructions = list(dis.get_instructions(frame.f_code))
205
+ tgt = _find_target_instr(instructions, frame.f_lasti)
206
+ if tgt is None:
207
+ return None
208
+ idx = instructions.index(tgt)
209
+
210
+ call_idx = None
211
+ for i in range(max(0, idx - 1), min(idx + 3, len(instructions))):
212
+ if instructions[i].opname == 'CALL':
213
+ call_idx = i
214
+ break
215
+ if call_idx is None:
216
+ call_idx = idx
217
+
218
+ var_name = None
219
+ for i in range(call_idx - 1, -1, -1):
220
+ instr = instructions[i]
221
+ if instr.opname == 'LOAD_GLOBAL':
222
+ name = instr.argval
223
+
224
+ val = frame.f_globals.get(name)
225
+ if val is None and name not in frame.f_builtins:
226
+ var_name = name
227
+ break
228
+ if instr.opname in ('LOAD_FAST', 'LOAD_DEREF'):
229
+ name = frame.f_code.co_varnames[instr.arg]
230
+ if frame.f_locals.get(name) is None:
231
+ var_name = name
232
+ break
233
+ if var_name is None:
234
+ return None
235
+
236
+ replacement = _KNOWN_CALLABLES.get(var_name, lambda *a, **kw: 0)
237
+ return PatchSpec('callable_guard', var_name, default_value=replacement)
238
+
239
+ def _null_guard_spec(frame):
240
+ instructions = list(dis.get_instructions(frame.f_code))
241
+ tgt = _find_target_instr(instructions, frame.f_lasti)
242
+ if tgt is None:
243
+ return None
244
+
245
+ if tgt.opname not in ('LOAD_ATTR', 'STORE_ATTR', 'DELETE_ATTR', 'LOAD_METHOD'):
246
+ return None
247
+
248
+ idx = instructions.index(tgt)
249
+
250
+ chain_instrs = []
251
+ if tgt.opname in ('LOAD_ATTR', 'LOAD_METHOD'):
252
+ chain_instrs.append(tgt)
253
+ curr = idx - 1
254
+ while curr >= 0:
255
+ instr = instructions[curr]
256
+ if instr.opname in ('LOAD_ATTR', 'LOAD_METHOD', 'LOAD_FAST', 'LOAD_GLOBAL', 'LOAD_DEREF'):
257
+ chain_instrs.insert(0, instr)
258
+ if instr.opname in ('LOAD_FAST', 'LOAD_GLOBAL', 'LOAD_DEREF'):
259
+ break
260
+ curr -= 1
261
+ else:
262
+ break
263
+
264
+ if not chain_instrs:
265
+
266
+ for i in range(idx, -1, -1):
267
+ if instructions[i].opname in ('LOAD_FAST', 'LOAD_DEREF'):
268
+ var_name = frame.f_code.co_varnames[instructions[i].arg]
269
+ attr_name = tgt.argval if tgt.opname in ('LOAD_ATTR', 'LOAD_METHOD') else None
270
+ return PatchSpec('null_guard', var_name, _infer_attribute_default(attr_name, var_name, instructions, idx))
271
+ return None
272
+
273
+ obj = None
274
+ root_instr = chain_instrs[0]
275
+ if root_instr.opname == 'LOAD_FAST':
276
+ root_name = frame.f_code.co_varnames[root_instr.arg]
277
+ obj = frame.f_locals.get(root_name)
278
+ parent_local = root_name
279
+ elif root_instr.opname == 'LOAD_GLOBAL':
280
+ root_name = root_instr.argval
281
+ obj = frame.f_globals.get(root_name, frame.f_builtins.get(root_name))
282
+ parent_local = root_name
283
+ elif root_instr.opname == 'LOAD_DEREF':
284
+ names = frame.f_code.co_freevars + frame.f_code.co_cellvars
285
+ root_name = names[root_instr.arg] if root_instr.arg < len(names) else f"unknown_{root_instr.arg}"
286
+ obj = frame.f_locals.get(root_name)
287
+ parent_local = root_name
288
+ else:
289
+ return None
290
+
291
+ if obj is None:
292
+ if len(chain_instrs) > 1:
293
+ attr_chain = [chain_instrs[j].argval for j in range(1, len(chain_instrs))]
294
+ attr_name = chain_instrs[-1].argval
295
+ default = _infer_attribute_default(attr_name, parent_local, instructions, idx)
296
+ return PatchSpec('null_guard', parent_local, default, key_name=tuple(attr_chain))
297
+ else:
298
+ attr_name = tgt.argval if tgt.opname in ('LOAD_ATTR', 'LOAD_METHOD') else None
299
+ return PatchSpec('null_guard', parent_local, _infer_attribute_default(attr_name, parent_local, instructions, idx), key_name=(attr_name,) if attr_name else None)
300
+
301
+ for i in range(1, len(chain_instrs)):
302
+ instr = chain_instrs[i]
303
+ attr = instr.argval
304
+ try:
305
+ next_obj = getattr(obj, attr)
306
+ except AttributeError:
307
+ break
308
+
309
+ if next_obj is None:
310
+
311
+ prop = getattr(type(obj), attr, None)
312
+ if isinstance(prop, property) and prop.fget is not None:
313
+ attr_chain = [chain_instrs[j].argval for j in range(1, i + 1)]
314
+ attr_name = chain_instrs[i+1].argval if i + 1 < len(chain_instrs) else (tgt.argval if tgt.opname in ('LOAD_ATTR', 'LOAD_METHOD') else None)
315
+ default = _infer_attribute_default(attr_name, attr, instructions, idx)
316
+ return PatchSpec('return_guard', parent_local, default, key_name=tuple(attr_chain), target_func=prop.fget, target_name=f"{type(obj).__name__}.{attr}")
317
+
318
+ attr_chain = [chain_instrs[j].argval for j in range(1, i + 1)]
319
+ attr_name = chain_instrs[i+1].argval if i + 1 < len(chain_instrs) else (tgt.argval if tgt.opname in ('LOAD_ATTR', 'LOAD_METHOD') else None)
320
+ default = _infer_attribute_default(attr_name, attr, instructions, idx)
321
+ return PatchSpec('null_guard', parent_local, default, key_name=tuple(attr_chain))
322
+
323
+ obj = next_obj
324
+
325
+ attr_name = tgt.argval if tgt.opname in ('LOAD_ATTR', 'LOAD_METHOD') else None
326
+ return PatchSpec('null_guard', parent_local, _infer_attribute_default(attr_name, parent_local, instructions, idx))
327
+
328
+ def _division_guard_spec(frame):
329
+ instructions = list(dis.get_instructions(frame.f_code))
330
+ tgt = _find_target_instr(instructions, frame.f_lasti)
331
+ if tgt is None:
332
+ return None
333
+ idx = instructions.index(tgt)
334
+ if tgt.opname == 'BINARY_OP':
335
+ if idx > 0 and instructions[idx-1].opname in ('LOAD_FAST', 'LOAD_DEREF'):
336
+ var_name = frame.f_code.co_varnames[instructions[idx-1].arg]
337
+ return PatchSpec(strategy='division_guard', var_name=var_name, default_value=1)
338
+ if tgt.opname in ('BINARY_TRUE_DIVIDE', 'BINARY_FLOOR_DIVIDE', 'BINARY_MODULO'):
339
+ if idx > 0 and instructions[idx-1].opname in ('LOAD_FAST', 'LOAD_DEREF'):
340
+ var_name = frame.f_code.co_varnames[instructions[idx-1].arg]
341
+ return PatchSpec(strategy='division_guard', var_name=var_name, default_value=1)
342
+ return None
343
+
344
+ def _none_subscript_spec(frame):
345
+ instructions = list(dis.get_instructions(frame.f_code))
346
+ tgt = _find_target_instr(instructions, frame.f_lasti)
347
+ if tgt is None or tgt.opname not in ('BINARY_SUBSCR', 'STORE_SUBSCR'):
348
+ return None
349
+ idx = instructions.index(tgt)
350
+
351
+ chain_spec = _try_chain_subscript(frame, instructions, idx)
352
+ if chain_spec is not None:
353
+ return chain_spec
354
+
355
+ container_var = None
356
+ key_const = None
357
+ key_var = None
358
+
359
+ for i in range(idx-1, -1, -1):
360
+ instr = instructions[i]
361
+ if instr.opname in ('LOAD_FAST', 'LOAD_DEREF'):
362
+ if container_var is None:
363
+ container_var = frame.f_code.co_varnames[instr.arg]
364
+ elif key_var is None and key_const is None:
365
+ key_var = frame.f_code.co_varnames[instr.arg]
366
+ break
367
+ else:
368
+ break
369
+ elif instr.opname == 'LOAD_CONST':
370
+ if key_const is None and key_var is None:
371
+ key_const = frame.f_code.co_consts[instr.arg]
372
+
373
+ if container_var is None:
374
+ return None
375
+ final_key = key_const if key_const is not None else key_var
376
+ if final_key is None:
377
+ return None
378
+
379
+ spec = PatchSpec('subscript_guard', var_name=container_var,
380
+ default_value=_infer_default(final_key if isinstance(final_key, str) else '', instructions, idx),
381
+ key_name=final_key)
382
+
383
+ prop_origin = _check_property_origin(frame, instructions, idx)
384
+ if prop_origin:
385
+
386
+ return PatchSpec('return_guard', container_var, {}, key_name=spec.key_name, target_func=prop_origin['target_func'], target_name=prop_origin['target_name'])
387
+
388
+ return spec
389
+
390
+ def _try_chain_subscript(frame, instructions, failing_idx):
391
+
392
+ keys_back = []
393
+
394
+ key_idx = failing_idx - 1
395
+ if key_idx < 0:
396
+ return None
397
+ key_instr = instructions[key_idx]
398
+ if key_instr.opname == 'LOAD_CONST':
399
+ keys_back.append(frame.f_code.co_consts[key_instr.arg])
400
+ else:
401
+
402
+ return None
403
+
404
+ pos = key_idx - 1
405
+ root_var = None
406
+ while pos >= 0:
407
+ instr = instructions[pos]
408
+ if instr.opname == 'BINARY_SUBSCR':
409
+ pos -= 1
410
+ if pos < 0:
411
+ return None
412
+ k_instr = instructions[pos]
413
+ if k_instr.opname == 'LOAD_CONST':
414
+ keys_back.append(frame.f_code.co_consts[k_instr.arg])
415
+ else:
416
+
417
+ return None
418
+ pos -= 1
419
+ elif instr.opname in ('LOAD_FAST', 'LOAD_DEREF'):
420
+ root_var = frame.f_code.co_varnames[instr.arg]
421
+ break
422
+ else:
423
+ return None
424
+
425
+ if root_var is None:
426
+ return None
427
+
428
+ keys_back.reverse()
429
+
430
+ keys_fwd = []
431
+ fwd = failing_idx + 1
432
+ while fwd + 1 < len(instructions):
433
+ if (instructions[fwd].opname == 'LOAD_CONST' and
434
+ instructions[fwd + 1].opname == 'BINARY_SUBSCR'):
435
+ keys_fwd.append(frame.f_code.co_consts[instructions[fwd].arg])
436
+ fwd += 2
437
+ else:
438
+ break
439
+
440
+ all_keys = keys_back + keys_fwd
441
+
442
+ if len(all_keys) < 2:
443
+ return None
444
+
445
+ last_instr_idx = failing_idx + len(keys_fwd) * 2
446
+ final_key = all_keys[-1]
447
+ default = _infer_default(final_key if isinstance(final_key, str) else '', instructions, last_instr_idx)
448
+
449
+ return PatchSpec('chain_subscript_guard', var_name=root_var,
450
+ default_value=default, key_name=tuple(all_keys))
451
+
452
+ def _index_bound_spec(frame):
453
+ instructions = list(dis.get_instructions(frame.f_code))
454
+ tgt = _find_target_instr(instructions, frame.f_lasti)
455
+ if tgt is None or tgt.opname != 'BINARY_SUBSCR':
456
+ return None
457
+ idx_instr = instructions.index(tgt)
458
+ loads = []
459
+ for i in range(idx_instr-1, -1, -1):
460
+ if instructions[i].opname in ('LOAD_FAST', 'LOAD_DEREF'):
461
+ loads.append(frame.f_code.co_varnames[instructions[i].arg])
462
+ if len(loads) == 2:
463
+ break
464
+ if len(loads) < 2:
465
+ return None
466
+ list_var, idx_var = loads[1], loads[0]
467
+ return PatchSpec('index_guard', idx_var, 0, list_len_var=list_var)
468
+
469
+ def _dict_get_spec(frame, msg):
470
+ match = re.search(r"KeyError\: '(\w+)'", msg)
471
+ if not match:
472
+ match = re.search(r"'(\w+)'", msg)
473
+ if not match:
474
+ return None
475
+ key = match.group(1)
476
+ instructions = list(dis.get_instructions(frame.f_code))
477
+ tgt = _find_target_instr(instructions, frame.f_lasti)
478
+ if tgt is None or tgt.opname != 'BINARY_SUBSCR':
479
+ return None
480
+ idx = instructions.index(tgt)
481
+ for i in range(idx-1, -1, -1):
482
+ if instructions[i].opname in ('LOAD_FAST', 'LOAD_DEREF'):
483
+ dict_var = frame.f_code.co_varnames[instructions[i].arg]
484
+ return PatchSpec('key_guard', dict_var, _infer_default(key, instructions, idx), key_name=key)
485
+ return None
486
+
487
+ def _str_concat_spec(frame, msg):
488
+ instructions = list(dis.get_instructions(frame.f_code))
489
+ tgt = _find_target_instr(instructions, frame.f_lasti)
490
+ if tgt and (tgt.opname == 'BINARY_ADD' or (tgt.opname == 'BINARY_OP' and tgt.arg == 0)):
491
+ idx = instructions.index(tgt)
492
+ if idx > 0 and instructions[idx-1].opname in ('LOAD_FAST', 'LOAD_DEREF'):
493
+ var = frame.f_code.co_varnames[instructions[idx-1].arg]
494
+ return PatchSpec('str_coerce_guard', var, "")
495
+ return None
496
+
497
+ def _file_guard_spec(frame):
498
+ instructions = list(dis.get_instructions(frame.f_code))
499
+ tgt = _find_target_instr(instructions, frame.f_lasti)
500
+ if tgt is None:
501
+ return None
502
+ idx = instructions.index(tgt)
503
+ for i in range(idx-1, -1, -1):
504
+ if instructions[i].opname in ('LOAD_FAST', 'LOAD_DEREF'):
505
+ var = frame.f_code.co_varnames[instructions[i].arg]
506
+ return PatchSpec('file_guard', var, "")
507
+ return None
508
+
509
+ def _type_coercion_spec(frame, msg):
510
+
511
+ instructions = list(dis.get_instructions(frame.f_code))
512
+ tgt = _find_target_instr(instructions, frame.f_lasti)
513
+ if tgt is None:
514
+ return None
515
+ idx = instructions.index(tgt)
516
+
517
+ call_idx = None
518
+ for i in range(max(0, idx - 1), min(idx + 3, len(instructions))):
519
+ if instructions[i].opname == 'CALL':
520
+ call_idx = i
521
+ break
522
+ if call_idx is None:
523
+ call_idx = idx
524
+
525
+ var_name = None
526
+ for i in range(call_idx - 1, -1, -1):
527
+ instr = instructions[i]
528
+ if instr.opname in ('LOAD_FAST', 'LOAD_DEREF'):
529
+ var_name = frame.f_code.co_varnames[instr.arg]
530
+ break
531
+ if instr.opname == 'LOAD_GLOBAL':
532
+ continue
533
+ if instr.opname in ('PRECALL', 'PUSH_NULL'):
534
+ continue
535
+ break
536
+
537
+ if var_name is None:
538
+ return None
539
+
540
+ default = 0
541
+ for i in range(call_idx - 1, -1, -1):
542
+ instr = instructions[i]
543
+ if instr.opname == 'LOAD_GLOBAL':
544
+ name = instr.argval
545
+ if isinstance(name, tuple):
546
+ name = name[1] if len(name) > 1 else name[0]
547
+ if name == 'int':
548
+ default = 0
549
+ elif name == 'float':
550
+ default = 0.0
551
+ elif name == 'str':
552
+ default = ""
553
+ break
554
+
555
+ return PatchSpec('type_coercion_guard', var_name, default)