Skip to content

Commit 47979aa

Browse files
committed
Sync server src/test/dist to dd/sqlite-annotation-cache tip
Brings in all review feedback fixes (FTS safety, offset-only LIMIT, cache limit param, int/positive Zod schemas, store lifecycle fix) and the rebuilt dist.
1 parent 71d0ab1 commit 47979aa

13 files changed

+7015
-8621
lines changed

server/dist/codeql-development-mcp-server.js

Lines changed: 6542 additions & 8401 deletions
Large diffs are not rendered by default.

server/dist/codeql-development-mcp-server.js.map

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/src/lib/cli-tool-registry.ts

Lines changed: 148 additions & 120 deletions
Large diffs are not rendered by default.

server/src/lib/query-results-evaluator.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44

55
import { executeCodeQLCommand } from './cli-executor';
66
import { logger } from '../utils/logger';
7-
import { writeFileSync, readFileSync, statSync } from 'fs';
7+
import { closeSync, fstatSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'fs';
88
import { dirname, isAbsolute } from 'path';
9-
import { mkdirSync } from 'fs';
109

1110
export interface QueryEvaluationResult {
1211
success: boolean;
@@ -37,7 +36,11 @@ export type BuiltInEvaluator = keyof typeof BUILT_IN_EVALUATORS;
3736
/**
3837
* In-memory cache for extracted query metadata, keyed by file path.
3938
* Stores the file modification time to invalidate when the file changes.
39+
* Bounded to {@link METADATA_CACHE_MAX} entries; least-recently-used entries
40+
* (by access) are evicted when the limit is reached. Entries are refreshed
41+
* on cache hits via delete+set, so Map iteration order reflects LRU state.
4042
*/
43+
const METADATA_CACHE_MAX = 256;
4144
const metadataCache = new Map<string, { mtime: number; metadata: QueryMetadata }>();
4245

4346
/**
@@ -46,15 +49,23 @@ const metadataCache = new Map<string, { mtime: number; metadata: QueryMetadata }
4649
*/
4750
export async function extractQueryMetadata(queryPath: string): Promise<QueryMetadata> {
4851
try {
49-
// Check cache with mtime validation
50-
const stat = statSync(queryPath);
51-
const mtime = stat.mtimeMs;
52-
const cached = metadataCache.get(queryPath);
53-
if (cached && cached.mtime === mtime) {
54-
return cached.metadata;
52+
// Open once, then fstat + read via the fd to avoid TOCTOU race (CWE-367).
53+
const fd = openSync(queryPath, 'r');
54+
let queryContent: string;
55+
let mtime: number;
56+
try {
57+
mtime = fstatSync(fd).mtimeMs;
58+
const cached = metadataCache.get(queryPath);
59+
if (cached && cached.mtime === mtime) {
60+
// Refresh position in Map to implement true LRU behavior.
61+
metadataCache.delete(queryPath);
62+
metadataCache.set(queryPath, cached);
63+
return cached.metadata;
64+
}
65+
queryContent = readFileSync(fd, 'utf-8');
66+
} finally {
67+
closeSync(fd);
5568
}
56-
57-
const queryContent = readFileSync(queryPath, 'utf-8');
5869
const metadata: QueryMetadata = {};
5970

6071
// Extract metadata from comments using regex patterns
@@ -75,6 +86,11 @@ export async function extractQueryMetadata(queryPath: string): Promise<QueryMeta
7586
metadata.tags = tagsMatch[1].split(/\s+/).filter(t => t.length > 0);
7687
}
7788

89+
// Evict oldest entries when the cache exceeds the size limit.
90+
if (metadataCache.size >= METADATA_CACHE_MAX) {
91+
const firstKey = metadataCache.keys().next().value;
92+
if (firstKey !== undefined) metadataCache.delete(firstKey);
93+
}
7894
metadataCache.set(queryPath, { mtime, metadata });
7995
return metadata;
8096
} catch (error) {

server/src/lib/result-processor.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
import { basename, dirname } from 'path';
1010
import { mkdirSync, readFileSync } from 'fs';
1111
import { createHash } from 'crypto';
12-
import { executeCodeQLCommand, CLIExecutionResult } from './cli-executor';
13-
import { getActualCodeqlVersion } from './cli-executor';
12+
import { CLIExecutionResult, executeCodeQLCommand, getActualCodeqlVersion } from './cli-executor';
1413
import { evaluateQueryResults, extractQueryMetadata, QueryEvaluationResult } from './query-results-evaluator';
1514
import { resolveQueryPath } from './query-resolver';
1615
import { sessionDataManager } from './session-data-manager';

server/src/lib/session-data-manager.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Session Data Management
3-
* Provides session lifecycle management backed by SqliteStore (sql.js WASM)
3+
* Provides session lifecycle management backed by SqliteStore (sql.js asm.js)
44
*/
55

66
import { mkdirSync, writeFileSync } from 'fs';
@@ -45,10 +45,18 @@ export class SessionDataManager {
4545

4646
/**
4747
* Initialize the database and ensure it's properly set up.
48-
* Must be awaited before any session operations (sql.js WASM init is async).
48+
* Must be awaited before any session operations (sql.js init is async).
4949
*/
5050
async initialize(): Promise<void> {
5151
try {
52+
// (Re)create the store if storageLocation changed since construction
53+
// (e.g. via updateConfig or test mocks), closing the previous store first.
54+
const storageDir = this.getConfig().storageLocation;
55+
if (storageDir !== this.storageDir) {
56+
this.store.close();
57+
this.storageDir = storageDir;
58+
this.store = new SqliteStore(this.storageDir);
59+
}
5260
await this.store.initialize();
5361
const count = this.store.countSessions();
5462
logger.info(`Session data manager initialized with ${count} sessions`);

0 commit comments

Comments
 (0)