Skip to content

Commit 9c59acc

Browse files
authored
fix(kernel): Correctly return instances of un-exported types (#321)
When an un-exported type that extends an exported type is returned with an interface as the declared type, the JSII kernel used to return a ref with the FQN of the exported supertype, instead of correctly wrapping the instance in a proxy of the interface type as it should have. --- Adds a test that covers the behavior of the JSII runtimes when a method is declared to return an interface type, and returns an instance of a private (un-exported) type that implements the interface while extending an exported type. This has been seen to cause issues in the Java runtime, for example, as the JSII kernel will return an ObjID with a type fragment that refers to the exported super-class, and not the interface type. --- Fixes #302
1 parent 44c3b9b commit 9c59acc

File tree

20 files changed

+458
-12
lines changed

20 files changed

+458
-12
lines changed

packages/jsii-build-tools/package-lock.json

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/jsii-calc/lib/compliance.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,3 +1196,28 @@ export interface LoadBalancedFargateServiceProps {
11961196
*/
11971197
publicTasks?: boolean;
11981198
}
1199+
1200+
/**
1201+
* Helps ensure the JSII kernel & runtime cooperate correctly when an un-exported instance of a class is returned with
1202+
* a declared type that is an exported interface, and the instance inherits from an exported class.
1203+
*
1204+
* @returns an instance of an un-exported class that extends ``ExportedBaseClass``, declared as ``IPrivatelyImplemented``.
1205+
*
1206+
* @see https://github.com/awslabs/jsii/issues/320
1207+
*/
1208+
export class ReturnsPrivateImplementationOfInterface {
1209+
public get privateImplementation(): IPrivatelyImplemented {
1210+
return new PrivateImplementation();
1211+
}
1212+
}
1213+
export interface IPrivatelyImplemented {
1214+
readonly success: boolean;
1215+
}
1216+
export class ExportedBaseClass {
1217+
constructor(public readonly success: boolean) {}
1218+
}
1219+
class PrivateImplementation extends ExportedBaseClass implements IPrivatelyImplemented {
1220+
constructor() {
1221+
super(true);
1222+
}
1223+
}

packages/jsii-calc/test/assembly.jsii

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1410,6 +1410,32 @@
14101410
],
14111411
"name": "DoubleTrouble"
14121412
},
1413+
"jsii-calc.ExportedBaseClass": {
1414+
"assembly": "jsii-calc",
1415+
"fqn": "jsii-calc.ExportedBaseClass",
1416+
"initializer": {
1417+
"initializer": true,
1418+
"parameters": [
1419+
{
1420+
"name": "success",
1421+
"type": {
1422+
"primitive": "boolean"
1423+
}
1424+
}
1425+
]
1426+
},
1427+
"kind": "class",
1428+
"name": "ExportedBaseClass",
1429+
"properties": [
1430+
{
1431+
"immutable": true,
1432+
"name": "success",
1433+
"type": {
1434+
"primitive": "boolean"
1435+
}
1436+
}
1437+
]
1438+
},
14131439
"jsii-calc.GiveMeStructs": {
14141440
"assembly": "jsii-calc",
14151441
"fqn": "jsii-calc.GiveMeStructs",
@@ -1633,6 +1659,22 @@
16331659
],
16341660
"name": "IInterfaceWithOptionalMethodArguments"
16351661
},
1662+
"jsii-calc.IPrivatelyImplemented": {
1663+
"assembly": "jsii-calc",
1664+
"fqn": "jsii-calc.IPrivatelyImplemented",
1665+
"kind": "interface",
1666+
"name": "IPrivatelyImplemented",
1667+
"properties": [
1668+
{
1669+
"abstract": true,
1670+
"immutable": true,
1671+
"name": "success",
1672+
"type": {
1673+
"primitive": "boolean"
1674+
}
1675+
}
1676+
]
1677+
},
16361678
"jsii-calc.IRandomNumberGenerator": {
16371679
"assembly": "jsii-calc",
16381680
"docs": {
@@ -2825,6 +2867,29 @@
28252867
}
28262868
]
28272869
},
2870+
"jsii-calc.ReturnsPrivateImplementationOfInterface": {
2871+
"assembly": "jsii-calc",
2872+
"docs": {
2873+
"comment": "Helps ensure the JSII kernel & runtime cooperate correctly when an un-exported instance of a class is returned with\na declared type that is an exported interface, and the instance inherits from an exported class.",
2874+
"return": "an instance of an un-exported class that extends ``ExportedBaseClass``, declared as ``IPrivatelyImplemented``.",
2875+
"see": "https://github.com/awslabs/jsii/issues/320"
2876+
},
2877+
"fqn": "jsii-calc.ReturnsPrivateImplementationOfInterface",
2878+
"initializer": {
2879+
"initializer": true
2880+
},
2881+
"kind": "class",
2882+
"name": "ReturnsPrivateImplementationOfInterface",
2883+
"properties": [
2884+
{
2885+
"immutable": true,
2886+
"name": "privateImplementation",
2887+
"type": {
2888+
"fqn": "jsii-calc.IPrivatelyImplemented"
2889+
}
2890+
}
2891+
]
2892+
},
28282893
"jsii-calc.RuntimeTypeChecking": {
28292894
"assembly": "jsii-calc",
28302895
"fqn": "jsii-calc.RuntimeTypeChecking",
@@ -3671,5 +3736,5 @@
36713736
}
36723737
},
36733738
"version": "0.7.11",
3674-
"fingerprint": "2o7FtEirv0LpuaJ1G+wAxoHTW7FHr4U1taZ5GcVYt2o="
3739+
"fingerprint": "Nw3vyHfzec4IFe674buqsgL3x8QgvKP0lUC3csRIW7g="
36753740
}

packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,12 @@ public void NullShouldBeTreatedAsUndefined()
862862
obj.VerifyPropertyIsUndefined();
863863
}
864864

865+
[Fact(DisplayName = Prefix + nameof(ReceiveInstanceOfPrivateClass))]
866+
public void ReceiveInstanceOfPrivateClass()
867+
{
868+
Assert.True(new ReturnsPrivateImplementationOfInterface().PrivateImplementation.Success);
869+
}
870+
865871
class NumberReturner : DeputyBase, IIReturnsNumber
866872
{
867873
public NumberReturner(double number)

packages/jsii-java-runtime-test/package-lock.json

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import software.amazon.jsii.tests.calculator.Polymorphism;
3333
import software.amazon.jsii.tests.calculator.Power;
3434
import software.amazon.jsii.tests.calculator.ReferenceEnumFromScopedPackage;
35+
import software.amazon.jsii.tests.calculator.ReturnsPrivateImplementationOfInterface;
3536
import software.amazon.jsii.tests.calculator.Statics;
3637
import software.amazon.jsii.tests.calculator.Sum;
3738
import software.amazon.jsii.tests.calculator.SyncVirtualMethods;
@@ -953,6 +954,14 @@ public void nullShouldBeTreatedAsUndefined() {
953954
obj.verifyPropertyIsUndefined();
954955
}
955956

957+
/**
958+
* @see https://github.com/awslabs/jsii/issues/320
959+
*/
960+
@Test
961+
public void receiveInstanceOfPrivateClass() {
962+
assertTrue(new ReturnsPrivateImplementationOfInterface().getPrivateImplementation().getSuccess());
963+
}
964+
956965
static class MulTen extends Multiply {
957966
public MulTen(final int value) {
958967
super(new Number(value), new Number(10));

packages/jsii-java-runtime/pom.xml.t.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ process.stdout.write(`<?xml version="1.0" encoding="UTF-8"?>
8080
<dependency>
8181
<groupId>javax.annotation</groupId>
8282
<artifactId>javax.annotation-api</artifactId>
83-
<version>[1.2,)</version>
83+
<version>[1.3.2,)</version>
8484
<scope>provided</scope>
8585
</dependency>
8686
</dependencies>

packages/jsii-kernel/lib/kernel.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,12 @@ export class Kernel {
435435
case spec.TypeKind.Class:
436436
case spec.TypeKind.Enum:
437437
const constructor = this._findSymbol(fqn);
438-
constructor.__jsii__ = { fqn };
438+
Object.defineProperty(constructor, '__jsii__', {
439+
configurable: false,
440+
enumerable: false,
441+
writable: false,
442+
value: { fqn }
443+
});
439444
}
440445
}
441446
}
@@ -956,7 +961,9 @@ export class Kernel {
956961
// have an object id, so we need to allocate one for it.
957962
this._debug('creating objref for', v);
958963
const fqn = this._fqnForObject(v);
959-
return this._createObjref(v, fqn);
964+
if (!targetType || !spec.isNamedTypeReference(targetType) || fqn === targetType.fqn) {
965+
return this._createObjref(v, fqn);
966+
}
960967
}
961968

962969
// if the method/property returns an object literal and the return type

packages/jsii-pacmak/lib/targets/java.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ class JavaGenerator extends Generator {
389389

390390
'properties': { 'project.build.sourceEncoding': 'UTF-8' },
391391

392-
'dependencies': { dependency: mavenDependencies() },
392+
'dependencies': { dependency: mavenDependencies() },
393393

394394
'build': {
395395
plugins: {

packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1410,6 +1410,32 @@
14101410
],
14111411
"name": "DoubleTrouble"
14121412
},
1413+
"jsii-calc.ExportedBaseClass": {
1414+
"assembly": "jsii-calc",
1415+
"fqn": "jsii-calc.ExportedBaseClass",
1416+
"initializer": {
1417+
"initializer": true,
1418+
"parameters": [
1419+
{
1420+
"name": "success",
1421+
"type": {
1422+
"primitive": "boolean"
1423+
}
1424+
}
1425+
]
1426+
},
1427+
"kind": "class",
1428+
"name": "ExportedBaseClass",
1429+
"properties": [
1430+
{
1431+
"immutable": true,
1432+
"name": "success",
1433+
"type": {
1434+
"primitive": "boolean"
1435+
}
1436+
}
1437+
]
1438+
},
14131439
"jsii-calc.GiveMeStructs": {
14141440
"assembly": "jsii-calc",
14151441
"fqn": "jsii-calc.GiveMeStructs",
@@ -1633,6 +1659,22 @@
16331659
],
16341660
"name": "IInterfaceWithOptionalMethodArguments"
16351661
},
1662+
"jsii-calc.IPrivatelyImplemented": {
1663+
"assembly": "jsii-calc",
1664+
"fqn": "jsii-calc.IPrivatelyImplemented",
1665+
"kind": "interface",
1666+
"name": "IPrivatelyImplemented",
1667+
"properties": [
1668+
{
1669+
"abstract": true,
1670+
"immutable": true,
1671+
"name": "success",
1672+
"type": {
1673+
"primitive": "boolean"
1674+
}
1675+
}
1676+
]
1677+
},
16361678
"jsii-calc.IRandomNumberGenerator": {
16371679
"assembly": "jsii-calc",
16381680
"docs": {
@@ -2825,6 +2867,29 @@
28252867
}
28262868
]
28272869
},
2870+
"jsii-calc.ReturnsPrivateImplementationOfInterface": {
2871+
"assembly": "jsii-calc",
2872+
"docs": {
2873+
"comment": "Helps ensure the JSII kernel & runtime cooperate correctly when an un-exported instance of a class is returned with\na declared type that is an exported interface, and the instance inherits from an exported class.",
2874+
"return": "an instance of an un-exported class that extends ``ExportedBaseClass``, declared as ``IPrivatelyImplemented``.",
2875+
"see": "https://github.com/awslabs/jsii/issues/320"
2876+
},
2877+
"fqn": "jsii-calc.ReturnsPrivateImplementationOfInterface",
2878+
"initializer": {
2879+
"initializer": true
2880+
},
2881+
"kind": "class",
2882+
"name": "ReturnsPrivateImplementationOfInterface",
2883+
"properties": [
2884+
{
2885+
"immutable": true,
2886+
"name": "privateImplementation",
2887+
"type": {
2888+
"fqn": "jsii-calc.IPrivatelyImplemented"
2889+
}
2890+
}
2891+
]
2892+
},
28282893
"jsii-calc.RuntimeTypeChecking": {
28292894
"assembly": "jsii-calc",
28302895
"fqn": "jsii-calc.RuntimeTypeChecking",
@@ -3671,5 +3736,5 @@
36713736
}
36723737
},
36733738
"version": "0.7.11",
3674-
"fingerprint": "2o7FtEirv0LpuaJ1G+wAxoHTW7FHr4U1taZ5GcVYt2o="
3739+
"fingerprint": "Nw3vyHfzec4IFe674buqsgL3x8QgvKP0lUC3csRIW7g="
36753740
}

0 commit comments

Comments
 (0)