"""AST-level resource leak injection for JavaScript using esprima.""" from typing import Optional import esprima from app.dataset.rules.base import Mutation, MutationRule class JSResourceLeakRule(MutationRule): """Remove a fetch Response body close/usage, leaking the reader. Uses `esprima` to locate a `try/finally` that closes a reader and removes the finally block. """ name = "js_resource_leak" language = "javascript" defect_type = "resource_not_closed" def detect_and_mutate(self, source: str, filename: str = "") -> Optional[Mutation]: # Modern JS is usually ESM: try module grammar first, then script. try: tree = esprima.parseModule(source, loc=True) except Exception: try: tree = esprima.parseScript(source, loc=True) except Exception: return None def walk(node): yield node for key in node.__dict__: child = getattr(node, key) if isinstance(child, list): for item in child: if hasattr(item, "type"): yield from walk(item) elif hasattr(child, "type"): yield from walk(child) for node in walk(tree): if node.type != "TryStatement" or not node.finalizer: continue start = node.loc.start.line end = node.finalizer.loc.end.line lines = source.splitlines(keepends=True) # Drop the finally block entirely, close the try block finally_start = node.finalizer.loc.start.line - 1 mutated = "".join(lines[:finally_start] + [" }\n"] + lines[end:]) return Mutation( defect_type=self.defect_type, language=self.language, line_start=start, line_end=end, mutated_source=mutated, reference_fix="Restore finally block to close/release resources.", description="Removed finally block, leaving resource unreleased.", ) return None