""" Bug checker — v2, using real static analysis instead of a heuristic. Why the old approach was wrong: The previous version fed code into a base (non-fine-tuned) CodeT5 model and flagged "bug" whenever its generated output differed from the input. That's not bug detection -- an untrained generative model will almost always produce *some* different text, so it was flagging correct code constantly. What this version does instead: 1. Syntax check via Python's own `ast.parse` -- catches things like missing colons, unmatched brackets, bad indentation. 100% accurate, since it's Python's real parser, not a guess. 2. Static analysis via `pyflakes` -- catches real bugs: undefined names, unused imports/variables, redefined functions, unreachable code, etc. This is the same engine that powers linters in VS Code / PyCharm. 3. (Optional) The fine-tuned seq2seq model from train.py, if you've trained one, is used ONLY to suggest a fix once a real issue has already been found by steps 1-2 -- not to decide whether a bug exists in the first place. Install: pip install pyflakes """ import ast import io from pyflakes.api import check as pyflakes_check from pyflakes.reporter import Reporter import pycodestyle class _ListReport(pycodestyle.BaseReport): """Collects pycodestyle warnings into a list instead of printing them.""" def __init__(self, options): super().__init__(options) self.results = [] def error(self, line_number, offset, text, check): code = super().error(line_number, offset, text, check) if code: self.results.append(f"line {line_number}, col {offset + 1}: {text}") return code def check_style(code: str): """ Runs pycodestyle (PEP8) checks. Returns a list of style warning strings, e.g.: ["line 2, col 80: E501 line too long (85 > 79 characters)"] Empty list means no style issues found. This is SEPARATE from real bugs -- style issues don't break the code, they just deviate from PEP8 conventions (naming, spacing, line length). """ lines = code.splitlines(keepends=True) guide = pycodestyle.StyleGuide(reporter=_ListReport) report = guide.init_report() checker = pycodestyle.Checker(lines=lines, options=guide.options, report=report) try: checker.check_all() except Exception: return ["Style check could not process this code."] return report.results def check_syntax(code: str): """Returns (is_valid: bool, error_message: str or None)""" try: ast.parse(code) return True, None except SyntaxError as e: return False, f"Syntax error at line {e.lineno}: {e.msg}" def check_static_issues(code: str): """ Runs pyflakes static analysis. Returns a list of issue strings, e.g.: ["line 3: undefined name 'x'", "line 1: 'os' imported but unused"] Empty list means no issues found. """ out_stream = io.StringIO() err_stream = io.StringIO() reporter = Reporter(out_stream, err_stream) try: pyflakes_check(code, filename="", reporter=reporter) except Exception: # pyflakes itself can't handle the code (e.g. severe syntax issues) -- # the syntax check above should catch these first, so this is a safety net. return ["Static analysis could not process this code."] issues = [] for stream in (out_stream, err_stream): text = stream.getvalue().strip() if text: issues.extend(line.replace(":", "line ") for line in text.split("\n")) return issues def detect_bugs(code: str): """ Main entry point. Returns: { "has_bug": bool, "syntax_valid": bool, "syntax_error": str or None, "static_issues": list[str], "summary": str -- human-readable summary } """ syntax_valid, syntax_error = check_syntax(code) if not syntax_valid: return { "has_bug": True, "syntax_valid": False, "syntax_error": syntax_error, "static_issues": [], "summary": f"Syntax error found: {syntax_error}", } static_issues = check_static_issues(code) has_bug = len(static_issues) > 0 if has_bug: summary = f"{len(static_issues)} issue(s) found via static analysis." else: summary = "No issues found by static analysis (syntax is valid, no undefined names, no unused imports/variables detected)." return { "has_bug": has_bug, "syntax_valid": True, "syntax_error": None, "static_issues": static_issues, "summary": summary, } if __name__ == "__main__": # Test cases examples = [ "def add(a, b)\n return a + b", # syntax error (missing colon) "def add(a, b):\n return a + c", # undefined name 'c' "import os\ndef add(a, b):\n return a + b", # unused import "def add(a, b):\n return a + b", # actually fine ] for code in examples: print("CODE:\n", code) print("RESULT:", detect_bugs(code)) print("-" * 60)