diff --git a/Extension/CHANGELOG.md b/Extension/CHANGELOG.md index 10d10b9984..a774d14fdd 100644 --- a/Extension/CHANGELOG.md +++ b/Extension/CHANGELOG.md @@ -1,8 +1,9 @@ # C/C++ for Visual Studio Code Changelog -## Version 1.19.2: January 17, 2024 +## Version 1.19.2: January 22, 2024 ### Enhancements * Implement progressive population of IntelliSense results. [#7759](https://github.com/microsoft/vscode-cpptools/issues/7759) +* Improve memory efficiency by using token parsing in the 'Add #include' feature. [#11515](https://github.com/microsoft/vscode-cpptools/issues/11515) * Improve the types supported for the 'Add #include' code action. * Various performance improvements. @@ -16,6 +17,7 @@ * Fix German code analysis translations. [PR #11845](https://github.com/microsoft/vscode-cpptools/pull/11845) * Thank you for the contribution [@Sir2B (Tobias Obermayer)](https://github.com/Sir2B) * Fix an EACCES error when using include wildcards with system includes. [#11833](https://github.com/microsoft/vscode-cpptools/issues/11833) +* Fix IntelliSense passes occurring while a user is typing, not honoring the `C_Cpp.intelliSenseUpdateDelay` setting. * Fix a call hierarchy bug leading to use of header-only TU's unnecessarily. * Fix a bug that could lead to missing TU source file candidates. * Fix a "random" IntelliSense crash during completion. diff --git a/Extension/package.json b/Extension/package.json index 781fc1a999..69400eee6d 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -5811,17 +5811,17 @@ }, "configurationDefaults": { "[cpp]": { - "editor.wordBasedSuggestions": false, + "editor.wordBasedSuggestions": "off", "editor.suggest.insertMode": "replace", "editor.semanticHighlighting.enabled": true }, "[cuda-cpp]": { - "editor.wordBasedSuggestions": false, + "editor.wordBasedSuggestions": "off", "editor.suggest.insertMode": "replace", "editor.semanticHighlighting.enabled": true }, "[c]": { - "editor.wordBasedSuggestions": false, + "editor.wordBasedSuggestions": "off", "editor.suggest.insertMode": "replace", "editor.semanticHighlighting.enabled": true } diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 0653d934ab..f7b76c4b83 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -53,7 +53,7 @@ import { import { Location, TextEdit, WorkspaceEdit } from './commonTypes'; import * as configs from './configurations'; import { DataBinding } from './dataBinding'; -import { CppSourceStr, clients, configPrefix, updateLanguageConfigurations } from './extension'; +import { CppSourceStr, clients, configPrefix, updateLanguageConfigurations, watchForCrashes } from './extension'; import { LocalizeStringParams, getLocaleId, getLocalizedString } from './localization'; import { PersistentFolderState, PersistentWorkspaceState } from './persistentState'; import { createProtocolFilter } from './protocolFilter'; @@ -536,7 +536,7 @@ interface DidChangeActiveEditorParams { } // Requests -const InitializationRequest: RequestType = new RequestType('cpptools/initialize'); +const InitializationRequest: RequestType = new RequestType('cpptools/initialize'); const QueryCompilerDefaultsRequest: RequestType = new RequestType('cpptools/queryCompilerDefaults'); const QueryTranslationUnitSourceRequest: RequestType = new RequestType('cpptools/queryTranslationUnitSource'); const SwitchHeaderSourceRequest: RequestType = new RequestType('cpptools/didSwitchHeaderSource'); @@ -1572,7 +1572,7 @@ export class DefaultClient implements Client { // Move initialization to a separate message, so we can see log output from it. // A request is used in order to wait for completion and ensure that no subsequent // higher priority message may be processed before the Initialization request. - await languageClient.sendRequest(InitializationRequest, cppInitializationParams); + watchForCrashes(await languageClient.sendRequest(InitializationRequest, cppInitializationParams)); } public async sendDidChangeSettings(): Promise { diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index d7aca934f3..64fde71688 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -14,6 +14,7 @@ import * as vscode from 'vscode'; import { Range } from 'vscode-languageclient'; import * as nls from 'vscode-nls'; import { TargetPopulation } from 'vscode-tas-client'; +import * as which from 'which'; import { logAndReturn } from '../Utility/Async/returns'; import * as util from '../common'; import { PlatformInformation } from '../platform'; @@ -922,8 +923,8 @@ function reportMacCrashes(): void { const crashObject: Record = {}; if (err?.code) { // If the directory isn't there, we have a problem... - crashObject["fs.stat: err.code"] = err.code; - telemetry.logLanguageServerEvent("MacCrash", crashObject, undefined); + crashObject["errCode"] = err.code; + telemetry.logLanguageServerEvent("MacCrash", crashObject); return; } @@ -959,17 +960,64 @@ function reportMacCrashes(): void { } } -let previousMacCrashData: string; -let previousMacCrashCount: number = 0; +export function watchForCrashes(crashDirectory: string): void { + if (process.platform !== "win32") { + prevCrashFile = ""; + fs.stat(crashDirectory, (err) => { + const crashObject: Record = {}; + if (err?.code) { + // If the directory isn't there, we have a problem... + crashObject["errCode"] = err.code; + telemetry.logLanguageServerEvent("CppCrash", crashObject); + return; + } -function logMacCrashTelemetry(data: string): void { + // vscode.workspace.createFileSystemWatcher only works in workspace folders. + try { + fs.watch(crashDirectory, (event, filename) => { + if (event !== "rename") { + return; + } + if (!filename || filename === prevCrashFile) { + return; + } + prevCrashFile = filename; + if (!filename.startsWith("cpptools")) { + return; + } + // Wait 5 seconds to allow time for the crash log to finish being written. + setTimeout(() => { + fs.readFile(path.resolve(crashDirectory, filename), 'utf8', (err, data) => { + void handleCrashFileRead(crashDirectory, filename, err, data); + }); + }, 5000); + }); + } catch (e) { + // The file watcher limit is hit (may not be possible on Mac, but just in case). + } + }); + } +} + +let previousCrashData: string; +let previousCrashCount: number = 0; + +function logCrashTelemetry(data: string, type: string): void { const crashObject: Record = {}; const crashCountObject: Record = {}; crashObject.CrashingThreadCallStack = data; - previousMacCrashCount = data === previousMacCrashData ? previousMacCrashCount + 1 : 0; - previousMacCrashData = data; - crashCountObject.CrashCount = previousMacCrashCount; - telemetry.logLanguageServerEvent("MacCrash", crashObject, crashCountObject); + previousCrashCount = data === previousCrashData ? previousCrashCount + 1 : 0; + previousCrashData = data; + crashCountObject.CrashCount = previousCrashCount + 1; + telemetry.logLanguageServerEvent(type, crashObject, crashCountObject); +} + +function logMacCrashTelemetry(data: string): void { + logCrashTelemetry(data, "MacCrash"); +} + +function logCppCrashTelemetry(data: string): void { + logCrashTelemetry(data, "CppCrash"); } function handleMacCrashFileRead(err: NodeJS.ErrnoException | undefined | null, data: string): void { @@ -1062,6 +1110,75 @@ function handleMacCrashFileRead(err: NodeJS.ErrnoException | undefined | null, d logMacCrashTelemetry(data); } +async function handleCrashFileRead(crashDirectory: string, crashFile: string, err: NodeJS.ErrnoException | undefined | null, data: string): Promise { + if (err) { + if (err.code === "ENOENT") { + return; // ignore known issue + } + return logCppCrashTelemetry("readFile: " + err.code); + } + + const lines: string[] = data.split("\n"); + data = crashFile + "\n"; + const filtPath: string | null = which.sync("c++filt", { nothrow: true }); + const isMac: boolean = process.platform === "darwin"; + const startStr: string = isMac ? " _" : "(_"; + const offsetStr: string = isMac ? " + " : "+0x"; + const dotStr: string = "…"; + for (let lineNum: number = 2; lineNum < lines.length - 3; ++lineNum) { // skip first/last lines + if (lineNum > 2) { + data += "\n"; + } + const line: string = lines[lineNum]; + const startPos: number = line.indexOf(startStr); + if (startPos === -1) { + data += dotStr; + continue; // expected + } + const offsetPos: number = line.indexOf(offsetStr, startPos + startStr.length); + if (offsetPos === -1) { + data += `missing "${offsetStr}"`; + continue; // unexpected + } + const startPos2: number = startPos + 1; + let funcStr: string = line.substring(startPos2, offsetPos); + if (filtPath) { + const ret = await util.spawnChildProcess(filtPath, [funcStr], undefined, true).catch(logAndReturn.undefined); + if (ret !== undefined) { + funcStr = ret.output; + funcStr = funcStr.replace(/std::(?:__1|__cxx11)/g, "std"); // simplify std namespaces. + funcStr = funcStr.replace(/std::basic_/g, "std::"); + funcStr = funcStr.replace(/ >/g, ">"); + funcStr = funcStr.replace(/, std::(?:allocator|char_traits)/g, ""); + funcStr = funcStr.replace(//g, ""); + funcStr = funcStr.replace(/, std::allocator/g, ""); + } + } + data += funcStr + offsetStr; + const offsetPos2: number = offsetPos + offsetStr.length; + if (isMac) { + data += line.substring(offsetPos2); + } else { + const endPos: number = line.indexOf(")", offsetPos2); + if (endPos === -1) { + data += "missing )"; + continue; // unexpected + } + data += line.substring(offsetPos2, endPos); + } + } + + if (data.length > 8192) { // The API has an 8k limit. + data = data.substring(0, 8191) + "…"; + } + + console.log(`Crash call stack:\n${data}`); + logCppCrashTelemetry(data); + + await util.deleteFile(path.resolve(crashDirectory, crashFile)).catch(logAndReturn.undefined); + void util.deleteDirectory(crashDirectory).catch(logAndReturn.undefined); +} + export function deactivate(): Thenable { clients.timeTelemetryCollector.clear(); console.log("deactivating extension"); diff --git a/Extension/src/common.ts b/Extension/src/common.ts index 591380af33..8a997c0de7 100644 --- a/Extension/src/common.ts +++ b/Extension/src/common.ts @@ -677,6 +677,22 @@ export function deleteFile(filePath: string): Promise { }); } +export function deleteDirectory(directoryPath: string): Promise { + return new Promise((resolve, reject) => { + if (fs.existsSync(directoryPath)) { + fs.rmdir(directoryPath, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + } else { + resolve(); + } + }); +} + export function getReadmeMessage(): string { const readmePath: string = getExtensionFilePath("README.md"); const readmeMessage: string = localize("refer.read.me", "Please refer to {0} for troubleshooting information. Issues can be created at {1}", readmePath, "https://github.com/Microsoft/vscode-cpptools/issues"); @@ -736,14 +752,16 @@ export interface ProcessReturnType { output: string; } -export async function spawnChildProcess(program: string, args: string[] = [], continueOn?: string, cancellationToken?: vscode.CancellationToken): Promise { +export async function spawnChildProcess(program: string, args: string[] = [], continueOn?: string, skipLogging?: boolean, cancellationToken?: vscode.CancellationToken): Promise { // Do not use CppSettings to avoid circular require() - const settings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("C_Cpp", null); - const loggingLevel: string | undefined = settings.get("loggingLevel"); - if (loggingLevel === "Information" || loggingLevel === "Debug") { - getOutputChannelLogger().appendLine(`$ ${program} ${args.join(' ')}`); + if (skipLogging === undefined || !skipLogging) { + const settings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("C_Cpp", null); + const loggingLevel: string | undefined = settings.get("loggingLevel"); + if (loggingLevel === "Information" || loggingLevel === "Debug") { + getOutputChannelLogger().appendLine(`$ ${program} ${args.join(' ')}`); + } } - const programOutput: ProcessOutput = await spawnChildProcessImpl(program, args, continueOn, cancellationToken); + const programOutput: ProcessOutput = await spawnChildProcessImpl(program, args, continueOn, skipLogging, cancellationToken); const exitCode: number | NodeJS.Signals | undefined = programOutput.exitCode; if (programOutput.exitCode) { return { succeeded: false, exitCode, output: programOutput.stderr || programOutput.stdout || localize('process.exited', 'Process exited with code {0}', exitCode) }; @@ -765,12 +783,12 @@ interface ProcessOutput { stderr: string; } -async function spawnChildProcessImpl(program: string, args: string[], continueOn?: string, cancellationToken?: vscode.CancellationToken): Promise { +async function spawnChildProcessImpl(program: string, args: string[], continueOn?: string, skipLogging?: boolean, cancellationToken?: vscode.CancellationToken): Promise { const result = new ManualPromise(); // Do not use CppSettings to avoid circular require() const settings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("C_Cpp", null); - const loggingLevel: string | undefined = settings.get("loggingLevel"); + const loggingLevel: string | undefined = (skipLogging === undefined || !skipLogging) ? settings.get("loggingLevel") : "None"; let proc: child_process.ChildProcess; if (await isExecutable(program)) {