Change:
Linux/Mac: TypeError: Cannot read property 'length' of null at t.i.getLinesContent ( at t.i.toRawText ( at t.beginModelSync ( at t.esureSyncedResources ( at at then ( at t.withSyncedResources ( at t.computeDirtyDiff ( at at then ( Windows: TypeError: Cannot read property 'length' of null at t.i.getLinesContent () at t.i.toRawText () at t.beginModelSync () at t.esureSyncedResources () at at then () at t.withSyncedResources () at t.computeDirtyDiff () at at then ()
Root cause: The telemetry service strips PII of of callstacks before logging them as a telemetry event. Following regex matches are used in the process the app root (eg: /Users/me/Downloads/Visual Studio ) the extensions folder (eg: ) /file:///.?/resources/app//gi /file:///./gi A sample call stack is as follows: After the first regex replace, we have Second and Third in this case do not have any net change The Fourth regex replace removes the important filepath info leaving us with This doesn't happen in Windows because the first regex never gets any match due to conflicts between forward and back slashes, the third regex does the real job of stripping out PII along with the prefix The unit tests didnt catch this because only the last 2 regex get applied by default. The first 2 are passed as config which is done by production code but not the unit tests. So we just got lucky. Errors from extensions do not have this problem, they don't have the prefix

Discussion:
// #1 `file:///DANGEROUS/PATH/resources/app/Useful/Information` // #2 // Any other file path that doesn't match the approved form above should be cleaned. // #3 "Error: ENOENT; no such file or directory" is often followed with PII, clean it <del> for (let piiPath of this._piiPaths) { this._cleanupPatterns.push([new RegExp(escapeRegExpCharacters(piiPath), 'gi'), '']); } </del> this._cleanupPatterns.push( [/file:///.*?/resources/app//gi, ''], [/file:///.*/gi, ''], [/ENOENT: no such file or directory.*?'([^']+)'/gi, 'ENOENT: no such file or directory'] ); <ins> for (let piiPath of this._piiPaths) { this._cleanupPatterns.push([new RegExp(escapeRegExpCharacters(piiPath), 'gi'), '']); } </ins> if (this._configurationService) { this._updateUserOptIn(); this._configurationService.onDidUpdateConfiguration(this._updateUserOptIn, this, this._disposables);

service.dispose(); })); <ins> test('Unexpected Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(function () { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); try { let settings = new ErrorTestingSettings(); let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender, piiPaths: [ settings.personalInfo + '/resources/app/' ] }, undefined); const errorTelemetry = new ErrorTelemetry(service); let dangerousPathWithImportantInfoError: any = new Error(settings.dangerousPathWithImportantInfo); dangerousPathWithImportantInfoError.stack = settings.stack; // Test that important information remains but personal info does not Errors.onUnexpectedError(dangerousPathWithImportantInfoError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.notEqual(testAppender.events[0].data.message.indexOf(settings.importantInfo), -1); assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1); assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1); assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.importantInfo), -1); assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1); assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1); assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1); assert.equal(testAppender.events[0].data.stack.split('n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); } finally { Errors.setUnexpectedErrorHandler(origErrorHandler); } })); test('Uncaught Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(function () { let errorStub = this.stub(window, 'onerror'); let settings = new ErrorTestingSettings(); let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender, piiPaths: [ settings.personalInfo + '/resources/app/' ] }, undefined); const errorTelemetry = new ErrorTelemetry(service); let dangerousPathWithImportantInfoError: any = new Error('dangerousPathWithImportantInfo'); dangerousPathWithImportantInfoError.stack = settings.stack; (<any>window.onerror)(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.equal(errorStub.callCount, 1); // Test that important information remains but personal info does not assert.notEqual(testAppender.events[0].data.message.indexOf(settings.importantInfo), -1); assert.equal(testAppender.events[0].data.message.indexOf(settings.personalInfo), -1); assert.equal(testAppender.events[0].data.message.indexOf(settings.filePrefix), -1); assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.importantInfo), -1); assert.equal(testAppender.events[0].data.stack.indexOf(settings.personalInfo), -1); assert.equal(testAppender.events[0].data.stack.indexOf(settings.filePrefix), -1); assert.notEqual(testAppender.events[0].data.stack.indexOf(settings.stack[4]), -1); assert.equal(testAppender.events[0].data.stack.split('n').length, settings.stack.length); errorTelemetry.dispose(); service.dispose(); })); </ins> test('Unexpected Error Telemetry removes PII but preserves Missing Model error message', sinon.test(function () { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();