| """ |
| 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="<input>", reporter=reporter) |
| except Exception: |
| |
| |
| 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("<input>:", "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__": |
| |
| examples = [ |
| "def add(a, b)\n return a + b", |
| "def add(a, b):\n return a + c", |
| "import os\ndef add(a, b):\n return a + b", |
| "def add(a, b):\n return a + b", |
| ] |
| for code in examples: |
| print("CODE:\n", code) |
| print("RESULT:", detect_bugs(code)) |
| print("-" * 60) |