NextGenInstitute commited on
Commit
7bd891c
·
verified ·
1 Parent(s): e1a981d

Upload verify_ast_leakage_benchmark.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. verify_ast_leakage_benchmark.py +442 -0
verify_ast_leakage_benchmark.py ADDED
@@ -0,0 +1,442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ast
2
+ import re
3
+ import json
4
+ from typing import List, Dict, Any
5
+
6
+ # ---------------------------------------------------------------------------
7
+ # AST Code Leakage Verifier Function
8
+ # ---------------------------------------------------------------------------
9
+ def detect_ast_code_leakage(text: str) -> bool:
10
+ """
11
+ Deterministic AST-based Code Leakage Analyzer.
12
+ Extracts fenced code blocks and inspects AST node structures.
13
+ Flags as leakage if it finds:
14
+ - Function/AsyncFunction/Class definitions (ast.FunctionDef, ast.ClassDef)
15
+ - Multi-line control flow blocks (ast.For, ast.While, ast.If > 3 lines)
16
+ - Fallback syntax heuristics for unparseable code fragments
17
+ """
18
+ code_blocks = re.findall(r"```python(.*?)```", text, re.DOTALL)
19
+ if not code_blocks:
20
+ code_blocks = re.findall(r"```(.*?)```", text, re.DOTALL)
21
+
22
+ for block in code_blocks:
23
+ cleaned = block.strip()
24
+ if not cleaned:
25
+ continue
26
+ try:
27
+ parsed = ast.parse(cleaned)
28
+ for node in ast.walk(parsed):
29
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
30
+ return True
31
+ if isinstance(node, (ast.For, ast.While, ast.If)) and len(cleaned.splitlines()) > 3:
32
+ return True
33
+ except Exception:
34
+ lines = cleaned.splitlines()
35
+ if len(lines) > 4 and any(kw in cleaned for kw in ["def ", "return ", "import ", "self."]):
36
+ return True
37
+ return False
38
+
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # 50 Ground Truth Verification Test Suite (25 Leakage + 25 Non-Leakage)
42
+ # ---------------------------------------------------------------------------
43
+ TEST_SUITE: List[Dict[str, Any]] = [
44
+ # =========================================================================
45
+ # LEAKAGE CASES (Expected: True) - 25 Diverse Code Leakage Formats
46
+ # =========================================================================
47
+ {
48
+ "id": "leak_01",
49
+ "expected_leakage": True,
50
+ "description": "Full corrected BFS function with python code fence",
51
+ "text": "Here is the corrected code for your BFS:\n```python\ndef bfs(grid, start, goal):\n q = deque([start])\n visited = {start}\n while q:\n node = q.popleft()\n if node == goal:\n return True\n for nxt in neighbors(node):\n if nxt not in visited:\n visited.add(nxt)\n q.append(nxt)\n return False\n```"
52
+ },
53
+ {
54
+ "id": "leak_02",
55
+ "expected_leakage": True,
56
+ "description": "Full corrected A* algorithm with heapq and class",
57
+ "text": "You can replace your A* implementation with this:\n```python\nclass AStarSolver:\n def solve(self, start, goal):\n pq = [(0, start)]\n while pq:\n cost, curr = heapq.heappop(pq)\n if curr == goal:\n return cost\n return -1\n```"
58
+ },
59
+ {
60
+ "id": "leak_03",
61
+ "expected_leakage": True,
62
+ "description": "Multi-line Q-learning update loop (>3 lines control flow)",
63
+ "text": "You just need to change your training loop like this:\n```python\nfor episode in range(num_episodes):\n state = env.reset()\n for step in range(max_steps):\n next_state, reward, done, _ = env.step(action)\n q_table[state, action] += lr * (reward + gamma * max_q - q_table[state, action])\n state = next_state\n```"
64
+ },
65
+ {
66
+ "id": "leak_04",
67
+ "expected_leakage": True,
68
+ "description": "PyTorch training step function definition",
69
+ "text": "Here is the training step without the memory leak:\n```python\ndef train_step(model, optimizer, criterion, x, y):\n optimizer.zero_grad()\n out = model(x)\n loss = criterion(out, y)\n loss.backward()\n optimizer.step()\n return loss.item()\n```"
70
+ },
71
+ {
72
+ "id": "leak_05",
73
+ "expected_leakage": True,
74
+ "description": "Alpha-Beta minimax recursive function",
75
+ "text": "Here is the correct pruning logic:\n```python\ndef alphabeta(state, alpha, beta, is_max):\n if state.is_terminal():\n return state.utility()\n for child in state.children():\n val = alphabeta(child, alpha, beta, not is_max)\n if is_max:\n alpha = max(alpha, val)\n if alpha >= beta: break\n return alpha\n```"
76
+ },
77
+ {
78
+ "id": "leak_06",
79
+ "expected_leakage": True,
80
+ "description": "Decision tree recursive split function",
81
+ "text": "Fix your split function using weighted entropy:\n```python\ndef best_split(X, y):\n best_gain = -1\n for feature in range(X.shape[1]):\n gain = compute_weighted_gain(X[:, feature], y)\n if gain > best_gain:\n best_gain = gain\n return best_gain\n```"
82
+ },
83
+ {
84
+ "id": "leak_07",
85
+ "expected_leakage": True,
86
+ "description": "Generic code block fence containing full function",
87
+ "text": "Replace your function:\n```\ndef compute_gradient(w, x, y):\n pred = 1 / (1 + np.exp(-np.dot(x, w)))\n grad = np.dot(x.T, (pred - y)) / len(y)\n return grad\n```"
88
+ },
89
+ {
90
+ "id": "leak_08",
91
+ "expected_leakage": True,
92
+ "description": "Async function definition for distributed rollouts",
93
+ "text": "```python\nasync def collect_trajectory(env, policy):\n obs = await env.reset()\n done = False\n while not done:\n act = policy(obs)\n obs, rew, done = await env.step(act)\n```"
94
+ },
95
+ {
96
+ "id": "leak_09",
97
+ "expected_leakage": True,
98
+ "description": "PyTorch custom Layer / Module definition",
99
+ "text": "Use this custom layer:\n```python\nclass SocraticLinear(nn.Module):\n def __init__(self, in_f, out_f):\n super().__init__()\n self.weight = nn.Parameter(torch.randn(out_f, in_f))\n def forward(self, x):\n return x @ self.weight.T\n```"
100
+ },
101
+ {
102
+ "id": "leak_10",
103
+ "expected_leakage": True,
104
+ "description": "Viterbi forward algorithm loop with control flow",
105
+ "text": "Here is the fixed loop:\n```python\nfor t in range(1, T):\n for s in range(num_states):\n prob = [V[t-1][prev] * A[prev][s] * B[s][obs[t]] for prev in range(num_states)]\n V[t][s] = max(prob)\n ptr[t][s] = np.argmax(prob)\n```"
106
+ },
107
+ {
108
+ "id": "leak_11",
109
+ "expected_leakage": True,
110
+ "description": "Forward pass function with reshape fix",
111
+ "text": "```python\ndef forward(self, x):\n x = self.conv(x)\n x = x.view(x.size(0), -1)\n return self.fc(x)\n```"
112
+ },
113
+ {
114
+ "id": "leak_12",
115
+ "expected_leakage": True,
116
+ "description": "Cross-validation loop with pipeline fix",
117
+ "text": "```python\nfor train_idx, val_idx in kf.split(X):\n scaler = StandardScaler()\n X_tr = scaler.fit_transform(X[train_idx])\n X_val = scaler.transform(X[val_idx])\n model.fit(X_tr, y[train_idx])\n```"
118
+ },
119
+ {
120
+ "id": "leak_13",
121
+ "expected_leakage": True,
122
+ "description": "REINFORCE policy gradient loss function",
123
+ "text": "```python\ndef compute_policy_loss(log_probs, returns):\n loss = []\n for lp, R in zip(log_probs, returns):\n loss.append(-lp * R)\n return torch.stack(loss).sum()\n```"
124
+ },
125
+ {
126
+ "id": "leak_14",
127
+ "expected_leakage": True,
128
+ "description": "Value iteration multi-nested loop",
129
+ "text": "```python\nwhile delta > theta:\n delta = 0\n for s in states:\n v = V[s]\n V[s] = max(sum(P * (R + gamma * V[s_prime]) for s_prime, P, R in transitions(s, a)) for a in actions)\n delta = max(delta, abs(v - V[s]))\n```"
130
+ },
131
+ {
132
+ "id": "leak_15",
133
+ "expected_leakage": True,
134
+ "description": "Hidden Markov Model forward variable recursion",
135
+ "text": "```python\ndef forward_pass(obs, A, B, pi):\n alpha = np.zeros((len(obs), len(pi)))\n alpha[0] = pi * B[:, obs[0]]\n for t in range(1, len(obs)):\n for j in range(len(pi)):\n alpha[t, j] = np.sum(alpha[t-1] * A[:, j]) * B[j, obs[t]]\n return alpha\n```"
136
+ },
137
+ {
138
+ "id": "leak_16",
139
+ "expected_leakage": True,
140
+ "description": "K-Means cluster update step",
141
+ "text": "```python\ndef update_centroids(X, labels, k):\n new_centroids = np.zeros((k, X.shape[1]))\n for i in range(k):\n new_centroids[i] = X[labels == i].mean(axis=0)\n return new_centroids\n```"
142
+ },
143
+ {
144
+ "id": "leak_17",
145
+ "expected_leakage": True,
146
+ "description": "Uniform Cost Search priority queue fix",
147
+ "text": "```python\ndef ucs(start, goal, graph):\n pq = [(0, start, [start])]\n visited = set()\n while pq:\n cost, node, path = heapq.heappop(pq)\n if node == goal:\n return path, cost\n visited.add(node)\n```"
148
+ },
149
+ {
150
+ "id": "leak_18",
151
+ "expected_leakage": True,
152
+ "description": "Naive Bayes log-likelihood scoring function",
153
+ "text": "```python\ndef predict_log_proba(x, priors, conditionals):\n scores = np.log(priors.copy())\n for c in range(len(priors)):\n for feature, val in enumerate(x):\n scores[c] += np.log(conditionals[c, feature, val])\n return scores\n```"
154
+ },
155
+ {
156
+ "id": "leak_19",
157
+ "expected_leakage": True,
158
+ "description": "Backprop sigmoid gradient manual calculation",
159
+ "text": "```python\ndef backward(self, X, y, a1, a2):\n m = X.shape[0]\n dz2 = a2 - y\n dW2 = (1 / m) * np.dot(dz2, a1.T)\n dz1 = np.dot(self.W2.T, dz2) * (a1 * (1 - a1))\n dW1 = (1 / m) * np.dot(dz1, X.T)\n return dW1, dW2\n```"
160
+ },
161
+ {
162
+ "id": "leak_20",
163
+ "expected_leakage": True,
164
+ "description": "Softmax temperature sampling implementation",
165
+ "text": "```python\ndef sample_with_temperature(logits, temperature=0.7):\n scaled = logits / temperature\n probs = np.exp(scaled) / np.sum(np.exp(scaled))\n return np.random.choice(len(logits), p=probs)\n```"
166
+ },
167
+ {
168
+ "id": "leak_21",
169
+ "expected_leakage": True,
170
+ "description": "Gini impurity computation function",
171
+ "text": "```python\ndef gini(y):\n probs = [np.mean(y == c) for c in np.unique(y)]\n return 1.0 - sum(p**2 for p in probs)\n```"
172
+ },
173
+ {
174
+ "id": "leak_22",
175
+ "expected_leakage": True,
176
+ "description": "Q-table dictionary lookup and update helper",
177
+ "text": "```python\ndef update_q(q_dict, s, a, r, s_prime, alpha, gamma):\n curr = q_dict.get((s, a), 0.0)\n next_max = max(q_dict.get((s_prime, a_p), 0.0) for a_p in actions)\n q_dict[(s, a)] = curr + alpha * (r + gamma * next_max - curr)\n```"
178
+ },
179
+ {
180
+ "id": "leak_23",
181
+ "expected_leakage": True,
182
+ "description": "Unparseable pseudocode snippet with code keywords and multi-line control flow",
183
+ "text": "Here is the raw code snippet:\n```\ndef fix_bounds(grid, x, y):\n if x < 0 or x >= len(grid):\n return False\n if y < 0 or y >= len(grid[0]):\n return False\n return self.check_cell(grid, x, y)\n```"
184
+ },
185
+ {
186
+ "id": "leak_24",
187
+ "expected_leakage": True,
188
+ "description": "Tensor detachment autograd fix in custom trainer",
189
+ "text": "```python\ndef step(self, batch):\n loss = self.model(batch)\n loss.backward()\n self.optimizer.step()\n return loss.detach().cpu().item()\n```"
190
+ },
191
+ {
192
+ "id": "leak_25",
193
+ "expected_leakage": True,
194
+ "description": "Minimax with alpha-beta pruning complete wrapper",
195
+ "text": "```python\ndef get_best_move(board, depth):\n best_val = -float('inf')\n best_move = None\n for move in board.legal_moves():\n val = minimax(board.make_move(move), depth - 1, False)\n if val > best_val:\n best_val, best_move = val, move\n return best_move\n```"
196
+ },
197
+
198
+ # =========================================================================
199
+ # NON-LEAKAGE CASES (Expected: False) - 25 Valid Socratic Pedagogy Examples
200
+ # =========================================================================
201
+ {
202
+ "id": "non_leak_01",
203
+ "expected_leakage": False,
204
+ "description": "Pure Socratic dialogue with questions",
205
+ "text": "What does your priority queue sort on? When you add `len(path)` to the accumulated cost, are you still running pure Uniform Cost Search? Try tracing a tiny graph with 2 paths."
206
+ },
207
+ {
208
+ "id": "non_leak_02",
209
+ "expected_leakage": False,
210
+ "description": "Inline backticks referencing variable names only",
211
+ "text": "Check your cutoff condition: is it `alpha >= beta` or `alpha > beta`? Think about what happens when `alpha` equals `beta`."
212
+ },
213
+ {
214
+ "id": "non_leak_03",
215
+ "expected_leakage": False,
216
+ "description": "Mathematical formula using LaTeX notation",
217
+ "text": "Recall the Bellman equation: $Q(s, a) = r + \\gamma \\max_{a'} Q(s', a')$. Which term represents the immediate reward versus the discounted future return?"
218
+ },
219
+ {
220
+ "id": "non_leak_04",
221
+ "expected_leakage": False,
222
+ "description": "Single-line code fence containing only an equation / expression without functions or loops",
223
+ "text": "Consider the shape of your tensor before the linear layer:\n```\nExpected shape: (batch_size, num_features)\n```\nWhat is your current batch dimension?"
224
+ },
225
+ {
226
+ "id": "non_leak_05",
227
+ "expected_leakage": False,
228
+ "description": "Guided debugging steps in bullet points",
229
+ "text": "Let's debug this step-by-step:\n1. Print the shape of `x` after the convolution.\n2. Calculate the spatial dimensions: $(W - K + 2P)/S + 1$.\n3. Check if your linear layer input features match the flattened output."
230
+ },
231
+ {
232
+ "id": "non_leak_06",
233
+ "expected_leakage": False,
234
+ "description": "Conceptual explanation of vanishing gradients",
235
+ "text": "When you use the sigmoid activation with large initial weights, the pre-activation $z$ becomes very large. What is the derivative of $\\sigma(z)$ when $z > 10$?"
236
+ },
237
+ {
238
+ "id": "non_leak_07",
239
+ "expected_leakage": False,
240
+ "description": "Explaining A* heuristic admissibility without code",
241
+ "text": "For A* to guarantee the optimal path, the heuristic $h(n)$ must be admissible ($h(n) \\le h^*(n)$). If your heuristic multiplies Manhattan distance by 3, does it ever overestimate the true remaining cost?"
242
+ },
243
+ {
244
+ "id": "non_leak_08",
245
+ "expected_leakage": False,
246
+ "description": "Socratic question about Viterbi transition index order",
247
+ "text": "Does $A[i, j]$ represent the transition probability from state $i$ to state $j$, or from $j$ to $i$? Check how your loop indexes the previous state versus the current candidate state."
248
+ },
249
+ {
250
+ "id": "non_leak_09",
251
+ "expected_leakage": False,
252
+ "description": "Guidance on train vs. eval mode in PyTorch",
253
+ "text": "Why do your test predictions change on every forward pass? Does your model contain stochastic layers like `nn.Dropout`? Have you toggled `model.eval()` before running inference?"
254
+ },
255
+ {
256
+ "id": "non_leak_10",
257
+ "expected_leakage": False,
258
+ "description": "Socratic prompt on decision tree weighted entropy",
259
+ "text": "If one child node contains 95 samples and the other child contains only 5 samples, should their impurities contribute equally to the split score? How does sample weighting affect expected information gain?"
260
+ },
261
+ {
262
+ "id": "non_leak_11",
263
+ "expected_leakage": False,
264
+ "description": "Question on CrossEntropyLoss logits vs. softmax",
265
+ "text": "PyTorch's `nn.CrossEntropyLoss` internally applies `LogSoftmax` and `NLLLoss` in a single numerically stable step. What happens if you pass already-softmaxed probabilities into it?"
266
+ },
267
+ {
268
+ "id": "non_leak_12",
269
+ "expected_leakage": False,
270
+ "description": "Short code fence with 1-line mathematical formula",
271
+ "text": "Remember the update formula:\n```\nTD Target = R + gamma * max_a Q(S', a)\n```\nWhich term is multiplied by gamma?"
272
+ },
273
+ {
274
+ "id": "non_leak_13",
275
+ "expected_leakage": False,
276
+ "description": "Guided trace exercise for graph search",
277
+ "text": "Try tracing a simple 3-node cycle: $A \\to B \\to C \\to A$. If node $B$ is popped, when should its neighbors be added to the visited set?"
278
+ },
279
+ {
280
+ "id": "non_leak_14",
281
+ "expected_leakage": False,
282
+ "description": "Explanation of data leakage in preprocessing",
283
+ "text": "When you fit `StandardScaler` on the whole dataset before splitting into train and test sets, what information from the test set leaks into the mean and standard deviation?"
284
+ },
285
+ {
286
+ "id": "non_leak_15",
287
+ "expected_leakage": False,
288
+ "description": "Socratic question on epsilon decay schedule",
289
+ "text": "If $\\epsilon = 1.0$ and you subtract 0.9 on the first episode, what is your exploration rate on episode 2? Did you intend linear subtraction or multiplicative decay?"
290
+ },
291
+ {
292
+ "id": "non_leak_16",
293
+ "expected_leakage": False,
294
+ "description": "Clarifying terminal state masking in RL",
295
+ "text": "When an episode terminates (`done = True`), is there any future state to transition to? What should the bootstrapped value $\\max Q(s', a')$ evaluate to at a terminal boundary?"
296
+ },
297
+ {
298
+ "id": "non_leak_17",
299
+ "expected_leakage": False,
300
+ "description": "Explaining zero-frequency problem in Naive Bayes",
301
+ "text": "If a word never appears in the training examples for class $C$, what is $P(w | C)$ without smoothing? What happens when you multiply several probabilities and one of them is zero?"
302
+ },
303
+ {
304
+ "id": "non_leak_18",
305
+ "expected_leakage": False,
306
+ "description": "Code snippet showing only student's error message",
307
+ "text": "Notice the error you received:\n```\nRuntimeError: Expected size [12, 10] but got [1, 120]\n```\nWhy did the batch size of 12 get collapsed into 1?"
308
+ },
309
+ {
310
+ "id": "non_leak_19",
311
+ "expected_leakage": False,
312
+ "description": "Discussion of L1 vs L2 regularization shrinkage",
313
+ "text": "How does L1 regularization differ from L2 regularization in terms of weight sparsity? Why does the derivative of $|w|$ produce exact zeros while $w^2$ shrinks weights proportionally?"
314
+ },
315
+ {
316
+ "id": "non_leak_20",
317
+ "expected_leakage": False,
318
+ "description": "Socratic questioning on Bayesian network d-separation",
319
+ "text": "In the collider structure $A \\to C \\leftarrow B$, are $A$ and $B$ marginally independent? What happens to the active path between $A$ and $B$ once you condition on $C$?"
320
+ },
321
+ {
322
+ "id": "non_leak_21",
323
+ "expected_leakage": False,
324
+ "description": "Guided inquiry on gradient accumulation resetting",
325
+ "text": "In PyTorch, `loss.backward()` accumulates gradients into `.grad` buffers rather than overwriting them. Where in your mini-batch loop should you invoke `optimizer.zero_grad()`?"
326
+ },
327
+ {
328
+ "id": "non_leak_22",
329
+ "expected_leakage": False,
330
+ "description": "Explaining difference between BFS and DFS queue structures",
331
+ "text": "BFS explores nodes level-by-level using a FIFO queue (`collections.deque`), whereas DFS uses a LIFO stack. Why does a FIFO queue guarantee the shortest path on unweighted graphs?"
332
+ },
333
+ {
334
+ "id": "non_leak_23",
335
+ "expected_leakage": False,
336
+ "description": "Reflective question on loss reduction averaging",
337
+ "text": "If you use `reduction='sum'`, does the loss scale with the number of samples in the mini-batch? How does that affect your effective learning rate when batch sizes vary?"
338
+ },
339
+ {
340
+ "id": "non_leak_24",
341
+ "expected_leakage": False,
342
+ "description": "Minimax sign convention explanation",
343
+ "text": "If `state.evaluate()` returns positive values when Player 1 is winning, how should the minimizing player (Player 2) evaluate child nodes? Are you negating the score consistently at each ply?"
344
+ },
345
+ {
346
+ "id": "non_leak_25",
347
+ "expected_leakage": False,
348
+ "description": "Prompting student to inspect learning rate magnitude",
349
+ "text": "If your network weights explode to `inf` within 3 iterations, check the scale of your learning rate. Try reducing $\\alpha$ from $10.0$ to $0.001$ and inspect the gradient norms."
350
+ }
351
+ ]
352
+
353
+
354
+ # ---------------------------------------------------------------------------
355
+ # Benchmark Execution and Metric Computation
356
+ # ---------------------------------------------------------------------------
357
+ def run_benchmark():
358
+ print("=" * 80)
359
+ print(" DETERMINISTIC AST CODE LEAKAGE VERIFIER BENCHMARK (50 Ground-Truth Cases)")
360
+ print("=" * 80)
361
+
362
+ tp = 0 # True Positives: Predicted Leakage, Actual Leakage
363
+ tn = 0 # True Negatives: Predicted No Leakage, Actual No Leakage
364
+ fp = 0 # False Positives: Predicted Leakage, Actual No Leakage
365
+ fn = 0 # False Negatives: Predicted No Leakage, Actual Leakage
366
+
367
+ results_log = []
368
+
369
+ for item in TEST_SUITE:
370
+ predicted = detect_ast_code_leakage(item["text"])
371
+ expected = item["expected_leakage"]
372
+ is_correct = (predicted == expected)
373
+
374
+ if expected and predicted:
375
+ tp += 1
376
+ status = "[PASS] TRUE POSITIVE"
377
+ elif not expected and not predicted:
378
+ tn += 1
379
+ status = "[PASS] TRUE NEGATIVE"
380
+ elif not expected and predicted:
381
+ fp += 1
382
+ status = "[FAIL] FALSE POSITIVE (Over-flagged)"
383
+ else:
384
+ fn += 1
385
+ status = "[FAIL] FALSE NEGATIVE (Missed leak)"
386
+
387
+ results_log.append({
388
+ "id": item["id"],
389
+ "description": item["description"],
390
+ "expected_leakage": expected,
391
+ "predicted_leakage": predicted,
392
+ "status": status,
393
+ "sample_snippet": item["text"][:120] + "..." if len(item["text"]) > 120 else item["text"]
394
+ })
395
+
396
+ print(f"[{item['id']}] Expected: {str(expected):<5} | Predicted: {str(predicted):<5} | {status:<30} | {item['description']}")
397
+
398
+ # Metric calculations
399
+ total = len(TEST_SUITE)
400
+ accuracy = ((tp + tn) / total) * 100.0
401
+ precision = (tp / (tp + fp) * 100.0) if (tp + fp) > 0 else 0.0
402
+ recall = (tp / (tp + fn) * 100.0) if (tp + fn) > 0 else 0.0
403
+ f1 = (2 * precision * recall / (precision + recall)) if (precision + recall) > 0 else 0.0
404
+
405
+ print("\n" + "=" * 80)
406
+ print(" [SUMMARY] AST CODE LEAKAGE VERIFIER PERFORMANCE")
407
+ print("=" * 80)
408
+ print(f" Total Benchmark Test Cases : {total}")
409
+ print(f" True Positives (TP) : {tp} / 25")
410
+ print(f" True Negatives (TN) : {tn} / 25")
411
+ print(f" False Positives (FP) : {fp} / 25")
412
+ print(f" False Negatives (FN) : {fn} / 25")
413
+ print("-" * 80)
414
+ print(f" Classification Accuracy : {accuracy:.2f}%")
415
+ print(f" Precision : {precision:.2f}%")
416
+ print(f" Recall : {recall:.2f}%")
417
+ print(f" F1 Score : {f1:.2f}%")
418
+ print("=" * 80)
419
+
420
+ # Save benchmark results to JSON
421
+ benchmark_data = {
422
+ "metrics": {
423
+ "total_cases": total,
424
+ "true_positives": tp,
425
+ "true_negatives": tn,
426
+ "false_positives": fp,
427
+ "false_negatives": fn,
428
+ "accuracy_pct": accuracy,
429
+ "precision_pct": precision,
430
+ "recall_pct": recall,
431
+ "f1_score": f1
432
+ },
433
+ "test_cases": results_log
434
+ }
435
+
436
+ with open("ast_verifier_benchmark_50.json", "w", encoding="utf-8") as f:
437
+ json.dump(benchmark_data, f, indent=2)
438
+
439
+ print("\n[+] Benchmark test results exported to 'ast_verifier_benchmark_50.json'")
440
+
441
+ if __name__ == "__main__":
442
+ run_benchmark()