File size: 5,235 Bytes
38b27cd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | """
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:
# 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("<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__":
# 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) |