Prevent overriding of Disposable.dispose

A common mistake made was overriding dispose and not calling super.dispose,
so a TS trick is now used to move it to a readonly property instead of a
member method which prevents it being overridden.
This commit is contained in:
Daniel Imms
2022-10-08 08:27:10 -07:00
parent 34475f24c3
commit ac659cb412
19 changed files with 94 additions and 142 deletions
+5 -9
View File
@@ -21,7 +21,7 @@
* http://linux.die.net/man/7/urxvt
*/
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services';
import { InstantiationService } from 'common/services/InstantiationService';
import { LogService } from 'common/services/LogService';
@@ -143,15 +143,11 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
// Setup WriteBuffer
this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult));
this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed));
}
public dispose(): void {
if (this._isDisposed) {
return;
}
super.dispose();
this._windowsMode?.dispose();
this._windowsMode = undefined;
this.register(toDisposable(() => {
this._windowsMode?.dispose();
this._windowsMode = undefined;
}));
}
public write(data: string | Uint8Array, callback?: () => void): void {
-4
View File
@@ -382,10 +382,6 @@ export class InputHandler extends Disposable implements IInputHandler {
this._parser.registerDcsHandler({ intermediates: '$', final: 'q' }, new DcsHandler((data, params) => this.requestStatusString(data, params)));
}
public dispose(): void {
super.dispose();
}
/**
* Async parse support.
*/
+6 -3
View File
@@ -17,15 +17,18 @@ export abstract class Disposable implements IDisposable {
}
/**
* Disposes the object, triggering the `dispose` method on all registered IDisposables.
* Disposes the object, triggering the `dispose` method on all registered IDisposables. This is a
* readonly property instead of a method to prevent subclasses overriding it which is an easy
* mistake that can introduce memory leaks. If a class extends Disposable, all dispose calls
* should be done via {@link register}.
*/
public dispose(): void {
public readonly dispose = (): void => {
this._isDisposed = true;
for (const d of this._disposables) {
d.dispose();
}
this._disposables.length = 0;
}
};
/**
* Registers a disposable object.
+8 -15
View File
@@ -3,35 +3,28 @@
* @license MIT
*/
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IMarker } from 'common/Types';
export class Marker extends Disposable implements IMarker {
private static _nextId = 1;
private _id: number = Marker._nextId++;
public isDisposed: boolean = false;
public get id(): number { return this._id; }
private readonly _onDispose = new EventEmitter<void>();
public readonly onDispose = this._onDispose.event;
public get isDisposed(): boolean { return this._isDisposed; };
constructor(
public line: number
) {
super();
}
public dispose(): void {
if (this.isDisposed) {
return;
}
this.isDisposed = true;
this.line = -1;
// Emit before super.dispose such that dispose listeners get a change to react
this._onDispose.fire();
super.dispose();
this.register(toDisposable(() => {
this.line = -1;
this._onDispose.fire();
}));
}
}
@@ -87,7 +87,7 @@ class TestEscapeSequenceParser extends EscapeSequenceParser {
}
}
public mockOscParser(): void {
this._oscParser = oscPutParser;
(this as any)._oscParser = oscPutParser;
}
public identifier(id: IFunctionIdentifier): number {
return this._identifier(id);
+10 -13
View File
@@ -5,7 +5,7 @@
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandlerType, OscFallbackHandlerType, IOscParser, EscHandlerType, IDcsParser, DcsFallbackHandlerType, IFunctionIdentifier, ExecuteFallbackHandlerType, CsiFallbackHandlerType, EscFallbackHandlerType, PrintHandlerType, PrintFallbackHandlerType, ExecuteHandlerType, IParserStackState, ParserStackType, ResumableHandlersType } from 'common/parser/Types';
import { ParserState, ParserAction } from 'common/parser/Constants';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IDisposable } from 'common/Types';
import { fill } from 'common/TypedArrayUtils';
import { Params } from 'common/parser/Params';
@@ -242,8 +242,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _executeHandlers: { [flag: number]: ExecuteHandlerType };
protected _csiHandlers: IHandlerCollection<CsiHandlerType>;
protected _escHandlers: IHandlerCollection<EscHandlerType>;
protected _oscParser: IOscParser;
protected _dcsParser: IDcsParser;
protected readonly _oscParser: IOscParser;
protected readonly _dcsParser: IDcsParser;
protected _errorHandler: (state: IParsingState) => IParsingState;
// fallback handlers
@@ -284,8 +284,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._executeHandlers = Object.create(null);
this._csiHandlers = Object.create(null);
this._escHandlers = Object.create(null);
this._oscParser = new OscParser();
this._dcsParser = new DcsParser();
this.register(toDisposable(() => {
this._csiHandlers = Object.create(null);
this._executeHandlers = Object.create(null);
this._escHandlers = Object.create(null);
}));
this._oscParser = this.register(new OscParser());
this._dcsParser = this.register(new DcsParser());
this._errorHandler = this._errorHandlerFb;
// swallow 7bit ST (ESC+\)
@@ -338,14 +343,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
return res.reverse().join('');
}
public dispose(): void {
this._csiHandlers = Object.create(null);
this._executeHandlers = Object.create(null);
this._escHandlers = Object.create(null);
this._oscParser.dispose();
this._dcsParser.dispose();
}
public setPrintHandler(handler: PrintHandlerType): void {
this._printHandler = handler;
}
+1 -6
View File
@@ -36,12 +36,7 @@ export class BufferService extends Disposable implements IBufferService {
super();
this.cols = Math.max(optionsService.rawOptions.cols || 0, MINIMUM_COLS);
this.rows = Math.max(optionsService.rawOptions.rows || 0, MINIMUM_ROWS);
this.buffers = new BufferSet(optionsService, this);
}
public dispose(): void {
super.dispose();
this.buffers.dispose();
this.buffers = this.register(new BufferSet(optionsService, this));
}
public resize(cols: number, rows: number): void {
+19 -17
View File
@@ -5,7 +5,7 @@
import { css } from 'common/Color';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IDecorationService, IInternalDecoration } from 'common/services/Services';
import { SortedList } from 'common/SortedList';
import { IColor } from 'common/Types';
@@ -32,6 +32,16 @@ export class DecorationService extends Disposable implements IDecorationService
public get decorations(): IterableIterator<IInternalDecoration> { return this._decorations.values(); }
constructor() {
super();
this.register(toDisposable(() => {
for (const d of this._decorations.values()) {
this._onDecorationRemoved.fire(d);
}
this.reset();
}));
}
public registerDecoration(options: IDecorationOptions): IDecoration | undefined {
if (options.marker.isDisposed) {
return undefined;
@@ -81,25 +91,19 @@ export class DecorationService extends Disposable implements IDecorationService
}
});
}
public dispose(): void {
for (const d of this._decorations.values()) {
this._onDecorationRemoved.fire(d);
}
this.reset();
}
}
class Decoration extends Disposable implements IInternalDecoration {
public readonly marker: IMarker;
public element: HTMLElement | undefined;
public isDisposed: boolean = false;
public readonly onRenderEmitter = this.register(new EventEmitter<HTMLElement>());
public readonly onRender = this.onRenderEmitter.event;
private readonly _onDispose = this.register(new EventEmitter<void>());
public readonly onDispose = this._onDispose.event;
public get isDisposed(): boolean { return this._isDisposed; }
private _cachedBg: IColor | undefined | null = null;
public get backgroundColorRGB(): IColor | undefined {
if (this._cachedBg === null) {
@@ -132,14 +136,12 @@ class Decoration extends Disposable implements IInternalDecoration {
if (this.options.overviewRulerOptions && !this.options.overviewRulerOptions.position) {
this.options.overviewRulerOptions.position = 'full';
}
}
public override dispose(): void {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
this._onDispose.fire();
super.dispose();
this.register(toDisposable(() => {
if (this._isDisposed) {
return;
}
this._onDispose.fire();
}));
}
}