File size: 1,245 Bytes
e298389
 
 
 
 
 
 
 
 
 
 
b56681c
 
e298389
b56681c
e298389
b56681c
e298389
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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) and 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:
	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)
	return max(MIN_TASK_SCORE, min(MAX_TASK_SCORE, score))