@@ -2202,6 +2202,73 @@ def _has_ver(expr: ast.AST | None) -> bool:
22022202 continue
22032203 if not is_entrypoint_name (node .name ):
22042204 continue
2205+ params = {arg .arg for arg in node .args .args }
2206+
2207+ default_state = {
2208+ arg .arg
2209+ for arg , default in zip (node .args .args [- len (node .args .defaults ):], node .args .defaults )
2210+ if isinstance (default , (ast .List , ast .Tuple )) and len (default .elts ) >= 2
2211+ }
2212+
2213+ for child in ast .walk (node ):
2214+ if not isinstance (child , ast .If ):
2215+ continue
2216+ if _body_has_calls (child .body ):
2217+ continue
2218+
2219+ replay_state = None
2220+ for compare in ast .walk (child .test ):
2221+ if not isinstance (compare , ast .Compare ):
2222+ continue
2223+ operands = [compare .left ] + list (compare .comparators )
2224+ for i , op in enumerate (compare .ops ):
2225+ if not isinstance (op , ast .Is ):
2226+ continue
2227+ left = operands [i ]
2228+ right = operands [i + 1 ]
2229+ left_root = _ast_root_name (left )
2230+ right_root = _ast_root_name (right )
2231+ if left_root in default_state and right_root in params :
2232+ replay_state = left_root
2233+ elif right_root in default_state and left_root in params :
2234+ replay_state = right_root
2235+ if replay_state is None :
2236+ continue
2237+
2238+ returns_default_slot = any (
2239+ isinstance (stmt , ast .Return )
2240+ and _ast_root_name (stmt .value ) == replay_state
2241+ for stmt in child .body
2242+ )
2243+ if not returns_default_slot :
2244+ continue
2245+
2246+ writes_input_slot = False
2247+ writes_output_slot = False
2248+ for stmt in ast .walk (node ):
2249+ if not isinstance (stmt , ast .Assign ):
2250+ continue
2251+ targets = stmt .targets
2252+ values = [stmt .value ] * len (targets )
2253+ if len (targets ) == 1 and isinstance (targets [0 ], ast .Tuple ) and isinstance (stmt .value , ast .Tuple ):
2254+ targets = list (targets [0 ].elts )
2255+ values = list (stmt .value .elts )
2256+ for target , value in zip (targets , values ):
2257+ if _ast_root_name (target ) != replay_state :
2258+ continue
2259+ if isinstance (value , ast .Name ) and value .id in params :
2260+ writes_input_slot = True
2261+ elif _expr_names (value ) & params :
2262+ writes_output_slot = True
2263+ if writes_input_slot and writes_output_slot :
2264+ return [{
2265+ "pattern" : "LAST_CALL_REPLAY" ,
2266+ "severity" : "critical" ,
2267+ "evidence" : (
2268+ f"{ entrypoint_name } replays from mutable default state "
2269+ "after input identity match"
2270+ ),
2271+ }]
22052272
22062273 signature_features : dict [str , set [str ]] = defaultdict (set )
22072274 saved_state_features : dict [str , set [str ]] = defaultdict (set )
0 commit comments