From 23f274fceef825b42834ccc382a8e2c22771a661 Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Tue, 18 Jul 2023 12:40:51 +0200 Subject: [PATCH] chore(kernel): use ES private fields (#4185) --- .../@jsii/kernel/src/disk-cache/disk-cache.ts | 28 +- packages/@jsii/kernel/src/kernel.ts | 420 +++++++++--------- packages/@jsii/kernel/src/objects.ts | 58 +-- .../@jsii/kernel/src/serialization.test.ts | 4 +- 4 files changed, 252 insertions(+), 258 deletions(-) diff --git a/packages/@jsii/kernel/src/disk-cache/disk-cache.ts b/packages/@jsii/kernel/src/disk-cache/disk-cache.ts index 2e4c5d7047..821ab3bab8 100644 --- a/packages/@jsii/kernel/src/disk-cache/disk-cache.ts +++ b/packages/@jsii/kernel/src/disk-cache/disk-cache.ts @@ -23,7 +23,7 @@ const PRUNE_AFTER_MILLISECONDS = process.env.JSII_RUNTIME_PACKAGE_CACHE_TTL : 30 * ONE_DAY_IN_MS; export class DiskCache { - private static readonly CACHE = new Map(); + static readonly #CACHE = new Map(); public static inDirectory(path: string): DiskCache { const didCreate = mkdirSync(path, { recursive: true }) != null; @@ -36,10 +36,10 @@ export class DiskCache { } path = realpathSync(path); - if (!this.CACHE.has(path)) { - this.CACHE.set(path, new DiskCache(path)); + if (!this.#CACHE.has(path)) { + this.#CACHE.set(path, new DiskCache(path)); } - return this.CACHE.get(path)!; + return this.#CACHE.get(path)!; } readonly #root: string; @@ -81,7 +81,7 @@ export class DiskCache { public pruneExpiredEntries() { const cutOff = new Date(Date.now() - PRUNE_AFTER_MILLISECONDS); - for (const entry of this.entries()) { + for (const entry of this.#entries()) { if (entry.atime < cutOff) { entry.lock((lockedEntry) => { // Check again in case it's been accessed which we waited for the lock... @@ -111,7 +111,7 @@ export class DiskCache { } } - private *entries(): Generator { + *#entries(): Generator { yield* inDirectory(this.#root); function* inDirectory(dir: string): Generator { @@ -130,7 +130,7 @@ export class Entry { public get atime(): Date { try { - const stat = statSync(this.markerFile); + const stat = statSync(this.#markerFile); return stat.atime; } catch (err: any) { if (err.code !== 'ENOENT') { @@ -144,17 +144,17 @@ export class Entry { return existsSync(this.path); } - private get lockFile(): string { + get #lockFile(): string { return `${this.path}.lock`; } - private get markerFile(): string { + get #markerFile(): string { return join(this.path, MARKER_FILE_NAME); } public lock(cb: (entry: LockedEntry) => T): T { mkdirSync(dirname(this.path), { recursive: true }); - lockSync(this.lockFile, { retries: 12, stale: 5_000 }); + lockSync(this.#lockFile, { retries: 12, stale: 5_000 }); let disposed = false; try { return cb({ @@ -186,18 +186,18 @@ export class Entry { ); } if (this.pathExists) { - if (existsSync(this.markerFile)) { + if (existsSync(this.#markerFile)) { const now = new Date(); - utimesSync(this.markerFile, now, now); + utimesSync(this.#markerFile, now, now); } else { - writeFileSync(this.markerFile, ''); + writeFileSync(this.#markerFile, ''); } } }, }); } finally { disposed = true; - unlockSync(this.lockFile); + unlockSync(this.#lockFile); } } diff --git a/packages/@jsii/kernel/src/kernel.ts b/packages/@jsii/kernel/src/kernel.ts index c8523c4102..201304e50c 100644 --- a/packages/@jsii/kernel/src/kernel.ts +++ b/packages/@jsii/kernel/src/kernel.ts @@ -1,5 +1,4 @@ import * as spec from '@jsii/spec'; -import { loadAssemblyFromPath } from '@jsii/spec'; import * as cp from 'child_process'; import * as fs from 'fs-extra'; import { createRequire } from 'module'; @@ -45,16 +44,19 @@ export class Kernel { */ public debugTimingEnabled = false; - private readonly assemblies = new Map(); - private readonly objects = new ObjectTable(this._typeInfoForFqn.bind(this)); - private readonly cbs = new Map(); - private readonly waiting = new Map(); - private readonly promises = new Map(); - private nextid = 20000; // incrementing counter for objid, cbid, promiseid - private syncInProgress?: string; // forbids async calls (begin) while processing sync calls (get/set/invoke) - private installDir?: string; + readonly #assemblies = new Map(); + readonly #objects = new ObjectTable(this.#typeInfoForFqn.bind(this)); + readonly #cbs = new Map(); + readonly #waiting = new Map(); + readonly #promises = new Map(); + + readonly #serializerHost: wire.SerializerHost; + + #nextid = 20000; // incrementing counter for objid, cbid, promiseid + #syncInProgress?: string; // forbids async calls (begin) while processing sync calls (get/set/invoke) + #installDir?: string; /** The internal require function, used instead of the global "require" so that webpack does not transform it... */ - private require?: typeof require; + #require?: typeof require; /** * Creates a jsii kernel object. @@ -63,17 +65,24 @@ export class Kernel { * It's responsibility is to execute the callback and return it's * result (or throw an error). */ - public constructor(public callbackHandler: (callback: api.Callback) => any) {} + public constructor(public callbackHandler: (callback: api.Callback) => any) { + this.#serializerHost = { + objects: this.#objects, + debug: this.#debug.bind(this), + findSymbol: this.#findSymbol.bind(this), + lookupType: this.#typeInfoForFqn.bind(this), + }; + } public load(req: api.LoadRequest): api.LoadResponse { - return this._debugTime( - () => this._load(req), + return this.#debugTime( + () => this.#load(req), `load(${JSON.stringify(req, null, 2)})`, ); } - private _load(req: api.LoadRequest): api.LoadResponse { - this._debug('load', req); + #load(req: api.LoadRequest): api.LoadResponse { + this.#debug('load', req); if ('assembly' in req) { throw new JsiiFault( @@ -85,7 +94,7 @@ export class Kernel { const pkgver = req.version; // check if we already have such a module - const packageDir = this._getPackageDir(pkgname); + const packageDir = this.#getPackageDir(pkgname); if (fs.pathExistsSync(packageDir)) { // module exists, verify version const epkg = fs.readJsonSync(path.join(packageDir, 'package.json')); @@ -98,8 +107,8 @@ export class Kernel { } // same version, no-op - this._debug('look up already-loaded assembly', pkgname); - const assm = this.assemblies.get(pkgname)!; + this.#debug('look up already-loaded assembly', pkgname); + const assm = this.#assemblies.get(pkgname)!; return { assembly: assm.metadata.name, @@ -111,7 +120,7 @@ export class Kernel { const originalUmask = process.umask(0o022); try { // untar the archive to its final location - const { cache } = this._debugTime( + const { cache } = this.#debugTime( () => tar.extract( req.tarball, @@ -128,7 +137,7 @@ export class Kernel { ); if (cache != null) { - this._debug( + this.#debug( `Package cache enabled, extraction resulted in a cache ${cache}`, ); } @@ -140,8 +149,8 @@ export class Kernel { // read .jsii metadata from the root of the package let assmSpec; try { - assmSpec = this._debugTime( - () => loadAssemblyFromPath(packageDir), + assmSpec = this.#debugTime( + () => spec.loadAssemblyFromPath(packageDir), `loadAssemblyFromPath(${packageDir})`, ); } catch (e: any) { @@ -151,13 +160,13 @@ export class Kernel { } // load the module and capture its closure - const closure = this._debugTime( - () => this.require!(packageDir), + const closure = this.#debugTime( + () => this.#require!(packageDir), `require(${packageDir})`, ); const assm = new Assembly(assmSpec, closure); - this._debugTime( - () => this._addAssembly(assm), + this.#debugTime( + () => this.#addAssembly(assm), `registerAssembly({ name: ${assm.metadata.name}, types: ${ Object.keys(assm.metadata.types ?? {}).length } })`, @@ -172,13 +181,13 @@ export class Kernel { public getBinScriptCommand( req: api.GetScriptCommandRequest, ): api.GetScriptCommandResponse { - return this._getBinScriptCommand(req); + return this.#getBinScriptCommand(req); } public invokeBinScript( req: api.InvokeScriptRequest, ): api.InvokeScriptResponse { - const { command, args, env } = this._getBinScriptCommand(req); + const { command, args, env } = this.#getBinScriptCommand(req); const result = cp.spawnSync(command, args, { encoding: 'utf-8', @@ -195,14 +204,14 @@ export class Kernel { } public create(req: api.CreateRequest): api.CreateResponse { - return this._create(req); + return this.#create(req); } public del(req: api.DelRequest): api.DelResponse { const { objref } = req; - this._debug('del', objref); - this.objects.deleteObject(objref); + this.#debug('del', objref); + this.#objects.deleteObject(objref); return {}; } @@ -210,31 +219,31 @@ export class Kernel { public sget(req: api.StaticGetRequest): api.GetResponse { const { fqn, property } = req; const symbol = `${fqn}.${property}`; - this._debug('sget', symbol); - const ti = this._typeInfoForProperty(property, fqn); + this.#debug('sget', symbol); + const ti = this.#typeInfoForProperty(property, fqn); if (!ti.static) { throw new JsiiFault(`property ${symbol} is not static`); } - const prototype = this._findSymbol(fqn); + const prototype = this.#findSymbol(fqn); - const value = this._ensureSync( + const value = this.#ensureSync( `property ${property}`, () => prototype[property], ); - this._debug('value:', value); - const ret = this._fromSandbox(value, ti, `of static property ${symbol}`); - this._debug('ret', ret); + this.#debug('value:', value); + const ret = this.#fromSandbox(value, ti, `of static property ${symbol}`); + this.#debug('ret', ret); return { value: ret }; } public sset(req: api.StaticSetRequest): api.SetResponse { const { fqn, property, value } = req; const symbol = `${fqn}.${property}`; - this._debug('sset', symbol); - const ti = this._typeInfoForProperty(property, fqn); + this.#debug('sset', symbol); + const ti = this.#typeInfoForProperty(property, fqn); if (!ti.static) { throw new JsiiFault(`property ${symbol} is not static`); @@ -244,12 +253,12 @@ export class Kernel { throw new JsiiFault(`static property ${symbol} is readonly`); } - const prototype = this._findSymbol(fqn); + const prototype = this.#findSymbol(fqn); - this._ensureSync( + this.#ensureSync( `property ${property}`, () => - (prototype[property] = this._toSandbox( + (prototype[property] = this.#toSandbox( value, ti, `assigned to static property ${symbol}`, @@ -261,35 +270,35 @@ export class Kernel { public get(req: api.GetRequest): api.GetResponse { const { objref, property } = req; - this._debug('get', objref, property); - const { instance, fqn, interfaces } = this.objects.findObject(objref); - const ti = this._typeInfoForProperty(property, fqn, interfaces); + this.#debug('get', objref, property); + const { instance, fqn, interfaces } = this.#objects.findObject(objref); + const ti = this.#typeInfoForProperty(property, fqn, interfaces); // if the property is overridden by the native code and "get" is called on the object, it // means that the native code is trying to access the "super" property. in order to enable // that, we actually keep a copy of the original property descriptor when we override, // so `findPropertyTarget` will return either the original property name ("property") or // the "super" property name (somehing like "$jsii$super$$"). - const propertyToGet = this._findPropertyTarget(instance, property); + const propertyToGet = this.#findPropertyTarget(instance, property); // make the actual "get", and block any async calls that might be performed // by jsii overrides. - const value = this._ensureSync( + const value = this.#ensureSync( `property '${objref[TOKEN_REF]}.${propertyToGet}'`, () => instance[propertyToGet], ); - this._debug('value:', value); - const ret = this._fromSandbox(value, ti, `of property ${fqn}.${property}`); - this._debug('ret:', ret); + this.#debug('value:', value); + const ret = this.#fromSandbox(value, ti, `of property ${fqn}.${property}`); + this.#debug('ret:', ret); return { value: ret }; } public set(req: api.SetRequest): api.SetResponse { const { objref, property, value } = req; - this._debug('set', objref, property, value); - const { instance, fqn, interfaces } = this.objects.findObject(objref); + this.#debug('set', objref, property, value); + const { instance, fqn, interfaces } = this.#objects.findObject(objref); - const propInfo = this._typeInfoForProperty(req.property, fqn, interfaces); + const propInfo = this.#typeInfoForProperty(req.property, fqn, interfaces); if (propInfo.immutable) { throw new JsiiFault( @@ -297,12 +306,12 @@ export class Kernel { ); } - const propertyToSet = this._findPropertyTarget(instance, property); + const propertyToSet = this.#findPropertyTarget(instance, property); - this._ensureSync( + this.#ensureSync( `property '${objref[TOKEN_REF]}.${propertyToSet}'`, () => - (instance[propertyToSet] = this._toSandbox( + (instance[propertyToSet] = this.#toSandbox( value, propInfo, `assigned to property ${fqn}.${property}`, @@ -316,8 +325,8 @@ export class Kernel { const { objref, method } = req; const args = req.args ?? []; - this._debug('invoke', objref, method, args); - const { ti, obj, fn } = this._findInvokeTarget(objref, method, args); + this.#debug('invoke', objref, method, args); + const { ti, obj, fn } = this.#findInvokeTarget(objref, method, args); // verify this is not an async method if (ti.async) { @@ -325,12 +334,12 @@ export class Kernel { } const fqn = jsiiTypeFqn(obj); - const ret = this._ensureSync( + const ret = this.#ensureSync( `method '${objref[TOKEN_REF]}.${method}'`, () => { return fn.apply( obj, - this._toSandboxValues( + this.#toSandboxValues( args, `method ${fqn ? `${fqn}#` : ''}${method}`, ti.parameters, @@ -339,12 +348,12 @@ export class Kernel { }, ); - const result = this._fromSandbox( + const result = this.#fromSandbox( ret, ti.returns ?? 'void', `returned by method ${fqn ? `${fqn}#` : ''}${method}`, ); - this._debug('invoke result', result); + this.#debug('invoke result', result); return { result }; } @@ -353,9 +362,9 @@ export class Kernel { const { fqn, method } = req; const args = req.args ?? []; - this._debug('sinvoke', fqn, method, args); + this.#debug('sinvoke', fqn, method, args); - const ti = this._typeInfoForMethod(method, fqn); + const ti = this.#typeInfoForMethod(method, fqn); if (!ti.static) { throw new JsiiFault(`${fqn}.${method} is not a static method`); @@ -366,13 +375,13 @@ export class Kernel { throw new JsiiFault(`${method} is an async method, use "begin" instead`); } - const prototype = this._findSymbol(fqn); + const prototype = this.#findSymbol(fqn); const fn = prototype[method] as (...params: any[]) => any; - const ret = this._ensureSync(`method '${fqn}.${method}'`, () => { + const ret = this.#ensureSync(`method '${fqn}.${method}'`, () => { return fn.apply( prototype, - this._toSandboxValues( + this.#toSandboxValues( args, `static method ${fqn}.${method}`, ti.parameters, @@ -380,9 +389,9 @@ export class Kernel { ); }); - this._debug('method returned:', ret); + this.#debug('method returned:', ret); return { - result: this._fromSandbox( + result: this.#fromSandbox( ret, ti.returns ?? 'void', `returned by static method ${fqn}.${method}`, @@ -394,15 +403,17 @@ export class Kernel { const { objref, method } = req; const args = req.args ?? []; - this._debug('begin', objref, method, args); + this.#debug('begin', objref, method, args); - if (this.syncInProgress) { + if (this.#syncInProgress) { throw new JsiiFault( - `Cannot invoke async method '${req.objref[TOKEN_REF]}.${req.method}' while sync ${this.syncInProgress} is being processed`, + `Cannot invoke async method '${req.objref[TOKEN_REF]}.${ + req.method + }' while sync ${this.#syncInProgress} is being processed`, ); } - const { ti, obj, fn } = this._findInvokeTarget(objref, method, args); + const { ti, obj, fn } = this.#findInvokeTarget(objref, method, args); // verify this is indeed an async method if (!ti.async) { @@ -413,7 +424,7 @@ export class Kernel { const promise = fn.apply( obj, - this._toSandboxValues( + this.#toSandboxValues( args, `async method ${fqn ? `${fqn}#` : ''}${method}`, ti.parameters, @@ -425,8 +436,8 @@ export class Kernel { // [1]: https://stackoverflow.com/questions/40920179/should-i-refrain-from-handling-promise-rejection-asynchronously/40921505 promise.catch((_) => undefined); - const prid = this._makeprid(); - this.promises.set(prid, { + const prid = this.#makeprid(); + this.#promises.set(prid, { promise, method: ti, }); @@ -437,9 +448,9 @@ export class Kernel { public async end(req: api.EndRequest): Promise { const { promiseid } = req; - this._debug('end', promiseid); + this.#debug('end', promiseid); - const storedPromise = this.promises.get(promiseid); + const storedPromise = this.#promises.get(promiseid); if (storedPromise == null) { throw new JsiiFault(`Cannot find promise with ID: ${promiseid}`); } @@ -448,9 +459,9 @@ export class Kernel { let result; try { result = await promise; - this._debug('promise result:', result); + this.#debug('promise result:', result); } catch (e: any) { - this._debug('promise error:', e); + this.#debug('promise error:', e); if (e.name === JsiiErrorType.JSII_FAULT) { if (e instanceof JsiiFault) { throw e; @@ -467,7 +478,7 @@ export class Kernel { } return { - result: this._fromSandbox( + result: this.#fromSandbox( result, method.returns ?? 'void', `returned by async method ${method.name}`, @@ -476,10 +487,10 @@ export class Kernel { } public callbacks(_req?: api.CallbacksRequest): api.CallbacksResponse { - this._debug('callbacks'); - const ret = Array.from(this.cbs.entries()).map(([cbid, cb]) => { - this.waiting.set(cbid, cb); // move to waiting - this.cbs.delete(cbid); // remove from created + this.#debug('callbacks'); + const ret = Array.from(this.#cbs.entries()).map(([cbid, cb]) => { + this.#waiting.set(cbid, cb); // move to waiting + this.#cbs.delete(cbid); // remove from created const callback: api.Callback = { cbid, cookie: cb.override.cookie, @@ -498,32 +509,32 @@ export class Kernel { public complete(req: api.CompleteRequest): api.CompleteResponse { const { cbid, err, result, name } = req; - this._debug('complete', cbid, err, result); + this.#debug('complete', cbid, err, result); - const cb = this.waiting.get(cbid); + const cb = this.#waiting.get(cbid); if (!cb) { throw new JsiiFault(`Callback ${cbid} not found`); } if (err) { - this._debug('completed with error:', err); + this.#debug('completed with error:', err); cb.fail( name === JsiiErrorType.JSII_FAULT ? new JsiiFault(err) : new RuntimeError(err), ); } else { - const sandoxResult = this._toSandbox( + const sandoxResult = this.#toSandbox( result, cb.expectedReturnType ?? 'void', // eslint-disable-next-line @typescript-eslint/no-base-to-string `returned by callback ${cb.toString()}`, ); - this._debug('completed with result:', sandoxResult); + this.#debug('completed with result:', sandoxResult); cb.succeed(sandoxResult); } - this.waiting.delete(cbid); + this.#waiting.delete(cbid); return { cbid }; } @@ -535,9 +546,9 @@ export class Kernel { public naming(req: api.NamingRequest): api.NamingResponse { const assemblyName = req.assembly; - this._debug('naming', assemblyName); + this.#debug('naming', assemblyName); - const assembly = this._assemblyFor(assemblyName); + const assembly = this.#assemblyFor(assemblyName); const targets = assembly.metadata.targets; if (!targets) { throw new JsiiFault( @@ -550,12 +561,12 @@ export class Kernel { public stats(_req?: api.StatsRequest): api.StatsResponse { return { - objectCount: this.objects.count, + objectCount: this.#objects.count, }; } - private _addAssembly(assm: Assembly) { - this.assemblies.set(assm.metadata.name, assm); + #addAssembly(assm: Assembly) { + this.#assemblies.set(assm.metadata.name, assm); // add the __jsii__.fqn property on every constructor. this allows // traversing between the javascript and jsii worlds given any object. @@ -566,14 +577,14 @@ export class Kernel { continue; // interfaces don't really exist case spec.TypeKind.Class: case spec.TypeKind.Enum: - const constructor = this._findSymbol(fqn); + const constructor = this.#findSymbol(fqn); tagJsiiConstructor(constructor, fqn); } } } // find the javascript constructor function for a jsii FQN. - private _findCtor( + #findCtor( fqn: string, args: any[], ): { ctor: any; parameters?: spec.Parameter[] } { @@ -581,14 +592,14 @@ export class Kernel { return { ctor: Object }; } - const typeinfo = this._typeInfoForFqn(fqn); + const typeinfo = this.#typeInfoForFqn(fqn); switch (typeinfo.kind) { case spec.TypeKind.Class: const classType = typeinfo as spec.ClassType; - this._validateMethodArguments(classType.initializer, args); + this.#validateMethodArguments(classType.initializer, args); return { - ctor: this._findSymbol(fqn), + ctor: this.#findSymbol(fqn), parameters: classType.initializer && classType.initializer.parameters, }; @@ -602,42 +613,40 @@ export class Kernel { } } - private _getPackageDir(pkgname: string): string { - if (!this.installDir) { - this.installDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsii-kernel-')); - this.require = createRequire(this.installDir); - fs.mkdirpSync(path.join(this.installDir, 'node_modules')); - this._debug('creating jsii-kernel modules workdir:', this.installDir); + #getPackageDir(pkgname: string): string { + if (!this.#installDir) { + this.#installDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsii-kernel-')); + this.#require = createRequire(this.#installDir); + fs.mkdirpSync(path.join(this.#installDir, 'node_modules')); + this.#debug('creating jsii-kernel modules workdir:', this.#installDir); - onExit.removeSync(this.installDir); + onExit.removeSync(this.#installDir); } - return path.join(this.installDir, 'node_modules', pkgname); + return path.join(this.#installDir, 'node_modules', pkgname); } - // prefixed with _ to allow calling this method internally without - // getting it recorded for testing. - private _create(req: api.CreateRequest): api.CreateResponse { - this._debug('create', req); + #create(req: api.CreateRequest): api.CreateResponse { + this.#debug('create', req); const { fqn, interfaces, overrides } = req; const requestArgs = req.args ?? []; - const ctorResult = this._findCtor(fqn, requestArgs); + const ctorResult = this.#findCtor(fqn, requestArgs); const ctor = ctorResult.ctor; const obj = new ctor( - ...this._toSandboxValues( + ...this.#toSandboxValues( requestArgs, `new ${fqn}`, ctorResult.parameters, ), ); - const objref = this.objects.registerObject(obj, fqn, req.interfaces ?? []); + const objref = this.#objects.registerObject(obj, fqn, req.interfaces ?? []); // overrides: for each one of the override method names, installs a // method on the newly created object which represents the remote "reverse proxy". if (overrides) { - this._debug('overrides', overrides); + this.#debug('overrides', overrides); const overrideTypeErrorMessage = 'Override can either be "method" or "property"'; @@ -656,7 +665,7 @@ export class Kernel { } methods.add(override.method); - this._applyMethodOverride(obj, objref, fqn, interfaces, override); + this.#applyMethodOverride(obj, objref, fqn, interfaces, override); } else if (api.isPropertyOverride(override)) { if (api.isMethodOverride(override)) { throw new JsiiFault(overrideTypeErrorMessage); @@ -668,7 +677,7 @@ export class Kernel { } properties.add(override.property); - this._applyPropertyOverride(obj, objref, fqn, interfaces, override); + this.#applyPropertyOverride(obj, objref, fqn, interfaces, override); } else { throw new JsiiFault(overrideTypeErrorMessage); } @@ -678,11 +687,11 @@ export class Kernel { return objref; } - private _getSuperPropertyName(name: string) { + #getSuperPropertyName(name: string) { return `$jsii$super$${name}$`; } - private _applyPropertyOverride( + #applyPropertyOverride( obj: any, objref: api.ObjRef, typeFqn: string, @@ -690,20 +699,20 @@ export class Kernel { override: api.PropertyOverride, ) { // error if we can find a method with this name - if (this._tryTypeInfoForMethod(override.property, typeFqn, interfaces)) { + if (this.#tryTypeInfoForMethod(override.property, typeFqn, interfaces)) { throw new JsiiFault( `Trying to override method '${override.property}' as a property`, ); } - let propInfo = this._tryTypeInfoForProperty( + let propInfo = this.#tryTypeInfoForProperty( override.property, typeFqn, interfaces, ); // if this is a private property (i.e. doesn't have `propInfo` the object has a key) if (!propInfo && override.property in obj) { - this._debug(`Skipping override of private property ${override.property}`); + this.#debug(`Skipping override of private property ${override.property}`); return; } @@ -720,10 +729,10 @@ export class Kernel { }; } - this._defineOverridenProperty(obj, objref, override, propInfo); + this.#defineOverridenProperty(obj, objref, override, propInfo); } - private _defineOverridenProperty( + #defineOverridenProperty( obj: any, objref: api.ObjRef, override: api.PropertyOverride, @@ -731,7 +740,7 @@ export class Kernel { ) { const propertyName = override.property; - this._debug('apply override', propertyName); + this.#debug('apply override', propertyName); // save the old property under $jsii$super$$ so that property overrides // can still access it via `super.`. @@ -744,7 +753,7 @@ export class Kernel { const prevEnumerable = prev.enumerable; prev.enumerable = false; - Object.defineProperty(obj, this._getSuperPropertyName(propertyName), prev); + Object.defineProperty(obj, this.#getSuperPropertyName(propertyName), prev); // we add callbacks for both 'get' and 'set', even if the property // is readonly. this is fine because if you try to set() a readonly @@ -753,32 +762,32 @@ export class Kernel { enumerable: prevEnumerable, configurable: prev.configurable, get: () => { - this._debug('virtual get', objref, propertyName, { + this.#debug('virtual get', objref, propertyName, { cookie: override.cookie, }); const result = this.callbackHandler({ cookie: override.cookie, - cbid: this._makecbid(), + cbid: this.#makecbid(), get: { objref, property: propertyName }, }); - this._debug('callback returned', result); - return this._toSandbox( + this.#debug('callback returned', result); + return this.#toSandbox( result, propInfo, `returned by callback property ${propertyName}`, ); }, set: (value: any) => { - this._debug('virtual set', objref, propertyName, { + this.#debug('virtual set', objref, propertyName, { cookie: override.cookie, }); this.callbackHandler({ cookie: override.cookie, - cbid: this._makecbid(), + cbid: this.#makecbid(), set: { objref, property: propertyName, - value: this._fromSandbox( + value: this.#fromSandbox( value, propInfo, `assigned to callback property ${propertyName}`, @@ -805,7 +814,7 @@ export class Kernel { } } - private _applyMethodOverride( + #applyMethodOverride( obj: any, objref: api.ObjRef, typeFqn: string, @@ -813,13 +822,13 @@ export class Kernel { override: api.MethodOverride, ) { // error if we can find a property with this name - if (this._tryTypeInfoForProperty(override.method, typeFqn, interfaces)) { + if (this.#tryTypeInfoForProperty(override.method, typeFqn, interfaces)) { throw new JsiiFault( `Trying to override property '${override.method}' as a method`, ); } - let methodInfo = this._tryTypeInfoForMethod( + let methodInfo = this.#tryTypeInfoForMethod( override.method, typeFqn, interfaces, @@ -828,7 +837,7 @@ export class Kernel { // If this is a private method (doesn't have methodInfo, key resolves on the object), we // are going to skip the override. if (!methodInfo && obj[override.method]) { - this._debug(`Skipping override of private method ${override.method}`); + this.#debug(`Skipping override of private method ${override.method}`); return; } @@ -850,10 +859,10 @@ export class Kernel { }; } - this._defineOverridenMethod(obj, objref, override, methodInfo); + this.#defineOverridenMethod(obj, objref, override, methodInfo); } - private _defineOverridenMethod( + #defineOverridenMethod( obj: any, objref: api.ObjRef, override: api.MethodOverride, @@ -872,16 +881,16 @@ export class Kernel { configurable: false, writable: false, value: (...methodArgs: any[]) => { - this._debug('invoke async method override', override); - const args = this._toSandboxValues( + this.#debug('invoke async method override', override); + const args = this.#toSandboxValues( methodArgs, methodContext, methodInfo.parameters, ); return new Promise((succeed, fail) => { - const cbid = this._makecbid(); - this._debug('adding callback to queue', cbid); - this.cbs.set(cbid, { + const cbid = this.#makecbid(); + this.#debug('adding callback to queue', cbid); + this.#cbs.set(cbid, { objref, override, args, @@ -899,7 +908,7 @@ export class Kernel { configurable: false, writable: false, value: (...methodArgs: any[]) => { - this._debug( + this.#debug( 'invoke sync method override', override, 'args', @@ -910,19 +919,19 @@ export class Kernel { // other end has done its work. const result = this.callbackHandler({ cookie: override.cookie, - cbid: this._makecbid(), + cbid: this.#makecbid(), invoke: { objref, method: methodName, - args: this._fromSandboxValues( + args: this.#fromSandboxValues( methodArgs, methodContext, methodInfo.parameters, ), }, }); - this._debug('Result', result); - return this._toSandbox( + this.#debug('Result', result); + return this.#toSandbox( result, methodInfo.returns ?? 'void', `returned by callback method ${methodName}`, @@ -932,14 +941,10 @@ export class Kernel { } } - private _findInvokeTarget( - objref: api.ObjRef, - methodName: string, - args: any[], - ) { - const { instance, fqn, interfaces } = this.objects.findObject(objref); - const ti = this._typeInfoForMethod(methodName, fqn, interfaces); - this._validateMethodArguments(ti, args); + #findInvokeTarget(objref: api.ObjRef, methodName: string, args: any[]) { + const { instance, fqn, interfaces } = this.#objects.findObject(objref); + const ti = this.#typeInfoForMethod(methodName, fqn, interfaces); + this.#validateMethodArguments(ti, args); // always first look up the method in the prototype. this practically bypasses // any methods overridden by derived classes (which are by definition native @@ -958,10 +963,7 @@ export class Kernel { return { ti, obj: instance, fn }; } - private _validateMethodArguments( - method: spec.Callable | undefined, - args: any[], - ) { + #validateMethodArguments(method: spec.Callable | undefined, args: any[]) { const params: spec.Parameter[] = method?.parameters ?? []; // error if args > params @@ -1000,17 +1002,17 @@ export class Kernel { } } - private _assemblyFor(assemblyName: string) { - const assembly = this.assemblies.get(assemblyName); + #assemblyFor(assemblyName: string) { + const assembly = this.#assemblies.get(assemblyName); if (!assembly) { throw new JsiiFault(`Could not find assembly: ${assemblyName}`); } return assembly; } - private _findSymbol(fqn: string) { + #findSymbol(fqn: string) { const [assemblyName, ...parts] = fqn.split('.'); - const assembly = this._assemblyFor(assemblyName); + const assembly = this.#assemblyFor(assemblyName); let curr = assembly.closure; while (parts.length > 0) { @@ -1027,11 +1029,11 @@ export class Kernel { return curr; } - private _typeInfoForFqn(fqn: string): spec.Type { + #typeInfoForFqn(fqn: string): spec.Type { const components = fqn.split('.'); const moduleName = components[0]; - const assembly = this.assemblies.get(moduleName); + const assembly = this.#assemblies.get(moduleName); if (!assembly) { throw new JsiiFault(`Module '${moduleName}' not found`); } @@ -1045,12 +1047,12 @@ export class Kernel { return fqnInfo; } - private _typeInfoForMethod( + #typeInfoForMethod( methodName: string, fqn: string, interfaces?: string[], ): spec.Method { - const ti = this._tryTypeInfoForMethod(methodName, fqn, interfaces); + const ti = this.#tryTypeInfoForMethod(methodName, fqn, interfaces); if (!ti) { const addendum = interfaces && interfaces.length > 0 @@ -1063,7 +1065,7 @@ export class Kernel { return ti; } - private _tryTypeInfoForMethod( + #tryTypeInfoForMethod( methodName: string, classFqn: string, interfaces: string[] = [], @@ -1072,7 +1074,7 @@ export class Kernel { if (fqn === wire.EMPTY_OBJECT_FQN) { continue; } - const typeinfo = this._typeInfoForFqn(fqn); + const typeinfo = this.#typeInfoForFqn(fqn); const methods = (typeinfo as spec.ClassType | spec.InterfaceType).methods ?? []; @@ -1093,7 +1095,7 @@ export class Kernel { continue; } - const found = this._tryTypeInfoForMethod(methodName, base); + const found = this.#tryTypeInfoForMethod(methodName, base); if (found) { return found; } @@ -1103,7 +1105,7 @@ export class Kernel { return undefined; } - private _tryTypeInfoForProperty( + #tryTypeInfoForProperty( property: string, classFqn: string, interfaces: string[] = [], @@ -1112,7 +1114,7 @@ export class Kernel { if (fqn === wire.EMPTY_OBJECT_FQN) { continue; } - const typeInfo = this._typeInfoForFqn(fqn); + const typeInfo = this.#typeInfoForFqn(fqn); let properties; let bases; @@ -1139,7 +1141,7 @@ export class Kernel { // recurse to parent type (if exists) for (const baseFqn of bases) { - const ret = this._tryTypeInfoForProperty(property, baseFqn); + const ret = this.#tryTypeInfoForProperty(property, baseFqn); if (ret) { return ret; } @@ -1149,12 +1151,12 @@ export class Kernel { return undefined; } - private _typeInfoForProperty( + #typeInfoForProperty( property: string, fqn: string, interfaces?: string[], ): spec.Property { - const typeInfo = this._tryTypeInfoForProperty(property, fqn, interfaces); + const typeInfo = this.#tryTypeInfoForProperty(property, fqn, interfaces); if (!typeInfo) { const addendum = interfaces && interfaces.length > 0 @@ -1166,18 +1168,13 @@ export class Kernel { } return typeInfo; } - private _toSandbox( + #toSandbox( v: any, expectedType: wire.OptionalValueOrVoid, context: string, ): any { return wire.process( - { - objects: this.objects, - debug: this._debug.bind(this), - findSymbol: this._findSymbol.bind(this), - lookupType: this._typeInfoForFqn.bind(this), - }, + this.#serializerHost, 'deserialize', v, expectedType, @@ -1185,18 +1182,13 @@ export class Kernel { ); } - private _fromSandbox( + #fromSandbox( v: any, targetType: wire.OptionalValueOrVoid, context: string, ): any { return wire.process( - { - objects: this.objects, - debug: this._debug.bind(this), - findSymbol: this._findSymbol.bind(this), - lookupType: this._typeInfoForFqn.bind(this), - }, + this.#serializerHost, 'serialize', v, targetType, @@ -1204,33 +1196,33 @@ export class Kernel { ); } - private _toSandboxValues( + #toSandboxValues( xs: readonly unknown[], methodContext: string, parameters: readonly spec.Parameter[] | undefined, ) { - return this._boxUnboxParameters( + return this.#boxUnboxParameters( xs, methodContext, parameters, - this._toSandbox.bind(this), + this.#toSandbox.bind(this), ); } - private _fromSandboxValues( + #fromSandboxValues( xs: readonly unknown[], methodContext: string, parameters: readonly spec.Parameter[] | undefined, ) { - return this._boxUnboxParameters( + return this.#boxUnboxParameters( xs, methodContext, parameters, - this._fromSandbox.bind(this), + this.#fromSandbox.bind(this), ); } - private _boxUnboxParameters( + #boxUnboxParameters( xs: readonly unknown[], methodContext: string, parameters: readonly spec.Parameter[] = [], @@ -1262,13 +1254,13 @@ export class Kernel { ); } - private _debug(...args: any[]) { + #debug(...args: any[]) { if (this.traceEnabled) { console.error('[@jsii/kernel]', ...args); } } - private _debugTime(cb: () => T, label: string): T { + #debugTime(cb: () => T, label: string): T { const fullLabel = `[@jsii/kernel:timing] ${label}`; if (this.debugTimingEnabled) { console.time(fullLabel); @@ -1286,8 +1278,8 @@ export class Kernel { * Ensures that `fn` is called and defends against beginning to invoke * async methods until fn finishes (successfully or not). */ - private _ensureSync(desc: string, fn: () => T): T { - this.syncInProgress = desc; + #ensureSync(desc: string, fn: () => T): T { + this.#syncInProgress = desc; try { return fn(); } catch (e: any) { @@ -1305,12 +1297,12 @@ export class Kernel { } throw new RuntimeError(e); } finally { - delete this.syncInProgress; + this.#syncInProgress = undefined; } } - private _findPropertyTarget(obj: any, property: string) { - const superProp = this._getSuperPropertyName(property); + #findPropertyTarget(obj: any, property: string) { + const superProp = this.#getSuperPropertyName(property); if (superProp in obj) { return superProp; } @@ -1320,10 +1312,10 @@ export class Kernel { /** * Shared (non-public implementation) to as not to break API recording. */ - private _getBinScriptCommand( + #getBinScriptCommand( req: api.GetScriptCommandRequest, ): api.GetScriptCommandResponse { - const packageDir = this._getPackageDir(req.assembly); + const packageDir = this.#getPackageDir(req.assembly); if (fs.pathExistsSync(packageDir)) { // module exists, verify version const epkg = fs.readJsonSync(path.join(packageDir, 'package.json')); @@ -1353,12 +1345,12 @@ export class Kernel { // type information // - private _makecbid() { - return `jsii::callback::${this.nextid++}`; + #makecbid() { + return `jsii::callback::${this.#nextid++}`; } - private _makeprid() { - return `jsii::promise::${this.nextid++}`; + #makeprid() { + return `jsii::promise::${this.#nextid++}`; } } diff --git a/packages/@jsii/kernel/src/objects.ts b/packages/@jsii/kernel/src/objects.ts index ea7d78fd76..7c5ce1d3cd 100644 --- a/packages/@jsii/kernel/src/objects.ts +++ b/packages/@jsii/kernel/src/objects.ts @@ -119,12 +119,13 @@ export function tagJsiiConstructor(constructor: any, fqn: string) { * type. */ export class ObjectTable { - private readonly objects = new Map(); - private nextid = 10000; + readonly #resolveType: (fqn: string) => spec.Type; + readonly #objects = new Map(); + #nextid = 10000; - public constructor( - private readonly resolveType: (fqn: string) => spec.Type, - ) {} + public constructor(resolveType: (fqn: string) => spec.Type) { + this.#resolveType = resolveType; + } /** * Register the given object with the given type @@ -157,19 +158,19 @@ export class ObjectTable { ); } - this.objects.get(existingRef[api.TOKEN_REF])!.interfaces = + this.#objects.get(existingRef[api.TOKEN_REF])!.interfaces = (obj as any)[IFACES_SYMBOL] = existingRef[api.TOKEN_INTERFACES] = interfaces = - this.removeRedundant(Array.from(allIfaces), fqn); + this.#removeRedundant(Array.from(allIfaces), fqn); } return existingRef; } - interfaces = this.removeRedundant(interfaces, fqn); + interfaces = this.#removeRedundant(interfaces, fqn); - const objid = this.makeId(fqn); - this.objects.set(objid, { instance: obj, fqn, interfaces }); + const objid = this.#makeId(fqn); + this.#objects.set(objid, { instance: obj, fqn, interfaces }); tagObject(obj, objid, interfaces); return { [api.TOKEN_REF]: objid, [api.TOKEN_INTERFACES]: interfaces }; @@ -186,7 +187,7 @@ export class ObjectTable { } const objid = objref[api.TOKEN_REF]; - const obj = this.objects.get(objid); + const obj = this.#objects.get(objid); if (!obj) { throw new JsiiFault(`Object ${objid} not found`); } @@ -217,20 +218,20 @@ export class ObjectTable { * Delete the registration with the given objref */ public deleteObject({ [api.TOKEN_REF]: objid }: api.ObjRef) { - if (!this.objects.delete(objid)) { + if (!this.#objects.delete(objid)) { throw new JsiiFault(`Object ${objid} not found`); } } public get count(): number { - return this.objects.size; + return this.#objects.size; } - private makeId(fqn: string) { - return `${fqn}@${this.nextid++}`; + #makeId(fqn: string) { + return `${fqn}@${this.#nextid++}`; } - private removeRedundant( + #removeRedundant( interfaces: string[] | undefined, fqn: string, ): string[] | undefined { @@ -239,7 +240,7 @@ export class ObjectTable { } const result = new Set(interfaces); - const builtIn = new InterfaceCollection(this.resolveType); + const builtIn = new InterfaceCollection(this.#resolveType); if (fqn !== EMPTY_OBJECT_FQN) { builtIn.addFromClass(fqn); @@ -261,14 +262,15 @@ export interface RegisteredObject { } class InterfaceCollection implements Iterable { - private readonly interfaces = new Set(); + readonly #resolveType: (fqn: string) => spec.Type; + readonly #interfaces = new Set(); - public constructor( - private readonly resolveType: (fqn: string) => spec.Type, - ) {} + public constructor(resolveType: (fqn: string) => spec.Type) { + this.#resolveType = resolveType; + } public addFromClass(fqn: string): void { - const ti = this.resolveType(fqn); + const ti = this.#resolveType(fqn); if (!spec.isClassType(ti)) { throw new JsiiFault( `Expected a class, but received ${spec.describeTypeReference(ti)}`, @@ -279,17 +281,17 @@ class InterfaceCollection implements Iterable { } if (ti.interfaces) { for (const iface of ti.interfaces) { - if (this.interfaces.has(iface)) { + if (this.#interfaces.has(iface)) { continue; } - this.interfaces.add(iface); + this.#interfaces.add(iface); this.addFromInterface(iface); } } } public addFromInterface(fqn: string): void { - const ti = this.resolveType(fqn); + const ti = this.#resolveType(fqn); if (!spec.isInterfaceType(ti)) { throw new JsiiFault( `Expected an interface, but received ${spec.describeTypeReference(ti)}`, @@ -299,15 +301,15 @@ class InterfaceCollection implements Iterable { return; } for (const iface of ti.interfaces) { - if (this.interfaces.has(iface)) { + if (this.#interfaces.has(iface)) { continue; } - this.interfaces.add(iface); + this.#interfaces.add(iface); this.addFromInterface(iface); } } public [Symbol.iterator]() { - return this.interfaces[Symbol.iterator](); + return this.#interfaces[Symbol.iterator](); } } diff --git a/packages/@jsii/kernel/src/serialization.test.ts b/packages/@jsii/kernel/src/serialization.test.ts index 3ed3a510f5..f7e47cf6ff 100644 --- a/packages/@jsii/kernel/src/serialization.test.ts +++ b/packages/@jsii/kernel/src/serialization.test.ts @@ -34,10 +34,10 @@ const host: SerializerHost = { describe(SerializationClass.Any, () => { const anySerializer = SERIALIZERS[SerializationClass.Any]; class PrivateType { - private readonly randomValue = Math.random(); + readonly #randomValue = Math.random(); public random() { - return this.randomValue; + return this.#randomValue; } }