Skip to content

Commit 0a83d52

Browse files
authored
fix(kernel): make type serialization explicit and recursive (#401)
Serialize and deserialize types according to their declared static type, and add validation on the runtime types matching the declared types. This is in contrast to previously, when we mostly only used the runtime types to determine what to do, and harly any validation was done. The runtime types used to be able to freely disagree with the declared types, and we put a lot of burden on the JSII runtimes at the other end of the wire. Fix tests that used to exercise the API with invalid arguments. Remove Proxy objects since they only existed to prevent accidentally overwriting certain keys via the jsii interface, and Symbols serve the same purpose (but simpler). Fixes aws/aws-cdk#1981.
1 parent da81078 commit 0a83d52

File tree

34 files changed

+1964
-517
lines changed

34 files changed

+1964
-517
lines changed

packages/jsii-calc/lib/compliance.ts

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as path from 'path';
1313
import * as os from 'os';
1414
import * as crypto from 'crypto';
1515
import { promisify } from 'util';
16-
import { composition, IFriendlyRandomGenerator, IRandomNumberGenerator, Multiply } from './calculator';
16+
import { IFriendlyRandomGenerator, IRandomNumberGenerator, Multiply } from './calculator';
1717

1818
const bundled = require('jsii-calc-bundled');
1919
import base = require('@scope/jsii-calc-base');
@@ -163,7 +163,7 @@ export class AllTypes {
163163
// unions
164164

165165
unionProperty: string | number | Number | Multiply = 'foo';
166-
unionArrayProperty: (composition.CompositeOperation | number)[] = [];
166+
unionArrayProperty: (Value | number)[] = [];
167167
unionMapProperty: { [key: string]: (Number | number | string) } = {};
168168

169169
// enum
@@ -194,6 +194,21 @@ export class AllTypes {
194194
enumMethod(value: StringEnum) {
195195
return value;
196196
}
197+
198+
199+
public anyOut(): any {
200+
const ret = new Number(42);
201+
Object.defineProperty(ret, 'tag', {
202+
value: "you're it"
203+
});
204+
return ret;
205+
}
206+
207+
public anyIn(inp: any) {
208+
if (inp.tag !== "you're it") {
209+
throw new Error(`Not the same object that I gave you, got: ${JSON.stringify(inp)}`);
210+
}
211+
}
197212
}
198213

199214
//
@@ -1321,18 +1336,73 @@ export class PublicClass {
13211336
public hello(): void {}
13221337
}
13231338
export interface IPublicInterface {
1324-
bye(): void;
1339+
bye(): string;
1340+
}
1341+
1342+
export interface IPublicInterface2 {
1343+
ciao(): string;
1344+
}
1345+
export class InbetweenClass extends PublicClass implements IPublicInterface2 {
1346+
public ciao(): string { return 'ciao'; }
13251347
}
1326-
export class InbetweenClass extends PublicClass {}
13271348
class PrivateClass extends InbetweenClass implements IPublicInterface {
1328-
public bye(): void {}
1349+
public bye(): string { return 'bye'; }
1350+
}
1351+
1352+
class HiddenClass implements IPublicInterface, IPublicInterface2 {
1353+
public bye(): string { return 'bye'; }
1354+
public ciao(): string { return 'ciao'; }
1355+
}
1356+
1357+
class HiddenSubclass extends HiddenClass {
13291358
}
1359+
13301360
export class Constructors {
13311361
public static makeClass(): PublicClass {
1332-
return new PrivateClass();
1362+
return new PrivateClass(); // Wire type should be InbetweenClass
13331363
}
1364+
13341365
public static makeInterface(): IPublicInterface {
1335-
return new PrivateClass();
1366+
return new PrivateClass(); // Wire type should be IPublicInterface
1367+
}
1368+
1369+
public static makeInterface2(): IPublicInterface2 {
1370+
return new PrivateClass(); // Wire type should be InbetweenClass
1371+
}
1372+
1373+
public static makeInterfaces(): IPublicInterface[] {
1374+
return [new PrivateClass()]; // Wire type should be IPublicInterface[]
1375+
}
1376+
1377+
public static hiddenInterface(): IPublicInterface {
1378+
return new HiddenClass(); // Wire type should be IPublicInterface
1379+
}
1380+
1381+
public static hiddenInterfaces(): IPublicInterface[] {
1382+
return [new HiddenClass()]; // Wire type should be IPublicInterface[]
1383+
}
1384+
1385+
public static hiddenSubInterfaces(): IPublicInterface[] {
1386+
return [new HiddenSubclass()]; // Wire type should be IPublicInterface[]
1387+
}
1388+
}
1389+
1390+
/**
1391+
* Test that a single instance can be returned under two different FQNs
1392+
*
1393+
* JSII clients can instantiate 2 different strongly-typed wrappers for the same
1394+
* object. Unfortunately, this will break object equality, but if we didn't do
1395+
* this it would break runtime type checks in the JVM or CLR.
1396+
*/
1397+
export class SingleInstanceTwoTypes {
1398+
private instance = new PrivateClass();
1399+
1400+
public interface1(): InbetweenClass {
1401+
return this.instance;
1402+
}
1403+
1404+
public interface2(): IPublicInterface {
1405+
return this.instance;
13361406
}
13371407
}
13381408

packages/jsii-calc/test/assembly.jsii

Lines changed: 130 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,23 @@
377377
},
378378
"kind": "class",
379379
"methods": [
380+
{
381+
"name": "anyIn",
382+
"parameters": [
383+
{
384+
"name": "inp",
385+
"type": {
386+
"primitive": "any"
387+
}
388+
}
389+
]
390+
},
391+
{
392+
"name": "anyOut",
393+
"returns": {
394+
"primitive": "any"
395+
}
396+
},
380397
{
381398
"name": "enumMethod",
382399
"parameters": [
@@ -498,7 +515,7 @@
498515
"primitive": "number"
499516
},
500517
{
501-
"fqn": "jsii-calc.composition.CompositeOperation"
518+
"fqn": "@scope/jsii-calc-lib.Value"
502519
}
503520
]
504521
}
@@ -1241,6 +1258,37 @@
12411258
},
12421259
"kind": "class",
12431260
"methods": [
1261+
{
1262+
"name": "hiddenInterface",
1263+
"returns": {
1264+
"fqn": "jsii-calc.IPublicInterface"
1265+
},
1266+
"static": true
1267+
},
1268+
{
1269+
"name": "hiddenInterfaces",
1270+
"returns": {
1271+
"collection": {
1272+
"elementtype": {
1273+
"fqn": "jsii-calc.IPublicInterface"
1274+
},
1275+
"kind": "array"
1276+
}
1277+
},
1278+
"static": true
1279+
},
1280+
{
1281+
"name": "hiddenSubInterfaces",
1282+
"returns": {
1283+
"collection": {
1284+
"elementtype": {
1285+
"fqn": "jsii-calc.IPublicInterface"
1286+
},
1287+
"kind": "array"
1288+
}
1289+
},
1290+
"static": true
1291+
},
12441292
{
12451293
"name": "makeClass",
12461294
"returns": {
@@ -1254,6 +1302,25 @@
12541302
"fqn": "jsii-calc.IPublicInterface"
12551303
},
12561304
"static": true
1305+
},
1306+
{
1307+
"name": "makeInterface2",
1308+
"returns": {
1309+
"fqn": "jsii-calc.IPublicInterface2"
1310+
},
1311+
"static": true
1312+
},
1313+
{
1314+
"name": "makeInterfaces",
1315+
"returns": {
1316+
"collection": {
1317+
"elementtype": {
1318+
"fqn": "jsii-calc.IPublicInterface"
1319+
},
1320+
"kind": "array"
1321+
}
1322+
},
1323+
"static": true
12571324
}
12581325
],
12591326
"name": "Constructors"
@@ -2113,11 +2180,29 @@
21132180
"methods": [
21142181
{
21152182
"abstract": true,
2116-
"name": "bye"
2183+
"name": "bye",
2184+
"returns": {
2185+
"primitive": "string"
2186+
}
21172187
}
21182188
],
21192189
"name": "IPublicInterface"
21202190
},
2191+
"jsii-calc.IPublicInterface2": {
2192+
"assembly": "jsii-calc",
2193+
"fqn": "jsii-calc.IPublicInterface2",
2194+
"kind": "interface",
2195+
"methods": [
2196+
{
2197+
"abstract": true,
2198+
"name": "ciao",
2199+
"returns": {
2200+
"primitive": "string"
2201+
}
2202+
}
2203+
],
2204+
"name": "IPublicInterface2"
2205+
},
21212206
"jsii-calc.IRandomNumberGenerator": {
21222207
"assembly": "jsii-calc",
21232208
"docs": {
@@ -2264,7 +2349,23 @@
22642349
"initializer": {
22652350
"initializer": true
22662351
},
2352+
"interfaces": [
2353+
{
2354+
"fqn": "jsii-calc.IPublicInterface2"
2355+
}
2356+
],
22672357
"kind": "class",
2358+
"methods": [
2359+
{
2360+
"name": "ciao",
2361+
"overrides": {
2362+
"fqn": "jsii-calc.IPublicInterface2"
2363+
},
2364+
"returns": {
2365+
"primitive": "string"
2366+
}
2367+
}
2368+
],
22682369
"name": "InbetweenClass"
22692370
},
22702371
"jsii-calc.InterfaceImplementedByAbstractClass": {
@@ -3585,6 +3686,32 @@
35853686
],
35863687
"name": "RuntimeTypeChecking"
35873688
},
3689+
"jsii-calc.SingleInstanceTwoTypes": {
3690+
"assembly": "jsii-calc",
3691+
"docs": {
3692+
"comment": "Test that a single instance can be returned under two different FQNs\n\nJSII clients can instantiate 2 different strongly-typed wrappers for the same\nobject. Unfortunately, this will break object equality, but if we didn't do\nthis it would break runtime type checks in the JVM or CLR."
3693+
},
3694+
"fqn": "jsii-calc.SingleInstanceTwoTypes",
3695+
"initializer": {
3696+
"initializer": true
3697+
},
3698+
"kind": "class",
3699+
"methods": [
3700+
{
3701+
"name": "interface1",
3702+
"returns": {
3703+
"fqn": "jsii-calc.InbetweenClass"
3704+
}
3705+
},
3706+
{
3707+
"name": "interface2",
3708+
"returns": {
3709+
"fqn": "jsii-calc.IPublicInterface"
3710+
}
3711+
}
3712+
],
3713+
"name": "SingleInstanceTwoTypes"
3714+
},
35883715
"jsii-calc.Statics": {
35893716
"assembly": "jsii-calc",
35903717
"fqn": "jsii-calc.Statics",
@@ -4373,5 +4500,5 @@
43734500
}
43744501
},
43754502
"version": "0.8.0",
4376-
"fingerprint": "kxASQYx+RdQQFUSD4FNIHmKMdV4L37gNRKJx0DAohMQ="
4503+
"fingerprint": "WIFIhqgEwUDjCsDr7gliujhqcKZQSkDP+NstBfmdvZU="
43774504
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Dive into a single failing test:
2+
3+
JSII_DEBUG=1 mvn test -Dtest=software.amazon.jsii.testing.ComplianceTest#unionTypes

packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/ComplianceTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,12 @@ public void unionTypes() {
196196

197197
// map
198198
Map<String, Object> map = new HashMap<>();
199-
map.put("Foo", new Multiply(new Number(2), new Number(99)));
199+
map.put("Foo", new Number(99));
200200
types.setUnionMapProperty(map);
201201

202202
// array
203-
types.setUnionArrayProperty(Arrays.asList("Hello", 123, new Number(33)));
204-
assertEquals(33, ((Number)((List<?>)types.getUnionArrayProperty()).get(2)).getValue());
203+
types.setUnionArrayProperty(Arrays.asList(123, new Number(33)));
204+
assertEquals(33, ((Number)((List<?>)types.getUnionArrayProperty()).get(1)).getValue());
205205
}
206206

207207

packages/jsii-kernel/lib/api.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,50 @@ export const TOKEN_REF = '$jsii.byref';
22
export const TOKEN_DATE = '$jsii.date';
33
export const TOKEN_ENUM = '$jsii.enum';
44

5-
export class ObjRef {
6-
[token: string]: string; // token = TOKEN_REF
5+
export interface ObjRef {
6+
[TOKEN_REF]: string;
77
}
88

9-
export interface Override {
10-
method?: string;
11-
property?: string;
9+
export function isObjRef(value: any): value is ObjRef {
10+
return typeof value === 'object' && value !== null && TOKEN_REF in value;
11+
}
12+
13+
export interface WireDate {
14+
[TOKEN_DATE]: string;
15+
}
16+
17+
export function isWireDate(value: any): value is WireDate {
18+
return typeof value === 'object' && value !== null && TOKEN_DATE in value;
19+
}
20+
21+
export interface WireEnum {
22+
[TOKEN_ENUM]: string;
23+
}
24+
25+
export function isWireEnum(value: any): value is WireEnum {
26+
return typeof value === 'object' && value !== null && TOKEN_ENUM in value;
27+
}
28+
29+
export type Override = MethodOverride | PropertyOverride;
30+
31+
export interface MethodOverride {
32+
method: string;
33+
cookie?: string;
34+
}
35+
36+
export function isMethodOverride(value: Override): value is MethodOverride {
37+
return (value as any).method != null; // Python passes "null"
38+
}
39+
40+
export interface PropertyOverride {
41+
property: string;
1242
cookie?: string;
1343
}
1444

45+
export function isPropertyOverride(value: Override): value is PropertyOverride {
46+
return (value as any).property != null; // Python passes "null"
47+
}
48+
1549
export interface Callback {
1650
cbid: string;
1751
cookie: string | undefined;

0 commit comments

Comments
 (0)