| from typing import Dict |
| import ast |
|
|
|
|
| UNSAFE_FUNCTIONS = {"eval", "exec", "compile", "__import__"} |
| MIN_TASK_SCORE = 0.01 |
| MAX_TASK_SCORE = 0.99 |
|
|
|
|
| def get_task() -> Dict: |
| return { |
| "id": "medium", |
| "difficulty": "medium", |
| "name": "vuln_patch", |
| "objective": "Remove unsafe calls while preserving function structure.", |
| "description": "Remove unsafe function usage while preserving functionality.", |
| "grader_name": "grade", |
| "score_range": [MIN_TASK_SCORE, MAX_TASK_SCORE], |
| } |
|
|
|
|
| class UnsafeCallVisitor(ast.NodeVisitor): |
| def __init__(self) -> None: |
| self.unsafe_calls = 0 |
|
|
| def visit_Call(self, node: ast.Call) -> None: |
| if isinstance(node.func, ast.Name): |
| if node.func.id in UNSAFE_FUNCTIONS: |
| self.unsafe_calls += 1 |
| self.generic_visit(node) |
|
|
|
|
| def _has_function_def(tree: ast.AST) -> bool: |
| return any(isinstance(node, ast.FunctionDef) for node in ast.walk(tree)) |
|
|
|
|
| def grade(candidate_code: str) -> float: |
| """ |
| Score breakdown: |
| - No unsafe calls → +0.7 |
| - Function structure preserved → +0.3 |
| """ |
| try: |
| tree = ast.parse(candidate_code) |
| except SyntaxError: |
| return MIN_TASK_SCORE |
|
|
| visitor = UnsafeCallVisitor() |
| visitor.visit(tree) |
|
|
| score = 0.0 |
|
|
| if visitor.unsafe_calls == 0: |
| score += 0.7 |
|
|
| if _has_function_def(tree): |
| score += 0.3 |
|
|
| score = round(score, 2) |
| score = max(MIN_TASK_SCORE, min(MAX_TASK_SCORE, score)) |
| return score |
|
|