codedebugger / agents /critic.py
psdhanushkumar's picture
Upload folder using huggingface_hub
f361447 verified
Raw
History Blame Contribute Delete
5.02 kB
"""
agents/critic.py - Anti-hacking checks for CodeDebugger.
"""
import re
# Common language constants that are legitimate return values, not cheating
EXCLUDED_CONSTANTS = {
'True', 'False', 'None', '0', '1', '-1',
'[]', '{}', '', 'true', 'false', 'null',
}
def check_hardcoded_outputs(code: str, test_cases: list) -> dict:
"""
Detect if model hardcodes expected outputs.
Check if any expected value from test_cases appears as a
literal return value in the code (e.g. "return 3" when
expected is "3").
Excludes common language constants (True, False, None, 0, 1, etc.)
and requires at least 2 matches before triggering penalty.
Returns: {"passed": bool, "penalty": float, "message": str}
Penalty if caught: -30
"""
matches = []
for tc in test_cases:
expected = str(tc.get("expected", "")).strip()
if not expected:
continue
# Skip common language constants — these are legitimate return values
if expected in EXCLUDED_CONSTANTS:
continue
# Escape for regex to avoid syntax issues
escaped_exp = re.escape(expected)
# Match 'return <expected>', 'return "<expected>"', or 'return \'<expected>\''
pattern = rf"return\s+({escaped_exp}|'{escaped_exp}'|\"{escaped_exp}\")"
if re.search(pattern, code):
matches.append(expected)
# Require at least 2 hardcoded matches to trigger — a single coincidence is not cheating
if len(matches) >= 2:
return {"passed": False, "penalty": -30.0, "message": f"Hardcoded returns detected for values: {', '.join(matches)}"}
return {"passed": True, "penalty": 0.0, "message": "OK"}
def check_function_deletion(original_code: str, submitted_code: str) -> dict:
"""
Detect if function signature was removed or changed.
Extract function name from original using regex.
Check if same function name exists in submitted code.
Returns: {"passed": bool, "penalty": float, "message": str}
Penalty if caught: -25
"""
match = re.search(r"def\s+([a-zA-Z0-9_]+)\s*\(", original_code)
if match:
func_name = match.group(1)
if not re.search(rf"def\s+{func_name}\s*\(", submitted_code):
return {"passed": False, "penalty": -25.0, "message": f"Function '{func_name}' was removed or renamed."}
return {"passed": True, "penalty": 0.0, "message": "OK"}
def check_trivial_pass(test_results: dict, execution_time: float) -> dict:
"""
Detect suspicious: all tests pass but execution < 0.01s.
If pass_rate == 1.0 AND execution_time_total < 0.01: suspicious.
Returns: {"passed": bool, "penalty": float, "message": str}
Penalty if caught: -20
"""
pass_rate = test_results.get("pass_rate", 0.0)
exec_time = test_results.get("execution_time_total", execution_time)
if pass_rate == 1.0 and exec_time < 0.01:
return {"passed": False, "penalty": -20.0, "message": "Suspiciously fast execution for all passing tests."}
return {"passed": True, "penalty": 0.0, "message": "OK"}
def check_code_gutting(original_code: str, submitted_code: str) -> dict:
"""
Detect if model deleted most of the code.
If len(submitted) < 0.3 * len(original): gutted.
If submitted is just "pass" or "return None": gutted.
Returns: {"passed": bool, "penalty": float, "message": str}
Penalty if caught: -35
"""
sub_strip = submitted_code.strip()
# Check for empty or trivial single-line bodies
if sub_strip in ["pass", "return None", "", "return"]:
return {"passed": False, "penalty": -35.0, "message": "Code is trivially empty or just 'pass'/'return None'."}
if len(submitted_code) < 0.3 * len(original_code):
return {"passed": False, "penalty": -35.0, "message": "Code length is < 30% of original."}
return {"passed": True, "penalty": 0.0, "message": "OK"}
def run_all_anti_hack_checks(code, original_code, test_cases, test_results, execution_time) -> dict:
"""
Run all 4 checks.
Returns: {
"all_passed": bool,
"total_penalty": float,
"checks": {
"hardcoded": result,
"deletion": result,
"trivial": result,
"gutting": result
}
}
"""
r_hardcoded = check_hardcoded_outputs(code, test_cases)
r_deletion = check_function_deletion(original_code, code)
r_trivial = check_trivial_pass(test_results, execution_time)
r_gutting = check_code_gutting(original_code, code)
checks = {
"hardcoded": r_hardcoded,
"deletion": r_deletion,
"trivial": r_trivial,
"gutting": r_gutting
}
all_passed = all(c["passed"] for c in checks.values())
total_penalty = sum(c["penalty"] for c in checks.values())
return {
"all_passed": all_passed,
"total_penalty": float(total_penalty),
"checks": checks
}