Skip to content

Commit cbe48a5

Browse files
author
Mikhail Arkhipov
authored
Fix handling of escaped string in tokenizer + PTVS launch hardening (#1377)
* Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3f. * Revert "Remove double event listening" This reverts commit af573be. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3f. * Revert "Remove double event listening" This reverts commit af573be. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string
1 parent 2027fce commit cbe48a5

File tree

3 files changed

+47
-9
lines changed

3 files changed

+47
-9
lines changed

src/client/activation/analysis.ts

+27-9
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
import * as path from 'path';
55
import { ExtensionContext, OutputChannel } from 'vscode';
6-
import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient';
6+
import { Message } from 'vscode-jsonrpc';
7+
import { CloseAction, Disposable, ErrorAction, ErrorHandler, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient';
78
import { IApplicationShell } from '../common/application/types';
89
import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants';
10+
import { createDeferred, Deferred } from '../common/helpers';
911
import { IFileSystem, IPlatformService } from '../common/platform/types';
1012
import { IProcessService } from '../common/process/types';
1113
import { StopWatch } from '../common/stopWatch';
@@ -21,6 +23,18 @@ const dotNetCommand = 'dotnet';
2123
const languageClientName = 'Python Tools';
2224
const analysisEngineFolder = 'analysis';
2325

26+
class LanguageServerStartupErrorHandler implements ErrorHandler {
27+
constructor(private readonly deferred: Deferred<void>) { }
28+
public error(error: Error, message: Message, count: number): ErrorAction {
29+
this.deferred.reject();
30+
return ErrorAction.Shutdown;
31+
}
32+
public closed(): CloseAction {
33+
this.deferred.reject();
34+
return CloseAction.DoNotRestart;
35+
}
36+
}
37+
2438
export class AnalysisExtensionActivator implements IExtensionActivator {
2539
private readonly configuration: IConfigurationService;
2640
private readonly appShell: IApplicationShell;
@@ -92,16 +106,23 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
92106

93107
private async tryStartLanguageClient(context: ExtensionContext, lc: LanguageClient): Promise<void> {
94108
let disposable: Disposable | undefined;
109+
const deferred = createDeferred<void>();
95110
try {
111+
lc.clientOptions.errorHandler = new LanguageServerStartupErrorHandler(deferred);
112+
96113
disposable = lc.start();
97-
await lc.onReady();
114+
lc.onReady()
115+
.then(() => deferred.resolve())
116+
.catch(ex => deferred.reject());
117+
await deferred.promise;
118+
98119
this.output.appendLine(`Language server ready: ${this.sw.elapsedTime} ms`);
99120
context.subscriptions.push(disposable);
100121
} catch (ex) {
101122
if (disposable) {
102123
disposable.dispose();
103-
throw ex;
104124
}
125+
throw ex;
105126
}
106127
}
107128

@@ -157,12 +178,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
157178
// tslint:disable-next-line:no-string-literal
158179
properties['SearchPaths'] = searchPaths;
159180

160-
if (isTestExecution()) {
161-
// tslint:disable-next-line:no-string-literal
162-
properties['TestEnvironment'] = true;
163-
}
164-
165181
const selector: string[] = [PYTHON];
182+
166183
// Options to control the language client
167184
return {
168185
// Register the server for Python documents
@@ -181,7 +198,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
181198
trimDocumentationText: false,
182199
maxDocumentationTextLength: 0
183200
},
184-
asyncStartup: true
201+
asyncStartup: true,
202+
testEnvironment: isTestExecution()
185203
}
186204
};
187205
}

src/client/language/tokenizer.ts

+3
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,9 @@ export class Tokenizer implements ITokenizer {
366366

367367
private skipToSingleEndQuote(quote: number): void {
368368
while (!this.cs.isEndOfStream()) {
369+
if (this.cs.currentChar === Char.LineFeed || this.cs.currentChar === Char.CarriageReturn) {
370+
return; // Unterminated single-line string
371+
}
369372
if (this.cs.currentChar === Char.Backslash && this.cs.nextChar === quote) {
370373
this.cs.advance(2);
371374
continue;

src/test/language/tokenizer.test.ts

+17
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,23 @@ suite('Language.Tokenizer', () => {
116116
assert.equal(tokens.getItemAt(0).type, TokenType.String);
117117
assert.equal(tokens.getItemAt(0).length, 14);
118118
});
119+
test('Strings: escape at the end of single quoted string ', async () => {
120+
const t = new Tokenizer();
121+
// tslint:disable-next-line:quotemark
122+
const tokens = t.tokenize("'quoted\\'\nx");
123+
assert.equal(tokens.count, 2);
124+
assert.equal(tokens.getItemAt(0).type, TokenType.String);
125+
assert.equal(tokens.getItemAt(0).length, 9);
126+
assert.equal(tokens.getItemAt(1).type, TokenType.Identifier);
127+
});
128+
test('Strings: escape at the end of double quoted string ', async () => {
129+
const t = new Tokenizer();
130+
const tokens = t.tokenize('"quoted\\"\nx');
131+
assert.equal(tokens.count, 2);
132+
assert.equal(tokens.getItemAt(0).type, TokenType.String);
133+
assert.equal(tokens.getItemAt(0).length, 9);
134+
assert.equal(tokens.getItemAt(1).type, TokenType.Identifier);
135+
});
119136
test('Comments', async () => {
120137
const t = new Tokenizer();
121138
const tokens = t.tokenize(' #co"""mment1\n\t\n#comm\'ent2 ');

0 commit comments

Comments
 (0)