Skip to content

Commit fa680b5

Browse files
authored
fix(featureDev): crash on large repos or unsupported encoding during file collection #4888
Problem: There are currently 2 problems in featureDev when doing collection of repository files: - Trying to decode binary files might crash the extension host, and - too large repos might make the extension run out of available memory. Solution: This CR fixes both problems by: - Adding a maximum allowed repo size of 200mb for collection - Changing the behavior of the TextDecoder to replace the unsupported characters instead of throwing TypeError Users who try to use featureDev on a codebase larger than 200mb will see a message asking for a different workspace to be selected.
1 parent 79e90cb commit fa680b5

File tree

3 files changed

+49
-18
lines changed

3 files changed

+49
-18
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Fixed a crash when trying to use Q /dev on large projects or projects containing files with unsupported encoding."
4+
}

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 maxRepoSizeBytes = 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: 42 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 { maxRepoSizeBytes } 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 totalSizeBytes = 0
9799
for (const rootPath of sourcePaths) {
98100
const allFiles = await vscode.workspace.findFiles(
99101
new vscode.RelativePattern(rootPath, '**'),
@@ -102,29 +104,48 @@ 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 relativePath = getWorkspaceRelativePath(file.fsPath, { workspaceFolders })
108+
if (!relativePath) {
109+
continue
110+
}
108111

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-
)
112+
const fileStat = await vscode.workspace.fs.stat(file)
113+
if (totalSizeBytes + fileStat.size > maxRepoSizeBytes) {
114+
throw new ContentLengthError()
122115
}
116+
117+
const fileContent = await readFile(file)
118+
if (fileContent === undefined) {
119+
continue
120+
}
121+
122+
// Now that we've read the file, increase our usage
123+
totalSizeBytes += fileStat.size
124+
storage.push({
125+
workspaceFolder: relativePath.workspaceFolder,
126+
relativeFilePath: relativePath.relativePath,
127+
fileUri: file,
128+
fileContent: fileContent,
129+
zipFilePath: prefixWithFolderPrefix(relativePath.workspaceFolder, relativePath.relativePath),
130+
})
123131
}
124132
}
125133
return storage
126134
}
127135

136+
const readFile = async (file: vscode.Uri) => {
137+
try {
138+
const fileContent = await SystemUtilities.readFile(file, new TextDecoder('utf8', { fatal: false }))
139+
return fileContent
140+
} catch (error) {
141+
getLogger().debug(
142+
`featureDev: Failed to read file ${file.fsPath} when collecting repository. Skipping the file`
143+
)
144+
}
145+
146+
return undefined
147+
}
148+
128149
const getSha256 = (file: Buffer) => createHash('sha256').update(file).digest('base64')
129150

130151
/**
@@ -137,9 +158,9 @@ export async function prepareRepoData(
137158
span: Metric<AmazonqCreateUpload>
138159
) {
139160
try {
161+
const files = await collectFiles(repoRootPaths, workspaceFolders, true)
140162
const zip = new AdmZip()
141163

142-
const files = await collectFiles(repoRootPaths, workspaceFolders, true)
143164
let totalBytes = 0
144165
for (const file of files) {
145166
const fileSize = (await vscode.workspace.fs.stat(file.fileUri)).size
@@ -163,6 +184,9 @@ export async function prepareRepoData(
163184
}
164185
} catch (error) {
165186
getLogger().debug(`featureDev: Failed to prepare repo: ${error}`)
187+
if (error instanceof ContentLengthError) {
188+
throw error
189+
}
166190
throw new PrepareRepoFailedError()
167191
}
168192
}

0 commit comments

Comments
 (0)