diff --git a/release-bin/cfn-validate-amazonlinux-aarch64 b/release-bin/cfn-validate-amazonlinux-aarch64 new file mode 100644 index 00000000..a998e797 Binary files /dev/null and b/release-bin/cfn-validate-amazonlinux-aarch64 differ diff --git a/release-bin/cfn-validate-amazonlinux-x64 b/release-bin/cfn-validate-amazonlinux-x64 new file mode 100644 index 00000000..4fb2a2bf Binary files /dev/null and b/release-bin/cfn-validate-amazonlinux-x64 differ diff --git a/release-bin/cfn-validate-darwin-aarch64 b/release-bin/cfn-validate-darwin-aarch64 index 105be8d4..a9c96b1a 100644 Binary files a/release-bin/cfn-validate-darwin-aarch64 and b/release-bin/cfn-validate-darwin-aarch64 differ diff --git a/release-bin/cfn-validate-darwin-x64 b/release-bin/cfn-validate-darwin-x64 index 23ba5ced..eeae5ba8 100644 Binary files a/release-bin/cfn-validate-darwin-x64 and b/release-bin/cfn-validate-darwin-x64 differ diff --git a/release-bin/cfn-validate-linux-aarch64 b/release-bin/cfn-validate-linux-aarch64 index 9d12e8b5..03f5ad27 100644 Binary files a/release-bin/cfn-validate-linux-aarch64 and b/release-bin/cfn-validate-linux-aarch64 differ diff --git a/release-bin/cfn-validate-linux-x64 b/release-bin/cfn-validate-linux-x64 index fd9ec8d3..fade6b6e 100644 Binary files a/release-bin/cfn-validate-linux-x64 and b/release-bin/cfn-validate-linux-x64 differ diff --git a/release-bin/cfn-validate-win32-aarch64.exe b/release-bin/cfn-validate-win32-aarch64.exe index 9d1e83cc..89b84ee3 100644 Binary files a/release-bin/cfn-validate-win32-aarch64.exe and b/release-bin/cfn-validate-win32-aarch64.exe differ diff --git a/release-bin/cfn-validate-win32-x64.exe b/release-bin/cfn-validate-win32-x64.exe index dfc690b2..407243dc 100644 Binary files a/release-bin/cfn-validate-win32-x64.exe and b/release-bin/cfn-validate-win32-x64.exe differ diff --git a/src/THIRD-PARTY-LICENSES.txt b/src/THIRD-PARTY-LICENSES.txt index ec5007f4..35057388 100644 --- a/src/THIRD-PARTY-LICENSES.txt +++ b/src/THIRD-PARTY-LICENSES.txt @@ -19849,7 +19849,7 @@ limitations under the License. ****************************** ruzstd -0.8.3 +0.9.1 MIT License Copyright (c) 2019 Moritz Borcherding diff --git a/src/bindings-go/go/THIRD-PARTY-LICENSES.txt b/src/bindings-go/go/THIRD-PARTY-LICENSES.txt index 6b120d91..bf542c25 100644 --- a/src/bindings-go/go/THIRD-PARTY-LICENSES.txt +++ b/src/bindings-go/go/THIRD-PARTY-LICENSES.txt @@ -11659,7 +11659,7 @@ limitations under the License. ****************************** ruzstd -0.8.3 +0.9.1 MIT License Copyright (c) 2019 Moritz Borcherding diff --git a/src/bindings-go/go/libs/darwin-aarch64/libbindings_go.a b/src/bindings-go/go/libs/darwin-aarch64/libbindings_go.a index 27b9bcde..2688282f 100644 Binary files a/src/bindings-go/go/libs/darwin-aarch64/libbindings_go.a and b/src/bindings-go/go/libs/darwin-aarch64/libbindings_go.a differ diff --git a/src/bindings-go/go/libs/darwin-x86-64/libbindings_go.a b/src/bindings-go/go/libs/darwin-x86-64/libbindings_go.a index 99cadc8a..b88a9d5d 100644 Binary files a/src/bindings-go/go/libs/darwin-x86-64/libbindings_go.a and b/src/bindings-go/go/libs/darwin-x86-64/libbindings_go.a differ diff --git a/src/bindings-go/go/libs/linux-aarch64/libbindings_go.a b/src/bindings-go/go/libs/linux-aarch64/libbindings_go.a index 2e3625b7..40b20a83 100644 Binary files a/src/bindings-go/go/libs/linux-aarch64/libbindings_go.a and b/src/bindings-go/go/libs/linux-aarch64/libbindings_go.a differ diff --git a/src/bindings-go/go/libs/linux-x86-64/libbindings_go.a b/src/bindings-go/go/libs/linux-x86-64/libbindings_go.a index 32781646..182b043b 100644 Binary files a/src/bindings-go/go/libs/linux-x86-64/libbindings_go.a and b/src/bindings-go/go/libs/linux-x86-64/libbindings_go.a differ diff --git a/src/bindings-go/go/libs/win32-x86-64/libbindings_go.a b/src/bindings-go/go/libs/win32-x86-64/libbindings_go.a index 1a73284b..7ba012ac 100644 Binary files a/src/bindings-go/go/libs/win32-x86-64/libbindings_go.a and b/src/bindings-go/go/libs/win32-x86-64/libbindings_go.a differ diff --git a/src/bindings-jvm/THIRD-PARTY-LICENSES.txt b/src/bindings-jvm/THIRD-PARTY-LICENSES.txt index 33157355..42080e5f 100644 --- a/src/bindings-jvm/THIRD-PARTY-LICENSES.txt +++ b/src/bindings-jvm/THIRD-PARTY-LICENSES.txt @@ -14156,7 +14156,7 @@ limitations under the License. ****************************** ruzstd -0.8.3 +0.9.1 MIT License Copyright (c) 2019 Moritz Borcherding diff --git a/src/bindings-jvm/generated/cloudformation-validate.jar b/src/bindings-jvm/generated/cloudformation-validate.jar index 6f6e23d7..7d56d0a1 100644 Binary files a/src/bindings-jvm/generated/cloudformation-validate.jar and b/src/bindings-jvm/generated/cloudformation-validate.jar differ diff --git a/src/bindings-python/THIRD-PARTY-LICENSES.txt b/src/bindings-python/THIRD-PARTY-LICENSES.txt index 3ec33acc..07714650 100644 --- a/src/bindings-python/THIRD-PARTY-LICENSES.txt +++ b/src/bindings-python/THIRD-PARTY-LICENSES.txt @@ -11659,7 +11659,7 @@ limitations under the License. ****************************** ruzstd -0.8.3 +0.9.1 MIT License Copyright (c) 2019 Moritz Borcherding diff --git a/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-macosx_10_12_x86_64.whl b/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-macosx_10_12_x86_64.whl index 8e254931..b5f48669 100644 Binary files a/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-macosx_10_12_x86_64.whl and b/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-macosx_10_12_x86_64.whl differ diff --git a/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-macosx_11_0_arm64.whl b/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-macosx_11_0_arm64.whl index 79b8acee..8aad7bbc 100644 Binary files a/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-macosx_11_0_arm64.whl and b/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-macosx_11_0_arm64.whl differ diff --git a/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-manylinux_2_34_aarch64.whl b/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-manylinux_2_34_aarch64.whl index 41f2d516..472baa32 100644 Binary files a/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-manylinux_2_34_aarch64.whl and b/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-manylinux_2_34_aarch64.whl differ diff --git a/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-manylinux_2_34_x86_64.whl b/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-manylinux_2_34_x86_64.whl index 3c039cfd..ec64d181 100644 Binary files a/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-manylinux_2_34_x86_64.whl and b/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-manylinux_2_34_x86_64.whl differ diff --git a/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-win_amd64.whl b/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-win_amd64.whl index be701910..12c96968 100644 Binary files a/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-win_amd64.whl and b/src/bindings-python/generated/dist/cloudformation_validate-1.14.0-py3-none-win_amd64.whl differ diff --git a/src/bindings-wasm/THIRD-PARTY-LICENSES.txt b/src/bindings-wasm/THIRD-PARTY-LICENSES.txt index 7eaf92f7..91e504fe 100644 --- a/src/bindings-wasm/THIRD-PARTY-LICENSES.txt +++ b/src/bindings-wasm/THIRD-PARTY-LICENSES.txt @@ -10439,7 +10439,7 @@ Some files are licensed Apache-2.0 (LICENSE-2.0.txt). ****************************** ruzstd -0.8.3 +0.9.1 MIT License Copyright (c) 2019 Moritz Borcherding diff --git a/src/bindings-wasm/dist/THIRD-PARTY-LICENSES.txt b/src/bindings-wasm/dist/THIRD-PARTY-LICENSES.txt index 7eaf92f7..91e504fe 100644 --- a/src/bindings-wasm/dist/THIRD-PARTY-LICENSES.txt +++ b/src/bindings-wasm/dist/THIRD-PARTY-LICENSES.txt @@ -10439,7 +10439,7 @@ Some files are licensed Apache-2.0 (LICENSE-2.0.txt). ****************************** ruzstd -0.8.3 +0.9.1 MIT License Copyright (c) 2019 Moritz Borcherding diff --git a/src/bindings-wasm/dist/bindings_wasm.d.ts b/src/bindings-wasm/dist/bindings_wasm.d.ts index 03bc88e8..ae99b4ea 100644 --- a/src/bindings-wasm/dist/bindings_wasm.d.ts +++ b/src/bindings-wasm/dist/bindings_wasm.d.ts @@ -485,9 +485,9 @@ export interface ResolvedResource { metadata?: JsonValue; properties: Record; /** - * True when the entire `Properties` block is a non-map intrinsic (e.g. - * `Properties: !Ref AWS::NoValue`) whose effective property set is only known - * at deploy time, as distinct from a resource that simply declares no properties. + * True when a resource-level or property-set macro can determine the + * effective property set only at deploy time, or when a non-map + * `Properties` intrinsic has no statically inspectable property map. */ propertiesDynamic: boolean; diagnostics: ResourceDiagnostics; @@ -1178,6 +1178,19 @@ export class WasmSemanticModel { transforms(): any; } +/** + * Number of export bodies that were entered and never returned. The host + * reads it after a failed call to tell a returned error from an interrupted + * call that left the instance inconsistent. + */ +export function activeCalls(): number; + export function init(): void; +/** + * Registers the host callback that receives panic messages. A panic still + * traps the instance; the callback only lets the host report why. + */ +export function setPanicListener(listener: (message: string) => void): void; + export function version(): string; diff --git a/src/bindings-wasm/dist/bindings_wasm.js b/src/bindings-wasm/dist/bindings_wasm.js index fa1e2429..21822338 100644 --- a/src/bindings-wasm/dist/bindings_wasm.js +++ b/src/bindings-wasm/dist/bindings_wasm.js @@ -15,15 +15,22 @@ class WasmCelEngine { * @returns {string} */ engineName() { - let deferred1_0; - let deferred1_1; + let deferred2_0; + let deferred2_1; try { const ret = wasm.wasmcelengine_engineName(this.__wbg_ptr); - deferred1_0 = ret[0]; - deferred1_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); + var ptr1 = ret[0]; + var len1 = ret[1]; + if (ret[3]) { + ptr1 = 0; + len1 = 0; + throw takeFromExternrefTable0(ret[2]); + } + deferred2_0 = ptr1; + deferred2_1 = len1; + return getStringFromWasm0(ptr1, len1); } finally { - wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); } } /** @@ -95,15 +102,22 @@ class WasmCompositeEngine { * @returns {string} */ engineName() { - let deferred1_0; - let deferred1_1; + let deferred2_0; + let deferred2_1; try { const ret = wasm.wasmcompositeengine_engineName(this.__wbg_ptr); - deferred1_0 = ret[0]; - deferred1_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); + var ptr1 = ret[0]; + var len1 = ret[1]; + if (ret[3]) { + ptr1 = 0; + len1 = 0; + throw takeFromExternrefTable0(ret[2]); + } + deferred2_0 = ptr1; + deferred2_1 = len1; + return getStringFromWasm0(ptr1, len1); } finally { - wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); } } /** @@ -175,15 +189,22 @@ class WasmRegoEngine { * @returns {string} */ engineName() { - let deferred1_0; - let deferred1_1; + let deferred2_0; + let deferred2_1; try { const ret = wasm.wasmregoengine_engineName(this.__wbg_ptr); - deferred1_0 = ret[0]; - deferred1_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); + var ptr1 = ret[0]; + var len1 = ret[1]; + if (ret[3]) { + ptr1 = 0; + len1 = 0; + throw takeFromExternrefTable0(ret[2]); + } + deferred2_0 = ptr1; + deferred2_1 = len1; + return getStringFromWasm0(ptr1, len1); } finally { - wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); } } /** @@ -278,7 +299,10 @@ class WasmSchemaValidator { */ schemaCount() { const ret = wasm.wasmschemavalidator_schemaCount(this.__wbg_ptr); - return ret >>> 0; + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return ret[0] >>> 0; } /** * @param {WasmSemanticModel} model @@ -331,6 +355,9 @@ class WasmSemanticModel { */ description() { const ret = wasm.wasmsemanticmodel_description(this.__wbg_ptr); + if (ret[3]) { + throw takeFromExternrefTable0(ret[2]); + } let v1; if (ret[0] !== 0) { v1 = getStringFromWasm0(ret[0], ret[1]).slice(); @@ -343,6 +370,9 @@ class WasmSemanticModel { */ formatVersion() { const ret = wasm.wasmsemanticmodel_formatVersion(this.__wbg_ptr); + if (ret[3]) { + throw takeFromExternrefTable0(ret[2]); + } let v1; if (ret[0] !== 0) { v1 = getStringFromWasm0(ret[0], ret[1]).slice(); @@ -430,24 +460,53 @@ class WasmSemanticModel { if (Symbol.dispose) WasmSemanticModel.prototype[Symbol.dispose] = WasmSemanticModel.prototype.free; exports.WasmSemanticModel = WasmSemanticModel; +/** + * Number of export bodies that were entered and never returned. The host + * reads it after a failed call to tell a returned error from an interrupted + * call that left the instance inconsistent. + * @returns {number} + */ +function activeCalls() { + const ret = wasm.activeCalls(); + return ret >>> 0; +} +exports.activeCalls = activeCalls; + function init() { wasm.init(); } exports.init = init; +/** + * Registers the host callback that receives panic messages. A panic still + * traps the instance; the callback only lets the host report why. + * @param {(message: string) => void} listener + */ +function setPanicListener(listener) { + wasm.setPanicListener(listener); +} +exports.setPanicListener = setPanicListener; + /** * @returns {string} */ function version() { - let deferred1_0; - let deferred1_1; + let deferred2_0; + let deferred2_1; try { const ret = wasm.version(); - deferred1_0 = ret[0]; - deferred1_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); + var ptr1 = ret[0]; + var len1 = ret[1]; + if (ret[3]) { + ptr1 = 0; + len1 = 0; + throw takeFromExternrefTable0(ret[2]); + } + deferred2_0 = ptr1; + deferred2_1 = len1; + return getStringFromWasm0(ptr1, len1); } finally { - wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); } } exports.version = version; @@ -537,6 +596,12 @@ function __wbg_get_imports() { __wbg___wbindgen_throw_344f42d3211c4765: function (arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)); }, + __wbg_call_774748efc4f5502f: function () { + return handleError(function (arg0, arg1, arg2, arg3) { + const ret = arg0.call(arg1, getStringFromWasm0(arg2, arg3)); + return ret; + }, arguments); + }, __wbg_call_8a2dd23819f8a60a: function () { return handleError(function (arg0, arg1) { const ret = arg0.call(arg1); diff --git a/src/bindings-wasm/dist/bindings_wasm_bg.wasm b/src/bindings-wasm/dist/bindings_wasm_bg.wasm index 00e97ea1..f9d8e4c9 100644 Binary files a/src/bindings-wasm/dist/bindings_wasm_bg.wasm and b/src/bindings-wasm/dist/bindings_wasm_bg.wasm differ diff --git a/src/bindings-wasm/dist/bindings_wasm_bg.wasm.d.ts b/src/bindings-wasm/dist/bindings_wasm_bg.wasm.d.ts index 8ec2bea5..78ece876 100644 --- a/src/bindings-wasm/dist/bindings_wasm_bg.wasm.d.ts +++ b/src/bindings-wasm/dist/bindings_wasm_bg.wasm.d.ts @@ -6,9 +6,11 @@ export const __wbg_wasmcompositeengine_free: (a: number, b: number) => void; export const __wbg_wasmregoengine_free: (a: number, b: number) => void; export const __wbg_wasmschemavalidator_free: (a: number, b: number) => void; export const __wbg_wasmsemanticmodel_free: (a: number, b: number) => void; +export const activeCalls: () => number; export const init: () => void; -export const version: () => [number, number]; -export const wasmcelengine_engineName: (a: number) => [number, number]; +export const setPanicListener: (a: any) => void; +export const version: () => [number, number, number, number]; +export const wasmcelengine_engineName: (a: number) => [number, number, number, number]; export const wasmcelengine_listRules: (a: number) => [number, number, number]; export const wasmcelengine_new: (a: any) => [number, number, number]; export const wasmcelengine_validateAwsCliCommand: (a: number, b: any) => [number, number, number]; @@ -20,7 +22,7 @@ export const wasmcelengine_validateTemplate: ( e: number, f: number, ) => [number, number, number]; -export const wasmcompositeengine_engineName: (a: number) => [number, number]; +export const wasmcompositeengine_engineName: (a: number) => [number, number, number, number]; export const wasmcompositeengine_listRules: (a: number) => [number, number, number]; export const wasmcompositeengine_new: (a: any) => [number, number, number]; export const wasmcompositeengine_validateAwsCliCommand: (a: number, b: any) => [number, number, number]; @@ -32,7 +34,7 @@ export const wasmcompositeengine_validateTemplate: ( e: number, f: number, ) => [number, number, number]; -export const wasmregoengine_engineName: (a: number) => [number, number]; +export const wasmregoengine_engineName: (a: number) => [number, number, number, number]; export const wasmregoengine_listRules: (a: number) => [number, number, number]; export const wasmregoengine_new: (a: any) => [number, number, number]; export const wasmregoengine_validateAwsCliCommand: (a: number, b: any) => [number, number, number]; @@ -46,11 +48,11 @@ export const wasmregoengine_validateTemplate: ( ) => [number, number, number]; export const wasmschemavalidator_listRules: (a: number) => [number, number, number]; export const wasmschemavalidator_new: (a: any) => [number, number, number]; -export const wasmschemavalidator_schemaCount: (a: number) => number; +export const wasmschemavalidator_schemaCount: (a: number) => [number, number, number]; export const wasmschemavalidator_validate: (a: number, b: number, c: number, d: number) => [number, number, number]; export const wasmsemanticmodel_conditions: (a: number) => [number, number, number]; -export const wasmsemanticmodel_description: (a: number) => [number, number]; -export const wasmsemanticmodel_formatVersion: (a: number) => [number, number]; +export const wasmsemanticmodel_description: (a: number) => [number, number, number, number]; +export const wasmsemanticmodel_formatVersion: (a: number) => [number, number, number, number]; export const wasmsemanticmodel_outputs: (a: number) => [number, number, number]; export const wasmsemanticmodel_parameters: (a: number) => [number, number, number]; export const wasmsemanticmodel_parse: (a: number, b: number) => [number, number, number]; diff --git a/src/bindings-wasm/dist/index.d.ts b/src/bindings-wasm/dist/index.d.ts index c864aa30..433b9146 100644 --- a/src/bindings-wasm/dist/index.d.ts +++ b/src/bindings-wasm/dist/index.d.ts @@ -227,7 +227,7 @@ export interface SchemaValidatorConfig { additionalSchemas?: SchemaSource[]; } export declare class TemplateModel { - private readonly inner; + private readonly owned; constructor(template: Template); resources(): Record; parameters(): Record; @@ -241,7 +241,8 @@ export declare class TemplateModel { free(): void; } export declare class SchemaValidator { - private readonly inner; + private readonly instance; + private readonly owned; constructor(config?: SchemaValidatorConfig); listRules(): RuleInfo[]; schemaCount(): number; diff --git a/src/bindings-wasm/dist/index.js b/src/bindings-wasm/dist/index.js index 33d931de..af508d29 100644 --- a/src/bindings-wasm/dist/index.js +++ b/src/bindings-wasm/dist/index.js @@ -200,7 +200,188 @@ function fromWireAwsCliCommandValidation(validation) { template: template == null ? null : Uint8Array.from(template), }; } -const bridge = require('./bindings_wasm'); +/** The error a WebAssembly trap raises; the configured TypeScript libraries do not declare it. */ +const WasmRuntimeError = globalThis.WebAssembly.RuntimeError; +/** Errors the instance guard raised itself, rethrown unchanged by enclosing guards. */ +const guardErrors = new WeakSet(); +const INTERNAL_ERROR_PREFIX = 'Internal validation error'; +function guardError(message) { + const error = new WasmRuntimeError(message); + guardErrors.add(error); + return error; +} +function isTrap(error) { + return error instanceof WasmRuntimeError || (error instanceof RangeError && /call stack/i.test(error.message)); +} +/** + * One evaluation of the generated glue and the WebAssembly instance it created. + * + * A Rust panic, an allocation failure, or a stack overflow traps the instance + * mid-call without running Rust cleanup, so locks, borrows, and the shadow + * stack can be left inconsistent. The first such failure terminates the + * instance: every later call on an object it owns throws instead of running + * on that state, and objects constructed afterwards use a fresh instance. + */ +class WasmInstance { + constructor(bridge) { + this.bridge = bridge; + bridge.setPanicListener((message) => { + this.panicMessage = message; + }); + } + get terminated() { + return this.terminationReason !== undefined; + } + /** Runs one call into the instance; terminates it if the call did not return through Rust. */ + call(operation) { + if (this.terminationReason !== undefined) { + throw guardError( + `${INTERNAL_ERROR_PREFIX}: the WebAssembly instance was terminated by an earlier failure ` + + `(${this.terminationReason}); construct a new object to continue`, + ); + } + this.panicMessage = undefined; + try { + return operation(this.bridge); + } catch (error) { + if (typeof error === 'object' && error !== null && guardErrors.has(error)) { + throw error; + } + if (this.panicMessage === undefined && !isTrap(error) && !this.interrupted()) { + throw error; + } + const reason = this.panicMessage ?? (error instanceof Error ? error.message : String(error)); + this.terminate(reason); + if (this.panicMessage === undefined && !isTrap(error)) { + // A host runtime hook interrupted generated deserialization: preserve + // the original exception while preventing reuse of the abandoned frame. + throw error; + } + const failure = guardError(`${INTERNAL_ERROR_PREFIX}: ${reason}`); + Object.defineProperty(failure, 'cause', { value: error, configurable: true, writable: true }); + throw failure; + } finally { + this.panicMessage = undefined; + } + } + interrupted() { + try { + return this.bridge.activeCalls() !== 0; + } catch { + return true; + } + } + terminate(reason) { + this.terminationReason ?? (this.terminationReason = reason); + if (activeInstance === this) { + activeInstance = undefined; + } + } +} +const loadedBridges = new WeakSet(); +function resolveBridgeCacheKey() { + try { + return require.resolve('./bindings_wasm'); + } catch { + return undefined; + } +} +function evictBridgeModule() { + const bridgeCacheKey = resolveBridgeCacheKey(); + if (bridgeCacheKey === undefined) { + return undefined; + } + const staleBridge = require.cache?.[bridgeCacheKey]; + if (staleBridge === undefined) { + return undefined; + } + delete require.cache[bridgeCacheKey]; + const childModules = module.children; + if (Array.isArray(childModules)) { + for (let index = childModules.length - 1; index >= 0; index -= 1) { + if (childModules[index] === staleBridge) { + childModules.splice(index, 1); + } + } + } + return staleBridge.exports; +} +function loadInstance() { + const evictedBridge = evictBridgeModule(); + const bridge = require('./bindings_wasm'); + if (bridge === evictedBridge || loadedBridges.has(bridge)) { + throw new WasmRuntimeError( + `${INTERNAL_ERROR_PREFIX}: the WebAssembly instance was terminated and this module loader ` + + 'cannot create a fresh one; restart the process or worker to continue', + ); + } + loadedBridges.add(bridge); + return new WasmInstance(bridge); +} +let activeInstance; +function currentInstance() { + activeInstance ?? (activeInstance = loadInstance()); + return activeInstance; +} +/** + * Frees objects the caller dropped without calling `free()`. It replaces the + * generated glue's own finalizer, which calls into the instance unconditionally: + * after a trap that call can trap again inside a finalization callback, which + * Node.js reports as an uncaught exception. + */ +const allocations = new FinalizationRegistry(({ instance, prototype, pointer }) => { + if (instance.terminated) { + return; + } + try { + instance.call(() => { + const shell = Object.create(prototype); + shell.__wbg_ptr = pointer; + shell.free(); + }); + } catch { + // The failure has already terminated the instance; a finalizer has no caller to report to. + } +}); +/** Exclusive owner of one generated-glue object and the instance that created it. */ +class Owned { + constructor(instance, create, label) { + this.instance = instance; + this.label = label; + const inner = instance.call(create); + const object = inner; + const pointer = object.__destroy_into_raw(); + object.__wbg_ptr = pointer; + allocations.register(inner, { instance, prototype: Object.getPrototypeOf(inner), pointer }, inner); + this.inner = inner; + } + /** The live object, for passing as an argument to another call on the same instance. */ + raw() { + if (this.inner === undefined) { + throw new Error(`${this.label} has been freed`); + } + return this.inner; + } + use(operation) { + const inner = this.inner; + if (inner === undefined) { + throw new Error(`${this.label} has been freed`); + } + return this.instance.call(() => operation(inner)); + } + free() { + const inner = this.inner; + if (inner === undefined) { + return; + } + this.inner = undefined; + allocations.unregister(inner); + if (!this.instance.terminated) { + this.instance.call(() => inner.free()); + } + } +} +activeInstance = loadInstance(); /** Name reported for an in-memory template when the caller does not supply one. */ exports.DEFAULT_TEMPLATE_NAME = 'template'; /** A template read from disk; the path labels the report and its diagnostics. */ @@ -232,7 +413,18 @@ class TemplateContent { } exports.TemplateContent = TemplateContent; function templateLabel(template) { - return template instanceof TemplateContent ? template.name : template.path; + const label = template instanceof TemplateContent ? template.name : template.path; + if (typeof label !== 'string') { + throw new TypeError('template name must be a string'); + } + return label; +} +function templateBytes(template) { + const bytes = template.readBytes(); + if (!(bytes instanceof Uint8Array)) { + throw new TypeError('template content must be a string or a Uint8Array'); + } + return new Uint8Array(bytes); } class RuleFile { constructor(path) { @@ -257,6 +449,64 @@ class SchemaFile { } } exports.SchemaFile = SchemaFile; +const MAX_WASM_INPUT_DEPTH = 64; +const MAP_FOR_EACH = Map.prototype.forEach; +function copyWasmInput(value, path, depth = 0, ancestors = new Set()) { + if (value === null || (typeof value !== 'object' && typeof value !== 'function')) { + return value; + } + if (depth > MAX_WASM_INPUT_DEPTH) { + throw new TypeError(`${path} exceeds the supported nesting depth`); + } + if (ancestors.has(value)) { + throw new TypeError(`${path} must not contain a cycle`); + } + ancestors.add(value); + try { + if (Array.isArray(value)) { + const length = Object.getOwnPropertyDescriptor(value, 'length')?.value; + if (!Number.isSafeInteger(length) || length < 0) { + throw new TypeError(`${path} must have a valid array length`); + } + const copied = []; + for (let index = 0; index < length; index += 1) { + const descriptor = Object.getOwnPropertyDescriptor(value, String(index)); + if (descriptor === undefined || !('value' in descriptor)) { + throw new TypeError(`${path}[${index}] must be a value property`); + } + copied.push(copyWasmInput(descriptor.value, `${path}[${index}]`, depth + 1, ancestors)); + } + return copied; + } + if (value instanceof Map) { + const copied = new Map(); + MAP_FOR_EACH.call(value, (entryValue, entryKey) => { + copied.set( + copyWasmInput(entryKey, `${path} key`, depth + 1, ancestors), + copyWasmInput(entryValue, `${path} value`, depth + 1, ancestors), + ); + }); + return copied; + } + if (!isPlainRecord(value)) { + throw new TypeError(`${path} must contain only plain objects, arrays, maps, and primitive values`); + } + const copied = Object.create(null); + for (const key of Reflect.ownKeys(value)) { + if (typeof key !== 'string') { + throw new TypeError(`${path} property names must be strings`); + } + const descriptor = Object.getOwnPropertyDescriptor(value, key); + if (descriptor === undefined || !('value' in descriptor)) { + throw new TypeError(`${path}.${key} must be a value property`); + } + copied[key] = copyWasmInput(descriptor.value, `${path}.${key}`, depth + 1, ancestors); + } + return copied; + } finally { + ancestors.delete(value); + } +} function toExternalRuleSources(sources) { return (sources ?? []).map((source) => source instanceof RuleFile ? { name: source.path, content: source.readContent() } : source, @@ -293,70 +543,89 @@ function toWasmSchemaValidatorConfig(config) { } class TemplateModel { constructor(template) { - this.inner = bridge.WasmSemanticModel.parse(template.readBytes()); + const bytes = templateBytes(template); + this.owned = new Owned(currentInstance(), (bridge) => bridge.WasmSemanticModel.parse(bytes), 'TemplateModel'); } resources() { - return this.inner.resources(); + return this.owned.use((inner) => inner.resources()); } parameters() { - return this.inner.parameters(); + return this.owned.use((inner) => inner.parameters()); } outputs() { - return this.inner.outputs(); + return this.owned.use((inner) => inner.outputs()); } conditions() { - return this.inner.conditions(); + return this.owned.use((inner) => inner.conditions()); } transforms() { - return this.inner.transforms(); + return this.owned.use((inner) => inner.transforms()); } formatVersion() { - return this.inner.formatVersion(); + return this.owned.use((inner) => inner.formatVersion()); } description() { - return this.inner.description(); + return this.owned.use((inner) => inner.description()); } toDiagnosticModel() { - return this.inner.toDiagnosticModel(); + return this.owned.use((inner) => inner.toDiagnosticModel()); } sourceLocation(path) { - return this.inner.sourceLocation(path); + if (typeof path !== 'string') { + throw new TypeError('path must be a string'); + } + return this.owned.use((inner) => inner.sourceLocation(path)); } free() { - this.inner.free(); + this.owned.free(); } } exports.TemplateModel = TemplateModel; class SchemaValidator { constructor(config) { - this.inner = new bridge.WasmSchemaValidator(toWasmSchemaValidatorConfig(config)); + const wasmConfig = copyWasmInput(toWasmSchemaValidatorConfig(config), 'schema validator config'); + this.instance = currentInstance(); + this.owned = new Owned( + this.instance, + (bridge) => new bridge.WasmSchemaValidator(wasmConfig), + 'SchemaValidator', + ); } listRules() { - return this.inner.listRules(); + return this.owned.use((inner) => inner.listRules()); } schemaCount() { - return this.inner.schemaCount(); + return this.owned.use((inner) => inner.schemaCount()); } validate(template, region) { - const model = bridge.WasmSemanticModel.parse(template.readBytes()); + if (region !== undefined && typeof region !== 'string') { + throw new TypeError('region must be a string'); + } + const bytes = templateBytes(template); + const model = new Owned(this.instance, (bridge) => bridge.WasmSemanticModel.parse(bytes), 'TemplateModel'); try { - return this.inner.validate(model, region).diagnostics; + const parsed = model.raw(); + return this.owned.use((inner) => inner.validate(parsed, region).diagnostics); } finally { model.free(); } } free() { - this.inner.free(); + this.owned.free(); } } exports.SchemaValidator = SchemaValidator; -function createEngineClass(WasmClass, toWasmConfig) { +function createEngineClass(label, wasmClass, toWasmConfig) { return class { constructor(config) { - this.inner = new WasmClass(toWasmConfig(config)); + const wasmConfig = copyWasmInput(toWasmConfig(config), `${label} config`); + this.owned = new Owned(currentInstance(), (bridge) => new (wasmClass(bridge))(wasmConfig), label); } validateTemplate(template, config) { - return this.inner.validateTemplate(template.readBytes(), config ?? {}, templateLabel(template)); + const bytes = templateBytes(template); + const label = templateLabel(template); + const wasmConfig = copyWasmInput(config ?? {}, 'validation config'); + return this.owned.use((inner) => inner.validateTemplate(bytes, wasmConfig, label)); } validateStandard(template, config) { return this.validateTemplate(template, { ...config, detailLevel: 'STANDARD' }); @@ -368,22 +637,27 @@ function createEngineClass(WasmClass, toWasmConfig) { if (!(request instanceof AwsCliCommand)) { throw new TypeError('request must be an AwsCliCommand'); } - return fromWireAwsCliCommandValidation(this.inner.validateAwsCliCommand(toWireAwsCliCommand(request))); + const wire = toWireAwsCliCommand(request); + return fromWireAwsCliCommandValidation(this.owned.use((inner) => inner.validateAwsCliCommand(wire))); } listRules() { - return this.inner.listRules(); + return this.owned.use((inner) => inner.listRules()); } engineName() { - return this.inner.engineName(); + return this.owned.use((inner) => inner.engineName()); } free() { - this.inner.free(); + this.owned.free(); } }; } -exports.RegoEngine = createEngineClass(bridge.WasmRegoEngine, toWasmEngineConfig); -exports.CelEngine = createEngineClass(bridge.WasmCelEngine, toWasmEngineConfig); -exports.CompositeEngine = createEngineClass(bridge.WasmCompositeEngine, toWasmCompositeEngineConfig); +exports.RegoEngine = createEngineClass('RegoEngine', (bridge) => bridge.WasmRegoEngine, toWasmEngineConfig); +exports.CelEngine = createEngineClass('CelEngine', (bridge) => bridge.WasmCelEngine, toWasmEngineConfig); +exports.CompositeEngine = createEngineClass( + 'CompositeEngine', + (bridge) => bridge.WasmCompositeEngine, + toWasmCompositeEngineConfig, +); function version() { - return bridge.version(); + return currentInstance().call((bridge) => bridge.version()); }