Skip to content

Commit 3eb3438

Browse files
committed
fix(featureDev): Prevent crash on large repos or unsupported encoding during file collection
1 parent 881b70b commit 3eb3438

File tree

2 files changed

+40
-18
lines changed

2 files changed

+40
-18
lines changed

packages/core/src/amazonqFeatureDev/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export const featureDevChat = 'featureDevChat'
1414

1515
export const featureName = 'Amazon Q feature development'
1616

17+
// Max allowed size for file collection
18+
export const maxRepoSize = 200 * 1024 * 1024
19+
1720
// License text that's used in codewhisperer reference log
1821
export const referenceLogText = (reference: CodeReference) =>
1922
`[${new Date().toLocaleString()}] Accepted recommendation from Amazon Q. Code provided with reference under <a href="${LicenseUtil.getLicenseHtml(

packages/core/src/amazonqFeatureDev/util/files.ts

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Uri } from 'vscode'
1212
import { GitIgnoreFilter } from './gitignore'
1313

1414
import AdmZip from 'adm-zip'
15-
import { PrepareRepoFailedError } from '../errors'
15+
import { ContentLengthError, PrepareRepoFailedError } from '../errors'
1616
import { getLogger } from '../../shared/logger/logger'
1717
import { maxFileSizeBytes } from '../limits'
1818
import { createHash } from 'crypto'
@@ -21,6 +21,7 @@ import { ToolkitError } from '../../shared/errors'
2121
import { AmazonqCreateUpload, Metric } from '../../shared/telemetry/telemetry'
2222
import { TelemetryHelper } from './telemetryHelper'
2323
import { sanitizeFilename } from '../../shared/utilities/textUtilities'
24+
import { maxRepoSize } from '../constants'
2425

2526
export function getExcludePattern(additionalPatterns: string[] = []) {
2627
const globAlwaysExcludedDirs = getGlobDirExcludedPatterns().map(pattern => `**/${pattern}/*`)
@@ -94,6 +95,7 @@ export async function collectFiles(
9495
return prefix === '' ? path : `${prefix}/${path}`
9596
}
9697

98+
let totalSize = 0
9799
for (const rootPath of sourcePaths) {
98100
const allFiles = await vscode.workspace.findFiles(
99101
new vscode.RelativePattern(rootPath, '**'),
@@ -102,29 +104,43 @@ export async function collectFiles(
102104
const files = respectGitIgnore ? await filterOutGitignoredFiles(rootPath, allFiles) : allFiles
103105

104106
for (const file of files) {
105-
try {
106-
const fileContent = await SystemUtilities.readFile(file, new TextDecoder('utf8', { fatal: true }))
107-
const relativePath = getWorkspaceRelativePath(file.fsPath, { workspaceFolders })
107+
const fileStat = await vscode.workspace.fs.stat(file)
108+
const { relativePath, fileContent } = await readFile(file, workspaceFolders)
109+
if (relativePath === undefined || fileContent === undefined) {
110+
continue
111+
}
108112

109-
if (relativePath) {
110-
storage.push({
111-
workspaceFolder: relativePath.workspaceFolder,
112-
relativeFilePath: relativePath.relativePath,
113-
fileUri: file,
114-
fileContent: fileContent,
115-
zipFilePath: prefixWithFolderPrefix(relativePath.workspaceFolder, relativePath.relativePath),
116-
})
117-
}
118-
} catch (error) {
119-
getLogger().debug(
120-
`featureDev: Failed to read file ${file.fsPath} when collecting repository: ${error}. Skipping the file`
121-
)
113+
totalSize += fileStat.size
114+
if (totalSize > maxRepoSize) {
115+
throw new ContentLengthError()
122116
}
117+
118+
storage.push({
119+
workspaceFolder: relativePath.workspaceFolder,
120+
relativeFilePath: relativePath.relativePath,
121+
fileUri: file,
122+
fileContent: fileContent,
123+
zipFilePath: prefixWithFolderPrefix(relativePath.workspaceFolder, relativePath.relativePath),
124+
})
123125
}
124126
}
125127
return storage
126128
}
127129

130+
const readFile = async (file: vscode.Uri, workspaceFolders: CurrentWsFolders) => {
131+
try {
132+
const fileContent = await SystemUtilities.readFile(file, new TextDecoder('utf8', { fatal: false }))
133+
const relativePath = getWorkspaceRelativePath(file.fsPath, { workspaceFolders })
134+
return { fileContent, relativePath }
135+
} catch (error) {
136+
getLogger().debug(
137+
`featureDev: Failed to read file ${file.fsPath} when collecting repository. Skipping the file`
138+
)
139+
}
140+
141+
return { fileContent: undefined, relativePath: undefined }
142+
}
143+
128144
const getSha256 = (file: Buffer) => createHash('sha256').update(file).digest('base64')
129145

130146
/**
@@ -137,9 +153,9 @@ export async function prepareRepoData(
137153
span: Metric<AmazonqCreateUpload>
138154
) {
139155
try {
156+
const files = await collectFiles(repoRootPaths, workspaceFolders, true)
140157
const zip = new AdmZip()
141158

142-
const files = await collectFiles(repoRootPaths, workspaceFolders, true)
143159
let totalBytes = 0
144160
for (const file of files) {
145161
const fileSize = (await vscode.workspace.fs.stat(file.fileUri)).size
@@ -163,6 +179,9 @@ export async function prepareRepoData(
163179
}
164180
} catch (error) {
165181
getLogger().debug(`featureDev: Failed to prepare repo: ${error}`)
182+
if (error instanceof ContentLengthError) {
183+
throw error
184+
}
166185
throw new PrepareRepoFailedError()
167186
}
168187
}

0 commit comments

Comments
 (0)