"""AST-level concurrency issue injection for Java using javalang.""" from typing import Optional import javalang from app.dataset.rules.base import Mutation, MutationRule class JavaConcurrencyRule(MutationRule): """Remove a synchronized block to expose a race condition. Uses `javalang` to locate `synchronized (lock) { ... }` and replaces it with the bare block body. """ name = "java_concurrency" language = "java" defect_type = "concurrency_issue" def detect_and_mutate(self, source: str, filename: str = "") -> Optional[Mutation]: try: tree = javalang.parse.parse(source) except Exception: return None for path, node in tree: if not isinstance(node, javalang.tree.SynchronizedStatement): continue pos = node.position if not pos: continue lines = source.splitlines(keepends=True) start = pos.line # Estimate end by braces of the synchronized block end = start depth = 0 for idx in range(start - 1, len(lines)): depth += lines[idx].count("{") - lines[idx].count("}") if depth > 0: end = idx + 1 if depth <= 0 and idx > start - 1: end = idx + 1 break body_lines = lines[start - 1:end] # drop header line and closing brace line, keep body; body is at same # indentation as the synchronized header minus one level inner = body_lines[1:-1] if len(body_lines) > 2 else [] dedented = [] for line in inner: if line.startswith(" "): dedented.append(" " + line[12:]) elif line.startswith(" "): dedented.append(" " + line[8:]) elif line.startswith(" "): dedented.append(line[4:]) else: dedented.append(line) mutated = "".join(lines[: start - 1] + dedented + lines[end:]) return Mutation( defect_type=self.defect_type, language=self.language, line_start=start, line_end=end, mutated_source=mutated, reference_fix="Restore `synchronized (lock)` to protect the critical section.", description="Removed synchronized block, exposing a race condition.", ) return None