From 8acae0eeabdda65729cfd6063681af03d6ca4d59 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 13 May 2019 12:52:22 +0200 Subject: [PATCH 1/3] feat(cdk): support encoding Tokens as numbers Numbers can now be encoded into a set of (hugely negative) numbers, and the encoding can be reversed. This allows APIs that take numbers to take lazy values and intrinsics that evaluate to numbers. This change only introduces the capability, it does not use it in any of the construct libraries yet. Fixes #1455. --- packages/@aws-cdk/cdk/lib/encoding.ts | 72 ++++++++++++++++++++++- packages/@aws-cdk/cdk/lib/resolve.ts | 13 ++++ packages/@aws-cdk/cdk/lib/token-map.ts | 33 +++++++++-- packages/@aws-cdk/cdk/lib/token.ts | 25 ++++++++ packages/@aws-cdk/cdk/test/test.tokens.ts | 47 ++++++++++++++- 5 files changed, 182 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/encoding.ts b/packages/@aws-cdk/cdk/lib/encoding.ts index f556adb75e230..b37fcdb0ce480 100644 --- a/packages/@aws-cdk/cdk/lib/encoding.ts +++ b/packages/@aws-cdk/cdk/lib/encoding.ts @@ -200,9 +200,79 @@ export function containsListTokenElement(xs: any[]) { export function unresolved(obj: any): boolean { if (typeof(obj) === 'string') { return TokenString.forStringToken(obj).test(); + } else if (typeof obj === 'number') { + return extractTokenDouble(obj) !== undefined; } else if (Array.isArray(obj) && obj.length === 1) { return typeof(obj[0]) === 'string' && TokenString.forListToken(obj[0]).test(); } else { return obj && typeof(obj[RESOLVE_METHOD]) === 'function'; } -} \ No newline at end of file +} + +/** + * Bit pattern in the top 16 bits of a double to indicate a Token + * + * An IEEE double in LE memory order looks like this (grouped + * into octets, then grouped into 32-bit words): + * + * mmmmmmm.mmmmmmm.mmmmmmm.mmmmmmm | mmmmmmm.mmmmmmm.EEEEEmm.sEEEEEE + * + * - m: mantissa (52 bits) + * - E: exponent (11 bits) + * - s: sign (1 bit) + * + * We put the following marker into the top 16 bits (exponent and sign), and + * use the mantissa part to encode the token index. To save some bit twiddling + * we use all top 16 bits for the tag. That loses us 2 mantissa bits to store + * information in but we still have 50, which is going to be plenty for any + * number of tokens to be created during the lifetime of any CDK application. + * + * Can't have all bits set because that makes a NaN, so unset the least + * significant exponent bit. + * + * Currently not supporting BE architectures. + */ +// tslint:disable-next-line:no-bitwise +const DOUBLE_TOKEN_MARKER_BITS = 0xFBFF << 16; + +/** + * Return a special Double value that encodes the given integer + */ +export function createTokenDouble(x: number) { + if (Math.floor(x) !== x || x < 0) { + throw new Error('Can only encode positive integers'); + } + + const buf = new ArrayBuffer(8); + const ints = new Uint32Array(buf); + + // tslint:disable:no-bitwise + ints[0] = x & 0x0000FFFFFFFF; // Bottom 32 bits of number + ints[1] = (x & 0xFFFF00000000) >> 32 | DOUBLE_TOKEN_MARKER_BITS; // Top 16 bits of number and the mask + // tslint:enable:no-bitwise + + return (new Float64Array(buf))[0]; + +} + +/** + * Extract the encoded integer out of the special Double value + * + * Returns undefined if the float is a not an encoded token. + */ +export function extractTokenDouble(encoded: number): number | undefined { + const buf = new ArrayBuffer(8); + (new Float64Array(buf))[0] = encoded; + + const ints = new Uint32Array(buf); + + // tslint:disable:no-bitwise + if ((ints[1] & 0xFFFF0000) !== DOUBLE_TOKEN_MARKER_BITS) { + return undefined; + } + + // Must use + instead of | here (bitwise operations + // will force 32-bits integer arithmetic, + will not). + return ints[0] + (ints[1] & 0xFFFF0000) << 16; + // tslint:enable:no-bitwise +} diff --git a/packages/@aws-cdk/cdk/lib/resolve.ts b/packages/@aws-cdk/cdk/lib/resolve.ts index beb21cb17b6b4..dbc957e1b6a1a 100644 --- a/packages/@aws-cdk/cdk/lib/resolve.ts +++ b/packages/@aws-cdk/cdk/lib/resolve.ts @@ -54,6 +54,13 @@ export function resolve(obj: any, context: ResolveContext): any { return resolveStringTokens(obj, context); } + // + // number - potentially decode Tokenized number + // + if (typeof(obj) === 'number') { + return resolveNumberToken(obj, context); + } + // // primitives - as-is // @@ -184,3 +191,9 @@ function resolveListTokens(xs: string[], context: ResolveContext): any { } return fragments.mapUnresolved(x => resolve(x, context)).values[0]; } + +function resolveNumberToken(x: number, context: ResolveContext): any { + const token = TokenMap.instance().lookupNumberToken(x); + if (token === undefined) { return x; } + return resolve(token, context); +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/token-map.ts b/packages/@aws-cdk/cdk/lib/token-map.ts index 3dd31ad0d8481..2db15eeb855d0 100644 --- a/packages/@aws-cdk/cdk/lib/token-map.ts +++ b/packages/@aws-cdk/cdk/lib/token-map.ts @@ -1,4 +1,5 @@ -import { BEGIN_LIST_TOKEN_MARKER, BEGIN_STRING_TOKEN_MARKER, END_TOKEN_MARKER, TokenString, VALID_KEY_CHARS } from "./encoding"; +import { BEGIN_LIST_TOKEN_MARKER, BEGIN_STRING_TOKEN_MARKER, createTokenDouble, + END_TOKEN_MARKER, extractTokenDouble, TokenString, VALID_KEY_CHARS } from "./encoding"; import { Token } from "./token"; const glob = global as any; @@ -23,7 +24,9 @@ export class TokenMap { return glob.__cdkTokenMap; } - private readonly tokenMap = new Map(); + private readonly stringTokenMap = new Map(); + private readonly numberTokenMap = new Map(); + private tokenCounter = 0; /** * Generate a unique string for this Token, returning a key @@ -49,6 +52,15 @@ export class TokenMap { return [`${BEGIN_LIST_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`]; } + /** + * Create a unique number representation for this Token and return it + */ + public registerNumber(token: Token): number { + const tokenIndex = this.tokenCounter++; + this.numberTokenMap.set(tokenIndex, token); + return createTokenDouble(tokenIndex); + } + /** * Reverse a string representation into a Token object */ @@ -76,13 +88,24 @@ export class TokenMap { return undefined; } + /** + * Reverse a number encoding into a Token, or undefined if the number wasn't a Token + */ + public lookupNumberToken(x: number): Token | undefined { + const tokenIndex = extractTokenDouble(x); + if (tokenIndex === undefined) { return undefined; } + const t = this.numberTokenMap.get(tokenIndex); + if (t === undefined) { throw new Error('Encoded representation of unknown number Token found'); } + return t; + } + /** * Find a Token by key. * * This excludes the token markers. */ public lookupToken(key: string): Token { - const token = this.tokenMap.get(key); + const token = this.stringTokenMap.get(key); if (!token) { throw new Error(`Unrecognized token key: ${key}`); } @@ -90,10 +113,10 @@ export class TokenMap { } private register(token: Token, representationHint?: string): string { - const counter = this.tokenMap.size; + const counter = this.tokenCounter++; const representation = (representationHint || `TOKEN`).replace(new RegExp(`[^${VALID_KEY_CHARS}]`, 'g'), '.'); const key = `${representation}.${counter}`; - this.tokenMap.set(key, token); + this.stringTokenMap.set(key, token); return key; } } \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/token.ts b/packages/@aws-cdk/cdk/lib/token.ts index d78bb418d3ab7..f7184115fe840 100644 --- a/packages/@aws-cdk/cdk/lib/token.ts +++ b/packages/@aws-cdk/cdk/lib/token.ts @@ -38,6 +38,7 @@ export class Token { private tokenStringification?: string; private tokenListification?: string[]; + private tokenNumberification?: number; /** * Creates a token that resolves to `value`. @@ -132,6 +133,30 @@ export class Token { } return this.tokenListification; } + + /** + * Return a floating point representation of this Token + * + * Call this if the Token intrinsically resolves to something that represents + * a number, and you need to pass it into an API that expects a number. + * + * You may not do any operations on the returned value; any arithmetic or + * other operations can and probably will destroy the token-ness of the value. + */ + public toNumber(): number { + if (this.tokenNumberification === undefined) { + const valueType = typeof this.valueOrFunction; + // Optimization: if we can immediately resolve this, don't bother + // registering a Token. + if (valueType === 'number') { return this.valueOrFunction; } + if (valueType !== 'function') { + throw new Error(`Token value is not number or lazy, can't represent as number: ${this.valueOrFunction}`); + } + this.tokenNumberification = TokenMap.instance().registerNumber(this); + } + + return this.tokenNumberification; + } } /** diff --git a/packages/@aws-cdk/cdk/test/test.tokens.ts b/packages/@aws-cdk/cdk/test/test.tokens.ts index 259a31974eabb..2d3e7cf504b85 100644 --- a/packages/@aws-cdk/cdk/test/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/test.tokens.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; -import { App as Root, Fn, Token } from '../lib'; +import { App as Root, Fn, Token, Stack } from '../lib'; +import { createTokenDouble, extractTokenDouble } from '../lib/encoding'; import { TokenMap } from '../lib/token-map'; import { evaluateCFN } from './evaluate-cfn'; @@ -387,7 +388,49 @@ export = { test.done(); }, - } + }, + + 'number encoding': { + 'arbitrary integers can be encoded, stringified, and recovered'(test: Test) { + for (let i = 0; i < 100; i++) { + + // We can encode all numbers up to 2^50-1 + const x = Math.floor(Math.random() * (Math.pow(2, 50) - 1)); + + const encoded = createTokenDouble(x); + // Roundtrip through JSONification + const roundtripped = JSON.parse(JSON.stringify({ theNumber: encoded })).theNumber; + const decoded = extractTokenDouble(roundtripped); + test.equal(decoded, x, `Fail roundtrip encoding of ${x}`); + } + + test.done(); + }, + + 'arbitrary numbers are correctly detected as not being tokens'(test: Test) { + test.equal(undefined, extractTokenDouble(0)); + test.equal(undefined, extractTokenDouble(1243)); + test.equal(undefined, extractTokenDouble(4835e+532)); + + test.done(); + }, + + 'can number-encode and resolve Token objects'(test: Test) { + // GIVEN + const stack = new Stack(); + const x = new Token(() => 123); + + // THEN + const encoded = x.toNumber(); + test.equal(true, Token.isToken(encoded), 'encoded number does not test as token'); + + // THEN + const resolved = stack.node.resolve({ value: encoded }); + test.deepEqual(resolved, { value: 123 }); + + test.done(); + }, + }, }; class Promise2 extends Token { From 817bc3f1a8a1a10e5ac2fddb9af848f6bce165a3 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 13 May 2019 15:33:09 +0200 Subject: [PATCH 2/3] Add check, fix build --- .../@aws-cdk/app-delivery/package-lock.json | 2 +- packages/@aws-cdk/applet-js/package-lock.json | 2 +- packages/@aws-cdk/assert/package-lock.json | 43 +++++++++++++------ packages/@aws-cdk/assets/package-lock.json | 2 +- .../package-lock.json | 2 +- .../aws-autoscaling-common/package-lock.json | 2 +- .../package-lock.json | 2 +- .../@aws-cdk/aws-cloudfront/package-lock.json | 2 +- .../@aws-cdk/aws-cloudtrail/package-lock.json | 2 +- .../@aws-cdk/aws-codebuild/package-lock.json | 2 +- .../@aws-cdk/aws-codecommit/package-lock.json | 2 +- .../package-lock.json | 2 +- .../package-lock.json | 2 +- .../aws-events-targets/package-lock.json | 2 +- .../@aws-cdk/aws-lambda/package-lock.json | 2 +- .../@aws-cdk/aws-route53/package-lock.json | 2 +- packages/@aws-cdk/aws-sqs/package-lock.json | 2 +- .../aws-stepfunctions-tasks/package-lock.json | 2 +- packages/@aws-cdk/cdk/lib/encoding.ts | 12 +++++- packages/@aws-cdk/cdk/package-lock.json | 2 +- packages/@aws-cdk/cdk/test/test.tokens.ts | 2 +- packages/@aws-cdk/cfnspec/package-lock.json | 2 +- .../cloudformation-diff/package-lock.json | 2 +- packages/aws-cdk/package-lock.json | 2 +- packages/cdk-dasm/package-lock.json | 2 +- .../simple-resource-bundler/package-lock.json | 2 +- tools/awslint/package-lock.json | 2 +- tools/cdk-build-tools/package-lock.json | 2 +- tools/cdk-integ-tools/package-lock.json | 2 +- tools/cfn2ts/package-lock.json | 2 +- tools/pkglint/package-lock.json | 2 +- tools/pkgtools/package-lock.json | 2 +- 32 files changed, 72 insertions(+), 43 deletions(-) diff --git a/packages/@aws-cdk/app-delivery/package-lock.json b/packages/@aws-cdk/app-delivery/package-lock.json index db19991690a51..88aad3fcb4f83 100644 --- a/packages/@aws-cdk/app-delivery/package-lock.json +++ b/packages/@aws-cdk/app-delivery/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/app-delivery", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/applet-js/package-lock.json b/packages/@aws-cdk/applet-js/package-lock.json index 35b5598dc5ccd..3d6aa703b1608 100644 --- a/packages/@aws-cdk/applet-js/package-lock.json +++ b/packages/@aws-cdk/applet-js/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/applet-js", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/assert/package-lock.json b/packages/@aws-cdk/assert/package-lock.json index afbd964739512..2dffe0f6a5106 100644 --- a/packages/@aws-cdk/assert/package-lock.json +++ b/packages/@aws-cdk/assert/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assert", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1444,7 +1444,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1462,11 +1463,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1479,15 +1482,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1590,7 +1596,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1600,6 +1607,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1612,17 +1620,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1639,6 +1650,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1711,7 +1723,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1721,6 +1734,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1796,7 +1810,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -1826,6 +1841,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1843,6 +1859,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1881,11 +1898,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, diff --git a/packages/@aws-cdk/assets/package-lock.json b/packages/@aws-cdk/assets/package-lock.json index 9497b01aeb055..c43201dfaeada 100644 --- a/packages/@aws-cdk/assets/package-lock.json +++ b/packages/@aws-cdk/assets/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assets", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json b/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json index 4294183a58817..c94960da9f871 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-applicationautoscaling", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-autoscaling-common/package-lock.json b/packages/@aws-cdk/aws-autoscaling-common/package-lock.json index 9aadca8a42d06..2392efb01585f 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package-lock.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-autoscaling-common", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json index d5c5785bc49ed..6824405d1fad1 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json @@ -1,6 +1,6 @@ { "name": "dns_validated_certificate_handler", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudfront/package-lock.json b/packages/@aws-cdk/aws-cloudfront/package-lock.json index e6d7537ce489f..3b9b386d18c95 100644 --- a/packages/@aws-cdk/aws-cloudfront/package-lock.json +++ b/packages/@aws-cdk/aws-cloudfront/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudfront", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudtrail/package-lock.json b/packages/@aws-cdk/aws-cloudtrail/package-lock.json index c18f48597418d..85ff8dd8f3fa8 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package-lock.json +++ b/packages/@aws-cdk/aws-cloudtrail/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudtrail", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-codebuild/package-lock.json b/packages/@aws-cdk/aws-codebuild/package-lock.json index fe0d06104bc98..4a1689214859a 100644 --- a/packages/@aws-cdk/aws-codebuild/package-lock.json +++ b/packages/@aws-cdk/aws-codebuild/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codebuild", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-codecommit/package-lock.json b/packages/@aws-cdk/aws-codecommit/package-lock.json index 5b158d030bc56..0210b68df6aae 100644 --- a/packages/@aws-cdk/aws-codecommit/package-lock.json +++ b/packages/@aws-cdk/aws-codecommit/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codecommit", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json b/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json index be8a1d5a85604..6edd1baeff44c 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codepipeline-actions", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json index 282a449c8ebde..ca0ca5c7743fa 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json @@ -1,6 +1,6 @@ { "name": "aws-global-lambda-coordinator", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-events-targets/package-lock.json b/packages/@aws-cdk/aws-events-targets/package-lock.json index 5150e1711514c..d24eb4a6e603c 100644 --- a/packages/@aws-cdk/aws-events-targets/package-lock.json +++ b/packages/@aws-cdk/aws-events-targets/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-events-targets", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-lambda/package-lock.json b/packages/@aws-cdk/aws-lambda/package-lock.json index 8d10e101bb79b..899890f49ed6b 100644 --- a/packages/@aws-cdk/aws-lambda/package-lock.json +++ b/packages/@aws-cdk/aws-lambda/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-lambda", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-route53/package-lock.json b/packages/@aws-cdk/aws-route53/package-lock.json index c2af9f7c26cdc..ccf1d80df07a4 100644 --- a/packages/@aws-cdk/aws-route53/package-lock.json +++ b/packages/@aws-cdk/aws-route53/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-route53", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-sqs/package-lock.json b/packages/@aws-cdk/aws-sqs/package-lock.json index 1a2e5b07d72fe..f3ba0e70d68bd 100644 --- a/packages/@aws-cdk/aws-sqs/package-lock.json +++ b/packages/@aws-cdk/aws-sqs/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-sqs", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json index 895967b36848e..90a8de80e8371 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-stepfunctions-tasks", - "version": "0.28.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cdk/lib/encoding.ts b/packages/@aws-cdk/cdk/lib/encoding.ts index b37fcdb0ce480..51afdf803bd37 100644 --- a/packages/@aws-cdk/cdk/lib/encoding.ts +++ b/packages/@aws-cdk/cdk/lib/encoding.ts @@ -236,12 +236,22 @@ export function unresolved(obj: any): boolean { const DOUBLE_TOKEN_MARKER_BITS = 0xFBFF << 16; /** - * Return a special Double value that encodes the given integer + * Highest encodable number + */ +const MAX_ENCODABLE_INTEGER = Math.pow(2, 50) - 1; + +/** + * Return a special Double value that encodes the given nonnegative integer + * + * We use this to encode Token ordinals. */ export function createTokenDouble(x: number) { if (Math.floor(x) !== x || x < 0) { throw new Error('Can only encode positive integers'); } + if (x > MAX_ENCODABLE_INTEGER) { + throw new Error(`Got an index too large to encode: ${x}`); + } const buf = new ArrayBuffer(8); const ints = new Uint32Array(buf); diff --git a/packages/@aws-cdk/cdk/package-lock.json b/packages/@aws-cdk/cdk/package-lock.json index 863b1c33c028e..ff4995939ee42 100644 --- a/packages/@aws-cdk/cdk/package-lock.json +++ b/packages/@aws-cdk/cdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cdk", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cdk/test/test.tokens.ts b/packages/@aws-cdk/cdk/test/test.tokens.ts index 2d3e7cf504b85..bb06652ea3ca8 100644 --- a/packages/@aws-cdk/cdk/test/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/test.tokens.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { App as Root, Fn, Token, Stack } from '../lib'; +import { App as Root, Fn, Stack, Token } from '../lib'; import { createTokenDouble, extractTokenDouble } from '../lib/encoding'; import { TokenMap } from '../lib/token-map'; import { evaluateCFN } from './evaluate-cfn'; diff --git a/packages/@aws-cdk/cfnspec/package-lock.json b/packages/@aws-cdk/cfnspec/package-lock.json index f8abce43617df..9bb3176515ad9 100644 --- a/packages/@aws-cdk/cfnspec/package-lock.json +++ b/packages/@aws-cdk/cfnspec/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cfnspec", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cloudformation-diff/package-lock.json b/packages/@aws-cdk/cloudformation-diff/package-lock.json index 99556a01b45b3..cdeadef4807b2 100644 --- a/packages/@aws-cdk/cloudformation-diff/package-lock.json +++ b/packages/@aws-cdk/cloudformation-diff/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cloudformation-diff", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/aws-cdk/package-lock.json b/packages/aws-cdk/package-lock.json index ef7a747f20301..7bdf1ee8afc33 100644 --- a/packages/aws-cdk/package-lock.json +++ b/packages/aws-cdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "aws-cdk", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/cdk-dasm/package-lock.json b/packages/cdk-dasm/package-lock.json index 02acf4d96249d..bdc4f2dea277b 100644 --- a/packages/cdk-dasm/package-lock.json +++ b/packages/cdk-dasm/package-lock.json @@ -1,6 +1,6 @@ { "name": "cdk-dasm", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/simple-resource-bundler/package-lock.json b/packages/simple-resource-bundler/package-lock.json index f83241653f5f4..80700db39be0a 100644 --- a/packages/simple-resource-bundler/package-lock.json +++ b/packages/simple-resource-bundler/package-lock.json @@ -1,6 +1,6 @@ { "name": "simple-resource-bundler", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/awslint/package-lock.json b/tools/awslint/package-lock.json index f9d91c8cac2da..34d4cf6757c86 100644 --- a/tools/awslint/package-lock.json +++ b/tools/awslint/package-lock.json @@ -1,6 +1,6 @@ { "name": "awslint", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/cdk-build-tools/package-lock.json b/tools/cdk-build-tools/package-lock.json index 764ea6f6f243e..3fa37a6a0b57d 100644 --- a/tools/cdk-build-tools/package-lock.json +++ b/tools/cdk-build-tools/package-lock.json @@ -3787,7 +3787,7 @@ "dependencies": { "resolve-from": { "version": "4.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "yargs-parser": { diff --git a/tools/cdk-integ-tools/package-lock.json b/tools/cdk-integ-tools/package-lock.json index d88a3ee2f7241..107e065192325 100644 --- a/tools/cdk-integ-tools/package-lock.json +++ b/tools/cdk-integ-tools/package-lock.json @@ -1,6 +1,6 @@ { "name": "cdk-integ-tools", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/cfn2ts/package-lock.json b/tools/cfn2ts/package-lock.json index 186dc3b3837a8..ef749eaa9c0ef 100644 --- a/tools/cfn2ts/package-lock.json +++ b/tools/cfn2ts/package-lock.json @@ -1,6 +1,6 @@ { "name": "cfn2ts", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/pkglint/package-lock.json b/tools/pkglint/package-lock.json index b4f0acbb3b4ec..70bb284f83ab7 100644 --- a/tools/pkglint/package-lock.json +++ b/tools/pkglint/package-lock.json @@ -1,6 +1,6 @@ { "name": "pkglint", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/pkgtools/package-lock.json b/tools/pkgtools/package-lock.json index 7519d7e8cc8b4..722f5b3631cfc 100644 --- a/tools/pkgtools/package-lock.json +++ b/tools/pkgtools/package-lock.json @@ -1,6 +1,6 @@ { "name": "pkgtools", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { From 48dd11c9977731da50e352b85542b258fcc6f816 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 13 May 2019 17:07:35 +0200 Subject: [PATCH 3/3] Oops, bitwise ops only work on 32 bits integers --- packages/@aws-cdk/cdk/lib/encoding.ts | 40 +++++++++++++++++++---- packages/@aws-cdk/cdk/test/test.tokens.ts | 10 ++++-- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/encoding.ts b/packages/@aws-cdk/cdk/lib/encoding.ts index 51afdf803bd37..d5d41a1cab7fd 100644 --- a/packages/@aws-cdk/cdk/lib/encoding.ts +++ b/packages/@aws-cdk/cdk/lib/encoding.ts @@ -215,7 +215,7 @@ export function unresolved(obj: any): boolean { * An IEEE double in LE memory order looks like this (grouped * into octets, then grouped into 32-bit words): * - * mmmmmmm.mmmmmmm.mmmmmmm.mmmmmmm | mmmmmmm.mmmmmmm.EEEEEmm.sEEEEEE + * mmmmmmmm.mmmmmmmm.mmmmmmmm.mmmmmmmm | mmmmmmmm.mmmmmmmm.EEEEmmmm.sEEEEEEE * * - m: mantissa (52 bits) * - E: exponent (11 bits) @@ -223,8 +223,8 @@ export function unresolved(obj: any): boolean { * * We put the following marker into the top 16 bits (exponent and sign), and * use the mantissa part to encode the token index. To save some bit twiddling - * we use all top 16 bits for the tag. That loses us 2 mantissa bits to store - * information in but we still have 50, which is going to be plenty for any + * we use all top 16 bits for the tag. That loses us 4 mantissa bits to store + * information in but we still have 48, which is going to be plenty for any * number of tokens to be created during the lifetime of any CDK application. * * Can't have all bits set because that makes a NaN, so unset the least @@ -238,7 +238,17 @@ const DOUBLE_TOKEN_MARKER_BITS = 0xFBFF << 16; /** * Highest encodable number */ -const MAX_ENCODABLE_INTEGER = Math.pow(2, 50) - 1; +const MAX_ENCODABLE_INTEGER = Math.pow(2, 48) - 1; + +/** + * Get 2^32 as a number, so we can do multiplication and div instead of bit shifting + * + * Necessary because in JavaScript, bit operations implicitly convert + * to int32 and we need them to work on "int64"s. + * + * So instead of x >> 32, we do Math.floor(x / 2^32), and vice versa. + */ +const BITS32 = Math.pow(2, 32); /** * Return a special Double value that encodes the given nonnegative integer @@ -257,12 +267,28 @@ export function createTokenDouble(x: number) { const ints = new Uint32Array(buf); // tslint:disable:no-bitwise - ints[0] = x & 0x0000FFFFFFFF; // Bottom 32 bits of number - ints[1] = (x & 0xFFFF00000000) >> 32 | DOUBLE_TOKEN_MARKER_BITS; // Top 16 bits of number and the mask + ints[0] = x & 0x0000FFFFFFFF; // Bottom 32 bits of number + + // This needs an "x >> 32" but that will make it a 32-bit number instead + // of a 64-bit number. + ints[1] = (shr32(x) & 0xFFFF) | DOUBLE_TOKEN_MARKER_BITS; // Top 16 bits of number and the mask // tslint:enable:no-bitwise return (new Float64Array(buf))[0]; +} +/** + * Shift a 64-bit int right 32 bits + */ +function shr32(x: number) { + return Math.floor(x / BITS32); +} + +/** + * Shift a 64-bit left 32 bits + */ +function shl32(x: number) { + return x * BITS32; } /** @@ -283,6 +309,6 @@ export function extractTokenDouble(encoded: number): number | undefined { // Must use + instead of | here (bitwise operations // will force 32-bits integer arithmetic, + will not). - return ints[0] + (ints[1] & 0xFFFF0000) << 16; + return ints[0] + shl32(ints[1] & 0xFFFF); // tslint:enable:no-bitwise } diff --git a/packages/@aws-cdk/cdk/test/test.tokens.ts b/packages/@aws-cdk/cdk/test/test.tokens.ts index bb06652ea3ca8..51ded366e7662 100644 --- a/packages/@aws-cdk/cdk/test/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/test.tokens.ts @@ -391,11 +391,15 @@ export = { }, 'number encoding': { + 'basic integer encoding works'(test: Test) { + test.equal(16, extractTokenDouble(createTokenDouble(16))); + test.done(); + }, + 'arbitrary integers can be encoded, stringified, and recovered'(test: Test) { for (let i = 0; i < 100; i++) { - - // We can encode all numbers up to 2^50-1 - const x = Math.floor(Math.random() * (Math.pow(2, 50) - 1)); + // We can encode all numbers up to 2^48-1 + const x = Math.floor(Math.random() * (Math.pow(2, 48) - 1)); const encoded = createTokenDouble(x); // Roundtrip through JSONification