File size: 1,528 Bytes
a5c9fd4
 
 
 
 
50a0917
 
a5c9fd4
 
 
 
b56681c
 
a5c9fd4
b56681c
a5c9fd4
b56681c
4a63c86
a5c9fd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50a0917
a5c9fd4
 
 
 
 
 
 
 
 
 
 
 
50a0917
 
 
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
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