Skip to content

Commit 861a9c8

Browse files
committed
Enhance validation in native Python finder and conda utilities to ensure data integrity and improve error handling
1 parent 42fb029 commit 861a9c8

File tree

2 files changed

+94
-16
lines changed

2 files changed

+94
-16
lines changed

src/managers/common/nativePythonFinder.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,24 @@ class NativePythonFinderImpl implements NativePythonFinder {
162162
traceVerbose('Finder - from cache environments', key);
163163
}
164164
const result = await this.pool.addToQueue(options);
165+
// Validate result from worker pool
166+
if (!result || !Array.isArray(result)) {
167+
this.outputChannel.warn(`[pet] Worker pool returned invalid result type: ${typeof result}`);
168+
return [];
169+
}
165170
this.cache.set(key, result);
166171
return result;
167172
}
168173

169174
private async handleSoftRefresh(options?: NativePythonEnvironmentKind | Uri[]): Promise<NativeInfo[]> {
170175
const key = this.getKey(options);
171176
const cacheResult = this.cache.get(key);
172-
if (!cacheResult) {
177+
// Validate cache integrity - if cached value is not a valid array, do a hard refresh
178+
if (!cacheResult || !Array.isArray(cacheResult)) {
179+
if (cacheResult !== undefined) {
180+
this.outputChannel.warn(`[pet] Cache contained invalid data type: ${typeof cacheResult}`);
181+
this.cache.delete(key);
182+
}
173183
return this.handleHardRefresh(options);
174184
}
175185

src/managers/conda/condaUtils.ts

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,38 @@ export async function runCondaExecutable(
256256
return await _runConda(conda, args, log, token);
257257
}
258258

259-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
260-
async function getCondaInfo(): Promise<any> {
259+
interface CondaInfo {
260+
envs_dirs: string[];
261+
}
262+
263+
/**
264+
* Runs `conda info --envs --json` and parses the result.
265+
* Validates the JSON response structure at the parsing boundary.
266+
* @returns Validated CondaInfo object
267+
* @throws Error if conda command fails or returns invalid JSON structure
268+
*/
269+
async function getCondaInfo(): Promise<CondaInfo> {
261270
const raw = await runConda(['info', '--envs', '--json']);
262-
return JSON.parse(raw);
271+
const parsed = JSON.parse(raw);
272+
273+
// Validate at the JSON→TypeScript boundary
274+
if (!parsed || typeof parsed !== 'object') {
275+
traceWarn(`conda info returned invalid data: ${typeof parsed}`);
276+
throw new Error(`conda info returned invalid data type: ${typeof parsed}`);
277+
}
278+
279+
const envsDirs = parsed['envs_dirs'];
280+
if (envsDirs === undefined || envsDirs === null) {
281+
traceWarn('conda info envs_dirs is undefined/null');
282+
return { envs_dirs: [] };
283+
}
284+
if (!Array.isArray(envsDirs)) {
285+
traceWarn(`conda info envs_dirs is not an array (type: ${typeof envsDirs})`);
286+
return { envs_dirs: [] };
287+
}
288+
289+
traceVerbose(`conda info returned ${envsDirs.length} environment directories`);
290+
return { envs_dirs: envsDirs };
263291
}
264292

265293
let prefixes: string[] | undefined;
@@ -269,17 +297,15 @@ export async function getPrefixes(): Promise<string[]> {
269297
}
270298

271299
const state = await getWorkspacePersistentState();
272-
prefixes = await state.get<string[]>(CONDA_PREFIXES_KEY);
273-
if (prefixes) {
300+
const storedPrefixes = await state.get<string[]>(CONDA_PREFIXES_KEY);
301+
if (storedPrefixes && Array.isArray(storedPrefixes)) {
302+
prefixes = storedPrefixes;
274303
return prefixes;
275304
}
276305

277306
try {
278307
const data = await getCondaInfo();
279-
prefixes = Array.isArray(data['envs_dirs']) ? (data['envs_dirs'] as string[]) : [];
280-
if (prefixes.length === 0) {
281-
traceWarn('Conda info returned no environment directories (envs_dirs)');
282-
}
308+
prefixes = data.envs_dirs;
283309
await state.set(CONDA_PREFIXES_KEY, prefixes);
284310
} catch (error) {
285311
traceError('Failed to get conda environment prefixes', error);
@@ -624,6 +650,11 @@ async function nativeToPythonEnv(
624650
conda: string,
625651
condaPrefixes: string[],
626652
): Promise<PythonEnvironment | undefined> {
653+
// Defensive check: Validate NativeEnvInfo object
654+
if (!e) {
655+
traceWarn('nativeToPythonEnv received null/undefined NativeEnvInfo');
656+
return undefined;
657+
}
627658
if (!(e.prefix && e.executable && e.version)) {
628659
let name = e.name;
629660
const environment = api.createPythonEnvironmentItem(
@@ -687,20 +718,45 @@ export async function refreshCondaEnvs(
687718
log: LogOutputChannel,
688719
manager: EnvironmentManager,
689720
): Promise<PythonEnvironment[]> {
690-
log.info('Refreshing conda environments');
691-
const data = await nativeFinder.refresh(hardRefresh);
721+
log.info(`Refreshing conda environments (hardRefresh=${hardRefresh})`);
722+
723+
let data: (NativeEnvInfo | NativeEnvManagerInfo)[];
724+
try {
725+
data = await nativeFinder.refresh(hardRefresh);
726+
} catch (error) {
727+
traceError('Failed to refresh native finder for conda environments', error);
728+
log.error(`Failed to refresh native finder: ${error instanceof Error ? error.message : String(error)}`);
729+
return [];
730+
}
731+
732+
// Ensure data is a valid array before proceeding
733+
if (!data || !Array.isArray(data)) {
734+
traceWarn(`Native finder returned invalid data: ${typeof data}, expected array`);
735+
log.warn(`Native finder returned invalid data type: ${typeof data}`);
736+
return [];
737+
}
738+
739+
traceVerbose(`Native finder returned ${data.length} items for conda refresh`);
692740

693741
let conda: string | undefined = undefined;
694742
try {
695743
conda = await getConda();
696-
} catch {
744+
} catch (error) {
745+
traceVerbose(`getConda() failed, will try to find conda from native data: ${error}`);
697746
conda = undefined;
698747
}
699748
if (conda === undefined) {
700749
const managers = data
701750
.filter((e) => !isNativeEnvInfo(e))
702751
.map((e) => e as NativeEnvManagerInfo)
703752
.filter((e) => e.tool.toLowerCase() === 'conda');
753+
754+
if (managers.length === 0) {
755+
traceWarn('No conda manager found in native finder data');
756+
log.warn('No conda manager found in native finder data');
757+
return [];
758+
}
759+
704760
conda = managers[0].executable;
705761
await setConda(conda);
706762
}
@@ -717,9 +773,21 @@ export async function refreshCondaEnvs(
717773

718774
await Promise.all(
719775
envs.map(async (e) => {
720-
const environment = await nativeToPythonEnv(e, api, manager, log, condaPath, condaPrefixes);
721-
if (environment) {
722-
collection.push(environment);
776+
try {
777+
const environment = await nativeToPythonEnv(e, api, manager, log, condaPath, condaPrefixes);
778+
if (environment) {
779+
collection.push(environment);
780+
}
781+
} catch (error) {
782+
traceError(
783+
`Failed to convert native env to Python environment: ${e.prefix ?? e.executable}`,
784+
error,
785+
);
786+
log.error(
787+
`Failed to process conda environment ${e.prefix ?? e.executable}: ${
788+
error instanceof Error ? error.message : String(error)
789+
}`,
790+
);
723791
}
724792
}),
725793
);

0 commit comments

Comments
 (0)