Skip to content

Commit

Permalink
deleteDeclaration: don't crash on the top node.
Browse files Browse the repository at this point in the history
A misbehaved client can sometimes cause the server to reach
`deleteDeclaration` with the SourceFile, and it will crash due to no
`node.parent`.  I couldn't find a good way to create a test for it, but
I could trigger it manually by having a file with just a `,`, and
sending an explicit `getCodeFixes` command to the server with
`errorCodes: [6133]`.

Do three things to improve this:

1. `textChanges.ts`: if we get here with the root node, delete it
   instead of failing.

2. `fixUnusedIdentifier.ts`: check that we don't `delete` a node that is
   the whole source file, so the error is more focused (should have more
   similar failure stacks).

3. `session.ts`: when there was any failure in `getCodeFixes`, check if
   the input had a diag code that does not appear in the requested text
   range, and throw an error saying that the failure is probably a
   result of a bad request.

Closes #33726 (probably not fixing it, but making it easier to find the
cause)
  • Loading branch information
elibarzilay committed Mar 24, 2021
1 parent fb60c9f commit 6ce82ab
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 7 deletions.
26 changes: 22 additions & 4 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1393,7 +1393,7 @@ namespace ts.server {
emptyArray;
}

private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs): readonly protocol.Diagnostic[] | readonly protocol.DiagnosticWithLinePosition[] {
private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs) {
const { configFile } = this.getConfigFileAndProject(args);
if (configFile) {
// all the config file errors are reported as part of semantic check so nothing to report here
Expand All @@ -1403,15 +1403,15 @@ namespace ts.server {
return this.getDiagnosticsWorker(args, /*isSemantic*/ false, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), !!args.includeLinePosition);
}

private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs): readonly protocol.Diagnostic[] | readonly protocol.DiagnosticWithLinePosition[] {
private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs) {
const { configFile, project } = this.getConfigFileAndProject(args);
if (configFile) {
return this.getConfigFileDiagnostics(configFile, project!, !!args.includeLinePosition); // TODO: GH#18217
}
return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file).filter(d => !!d.file), !!args.includeLinePosition);
}

private getSuggestionDiagnosticsSync(args: protocol.SuggestionDiagnosticsSyncRequestArgs): readonly protocol.Diagnostic[] | readonly protocol.DiagnosticWithLinePosition[] {
private getSuggestionDiagnosticsSync(args: protocol.SuggestionDiagnosticsSyncRequestArgs) {
const { configFile } = this.getConfigFileAndProject(args);
if (configFile) {
// Currently there are no info diagnostics for config files.
Expand Down Expand Up @@ -2233,7 +2233,25 @@ namespace ts.server {
const scriptInfo = project.getScriptInfoForNormalizedPath(file)!;
const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo);

const codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes, this.getFormatOptions(file), this.getPreferences(file));
let codeActions: readonly CodeFixAction[];
try {
codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes, this.getFormatOptions(file), this.getPreferences(file));
}
catch(e) {
const ls = project.getLanguageService();
const existingDiagCodes = [
...ls.getSyntacticDiagnostics(file),
...ls.getSemanticDiagnostics(file),
...ls.getSuggestionDiagnostics(file)
].map(d =>
decodedTextSpanIntersectsWith(startPosition, endPosition - startPosition, d.start!, d.length!)
&& d.code);
const badCode = args.errorCodes.find(c => !existingDiagCodes.includes(c));
if (badCode !== undefined) {
e.message = `BADCLIENT: Bad error code, ${badCode} not found in range ${startPosition}..${endPosition} (found: ${existingDiagCodes.join(", ")}); could have caused this error:\n${e.message}`;
}
throw e;
}
return simplifiedResult ? codeActions.map(codeAction => this.mapCodeFixAction(codeAction)) : codeActions;
}

Expand Down
6 changes: 4 additions & 2 deletions src/services/codefixes/fixUnusedIdentifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,10 @@ namespace ts.codefix {
if (isParameter(parent)) {
tryDeleteParameter(changes, sourceFile, parent, checker, sourceFiles, program, cancellationToken, isFixAll);
}
else if (!isFixAll || !(isIdentifier(token) && FindAllReferences.Core.isSymbolReferencedInFile(token, checker, sourceFile))) {
changes.delete(sourceFile, isImportClause(parent) ? token : isComputedPropertyName(parent) ? parent.parent : parent);
else if (!(isFixAll && isIdentifier(token) && FindAllReferences.Core.isSymbolReferencedInFile(token, checker, sourceFile))) {
const node = isImportClause(parent) ? token : isComputedPropertyName(parent) ? parent.parent : parent;
Debug.assert(node !== sourceFile, "should not delete whole source file");
changes.delete(sourceFile, node);
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/services/textChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1363,7 +1363,11 @@ namespace ts.textChanges {
break;

default:
if (isImportClause(node.parent) && node.parent.name === node) {
if (!node.parent) {
// a misbehaving client can reach here with the SourceFile node
deleteNode(changes, sourceFile, node);
}
else if (isImportClause(node.parent) && node.parent.name === node) {
deleteDefaultImport(changes, sourceFile, node.parent);
}
else if (isCallExpression(node.parent) && contains(node.parent.arguments, node)) {
Expand Down

0 comments on commit 6ce82ab

Please sign in to comment.