Skip to content

Commit 8ea39ac

Browse files
authored
fix(kernel): Improve tagged type of wire values (#346)
When passing references across the JSII language boundary, the static return type of a method is used instead of the runtime type of the object if they are not the same, even if they are compatible. This causes issues that make it impossible to up-cast from methods such as `IConstruct.findChild` that return a super-class that is expected to be up-casted to it's known dynamic type. Fixes #345
1 parent 0301e30 commit 8ea39ac

File tree

23 files changed

+589
-7
lines changed

23 files changed

+589
-7
lines changed

packages/jsii-calc/lib/compliance.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1232,4 +1232,27 @@ export class JsiiAgent {
12321232
public static get jsiiAgent(): string | undefined {
12331233
return process.env.JSII_AGENT;
12341234
}
1235-
};
1235+
};
1236+
1237+
// Ensure the JSII kernel tags instances with the "most appropriate" FQN type label, so that runtimes are able to
1238+
// correctly choose the implementation proxy that should be used. Failure to do so could cause situations where userland
1239+
// needs to up-cast an instance to an incompatible type, which certain runtimes (such as Java) will prevent.
1240+
// @See https://github.com/awslabs/jsii/issues/345
1241+
export class PublicClass {
1242+
public hello(): void {}
1243+
}
1244+
export interface IPublicInterface {
1245+
bye(): void;
1246+
}
1247+
export class InbetweenClass extends PublicClass {}
1248+
class PrivateClass extends InbetweenClass implements IPublicInterface {
1249+
public bye(): void {}
1250+
}
1251+
export class Constructors {
1252+
public static makeClass(): PublicClass {
1253+
return new PrivateClass();
1254+
}
1255+
public static makeInterface(): IPublicInterface {
1256+
return new PrivateClass();
1257+
}
1258+
}

packages/jsii-calc/test/assembly.jsii

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,31 @@
10981098
}
10991099
]
11001100
},
1101+
"jsii-calc.Constructors": {
1102+
"assembly": "jsii-calc",
1103+
"fqn": "jsii-calc.Constructors",
1104+
"initializer": {
1105+
"initializer": true
1106+
},
1107+
"kind": "class",
1108+
"methods": [
1109+
{
1110+
"name": "makeClass",
1111+
"returns": {
1112+
"fqn": "jsii-calc.PublicClass"
1113+
},
1114+
"static": true
1115+
},
1116+
{
1117+
"name": "makeInterface",
1118+
"returns": {
1119+
"fqn": "jsii-calc.IPublicInterface"
1120+
},
1121+
"static": true
1122+
}
1123+
],
1124+
"name": "Constructors"
1125+
},
11011126
"jsii-calc.DefaultedConstructorArgument": {
11021127
"assembly": "jsii-calc",
11031128
"fqn": "jsii-calc.DefaultedConstructorArgument",
@@ -1675,6 +1700,18 @@
16751700
}
16761701
]
16771702
},
1703+
"jsii-calc.IPublicInterface": {
1704+
"assembly": "jsii-calc",
1705+
"fqn": "jsii-calc.IPublicInterface",
1706+
"kind": "interface",
1707+
"methods": [
1708+
{
1709+
"abstract": true,
1710+
"name": "bye"
1711+
}
1712+
],
1713+
"name": "IPublicInterface"
1714+
},
16781715
"jsii-calc.IRandomNumberGenerator": {
16791716
"assembly": "jsii-calc",
16801717
"docs": {
@@ -1743,6 +1780,18 @@
17431780
}
17441781
]
17451782
},
1783+
"jsii-calc.InbetweenClass": {
1784+
"assembly": "jsii-calc",
1785+
"base": {
1786+
"fqn": "jsii-calc.PublicClass"
1787+
},
1788+
"fqn": "jsii-calc.InbetweenClass",
1789+
"initializer": {
1790+
"initializer": true
1791+
},
1792+
"kind": "class",
1793+
"name": "InbetweenClass"
1794+
},
17461795
"jsii-calc.InterfaceImplementedByAbstractClass": {
17471796
"assembly": "jsii-calc",
17481797
"datatype": true,
@@ -2852,6 +2901,20 @@
28522901
}
28532902
]
28542903
},
2904+
"jsii-calc.PublicClass": {
2905+
"assembly": "jsii-calc",
2906+
"fqn": "jsii-calc.PublicClass",
2907+
"initializer": {
2908+
"initializer": true
2909+
},
2910+
"kind": "class",
2911+
"methods": [
2912+
{
2913+
"name": "hello"
2914+
}
2915+
],
2916+
"name": "PublicClass"
2917+
},
28552918
"jsii-calc.ReferenceEnumFromScopedPackage": {
28562919
"assembly": "jsii-calc",
28572920
"docs": {
@@ -3762,5 +3825,5 @@
37623825
}
37633826
},
37643827
"version": "0.7.13",
3765-
"fingerprint": "mc9Rni6GLgVDz8M6CGHapEiYbWsdEIl7CmBI0Qn+KXU="
3828+
"fingerprint": "WNp0Sw1d2+3FaQD0Fn01K4wYQi7Ms3w+NQatOylD1kM="
37663829
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,16 @@ public void ReceiveInstanceOfPrivateClass()
874874
Assert.True(new ReturnsPrivateImplementationOfInterface().PrivateImplementation.Success);
875875
}
876876

877+
[Fact(DisplayName = Prefix + nameof(ObjRefsAreLabelledUsingWithTheMostCorrectType))]
878+
public void ObjRefsAreLabelledUsingWithTheMostCorrectType()
879+
{
880+
var classRef = Constructors.MakeClass();
881+
var ifaceRef = Constructors.MakeInterface();
882+
883+
Assert.Equal(typeof(InbetweenClass), classRef.GetType());
884+
Assert.NotEqual(typeof(InbetweenClass), ifaceRef.GetType());
885+
}
886+
877887
class NumberReturner : DeputyBase, IIReturnsNumber
878888
{
879889
public NumberReturner(double number)
@@ -1038,4 +1048,4 @@ public double Next()
10381048
}
10391049
}
10401050
}
1041-
}
1051+
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
import software.amazon.jsii.tests.calculator.GreetingAugmenter;
1818
import software.amazon.jsii.tests.calculator.IFriendlier;
1919
import software.amazon.jsii.tests.calculator.IFriendlyRandomGenerator;
20+
import software.amazon.jsii.tests.calculator.IPublicInterface;
2021
import software.amazon.jsii.tests.calculator.InterfaceWithProperties;
2122
import software.amazon.jsii.tests.calculator.IRandomNumberGenerator;
23+
import software.amazon.jsii.tests.calculator.InbetweenClass;
2224
import software.amazon.jsii.tests.calculator.InterfaceImplementedByAbstractClass;
2325
import software.amazon.jsii.tests.calculator.JSObjectLiteralForInterface;
2426
import software.amazon.jsii.tests.calculator.JSObjectLiteralToNative;
@@ -31,6 +33,7 @@
3133
import software.amazon.jsii.tests.calculator.NumberGenerator;
3234
import software.amazon.jsii.tests.calculator.Polymorphism;
3335
import software.amazon.jsii.tests.calculator.Power;
36+
import software.amazon.jsii.tests.calculator.PublicClass;
3437
import software.amazon.jsii.tests.calculator.ReferenceEnumFromScopedPackage;
3538
import software.amazon.jsii.tests.calculator.ReturnsPrivateImplementationOfInterface;
3639
import software.amazon.jsii.tests.calculator.Statics;
@@ -48,6 +51,8 @@
4851
import software.amazon.jsii.tests.calculator.lib.Value;
4952
import software.amazon.jsii.tests.calculator.JavaReservedWords;
5053
import software.amazon.jsii.tests.calculator.ClassWithPrivateConstructorAndAutomaticProperties;
54+
import software.amazon.jsii.tests.calculator.Constructors;
55+
5156
import org.junit.Test;
5257

5358
import java.io.IOException;
@@ -954,7 +959,7 @@ public void nullShouldBeTreatedAsUndefined() {
954959
obj.setChangeMeToUndefined(null);
955960
obj.verifyPropertyIsUndefined();
956961
}
957-
962+
958963
@Test
959964
public void testJsiiAgent() {
960965
assertEquals("Java/" + System.getProperty("java.version"), JsiiAgent.getJsiiAgent());
@@ -968,6 +973,15 @@ public void receiveInstanceOfPrivateClass() {
968973
assertTrue(new ReturnsPrivateImplementationOfInterface().getPrivateImplementation().getSuccess());
969974
}
970975

976+
@Test
977+
public void objRefsAreLabelledUsingWithTheMostCorrectType() {
978+
final PublicClass classRef = Constructors.makeClass();
979+
final IPublicInterface ifaceRef = Constructors.makeInterface();
980+
981+
assertTrue(classRef instanceof InbetweenClass);
982+
assertTrue(ifaceRef instanceof IPublicInterface);
983+
}
984+
971985
static class MulTen extends Multiply {
972986
public MulTen(final int value) {
973987
super(new Number(value), new Number(10));

packages/jsii-kernel/lib/kernel.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,7 @@ export class Kernel {
961961
// have an object id, so we need to allocate one for it.
962962
this._debug('creating objref for', v);
963963
const fqn = this._fqnForObject(v);
964-
if (!targetType || !spec.isNamedTypeReference(targetType) || fqn === targetType.fqn) {
964+
if (!targetType || !spec.isNamedTypeReference(targetType) || this._isAssignable(fqn, targetType)) {
965965
return this._createObjref(v, fqn);
966966
}
967967
}
@@ -1029,6 +1029,31 @@ export class Kernel {
10291029
return v;
10301030
}
10311031

1032+
/**
1033+
* Tests whether a given type (by it's FQN) can be assigned to a named type reference.
1034+
*
1035+
* @param actualTypeFqn the FQN of the type that is being tested.
1036+
* @param requiredType the required reference type.
1037+
*
1038+
* @returns true if ``requiredType`` is a super-type (base class or implemented interface) of the type designated by
1039+
* ``actualTypeFqn``.
1040+
*/
1041+
private _isAssignable(actualTypeFqn: string, requiredType: spec.NamedTypeReference): boolean {
1042+
if (requiredType.fqn === actualTypeFqn) {
1043+
return true;
1044+
}
1045+
const actualType = this._typeInfoForFqn(actualTypeFqn);
1046+
if (spec.isClassType(actualType) && actualType.base) {
1047+
if (this._isAssignable(actualType.base.fqn, requiredType)) {
1048+
return true;
1049+
}
1050+
}
1051+
if (spec.isClassOrInterfaceType(actualType) && actualType.interfaces) {
1052+
return actualType.interfaces.find(iface => this._isAssignable(iface.fqn, requiredType)) != null;
1053+
}
1054+
return false;
1055+
}
1056+
10321057
private _toSandboxValues(args: any[]) {
10331058
return args.map(v => this._toSandbox(v));
10341059
}

packages/jsii-kernel/test/test.kernel.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,7 @@ defineTest('object literals are returned by reference', async (test, sandbox) =>
885885

886886
test.equal(newValue,
887887
sandbox.get({
888-
objref: sandbox.get({ objref, property: 'mutableObject' }).value,
888+
objref: sandbox.get({ objref, property: 'mutableObject' }).value,
889889
property: 'value'
890890
}).value);
891891

@@ -963,6 +963,16 @@ defineTest('JSII_AGENT is undefined in node.js', async (test, sandbox) => {
963963
test.equal(sandbox.sget({ fqn: 'jsii-calc.JsiiAgent', property: 'jsiiAgent' }).value, undefined);
964964
});
965965

966+
defineTest('ObjRefs are labeled with the "most correct" type', async (test, sandbox) => {
967+
const classRef = sandbox.sinvoke({ fqn: 'jsii-calc.Constructors', method: 'makeClass' }).result as api.ObjRef;
968+
const ifaceRef = sandbox.sinvoke({ fqn: 'jsii-calc.Constructors', method: 'makeInterface' }).result as api.ObjRef;
969+
970+
test.ok(classRef[api.TOKEN_REF].startsWith('jsii-calc.InbetweenClass'),
971+
`${classRef[api.TOKEN_REF]} starts with jsii-calc.InbetweenClass`);
972+
test.ok(ifaceRef[api.TOKEN_REF].startsWith('jsii-calc.IPublicInterface'),
973+
`${ifaceRef[api.TOKEN_REF]} starts with jsii-calc.IPublicInterface`);
974+
});
975+
966976
// =================================================================================================
967977

968978
const testNames: { [name: string]: boolean } = { };

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

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,31 @@
10981098
}
10991099
]
11001100
},
1101+
"jsii-calc.Constructors": {
1102+
"assembly": "jsii-calc",
1103+
"fqn": "jsii-calc.Constructors",
1104+
"initializer": {
1105+
"initializer": true
1106+
},
1107+
"kind": "class",
1108+
"methods": [
1109+
{
1110+
"name": "makeClass",
1111+
"returns": {
1112+
"fqn": "jsii-calc.PublicClass"
1113+
},
1114+
"static": true
1115+
},
1116+
{
1117+
"name": "makeInterface",
1118+
"returns": {
1119+
"fqn": "jsii-calc.IPublicInterface"
1120+
},
1121+
"static": true
1122+
}
1123+
],
1124+
"name": "Constructors"
1125+
},
11011126
"jsii-calc.DefaultedConstructorArgument": {
11021127
"assembly": "jsii-calc",
11031128
"fqn": "jsii-calc.DefaultedConstructorArgument",
@@ -1675,6 +1700,18 @@
16751700
}
16761701
]
16771702
},
1703+
"jsii-calc.IPublicInterface": {
1704+
"assembly": "jsii-calc",
1705+
"fqn": "jsii-calc.IPublicInterface",
1706+
"kind": "interface",
1707+
"methods": [
1708+
{
1709+
"abstract": true,
1710+
"name": "bye"
1711+
}
1712+
],
1713+
"name": "IPublicInterface"
1714+
},
16781715
"jsii-calc.IRandomNumberGenerator": {
16791716
"assembly": "jsii-calc",
16801717
"docs": {
@@ -1743,6 +1780,18 @@
17431780
}
17441781
]
17451782
},
1783+
"jsii-calc.InbetweenClass": {
1784+
"assembly": "jsii-calc",
1785+
"base": {
1786+
"fqn": "jsii-calc.PublicClass"
1787+
},
1788+
"fqn": "jsii-calc.InbetweenClass",
1789+
"initializer": {
1790+
"initializer": true
1791+
},
1792+
"kind": "class",
1793+
"name": "InbetweenClass"
1794+
},
17461795
"jsii-calc.InterfaceImplementedByAbstractClass": {
17471796
"assembly": "jsii-calc",
17481797
"datatype": true,
@@ -2852,6 +2901,20 @@
28522901
}
28532902
]
28542903
},
2904+
"jsii-calc.PublicClass": {
2905+
"assembly": "jsii-calc",
2906+
"fqn": "jsii-calc.PublicClass",
2907+
"initializer": {
2908+
"initializer": true
2909+
},
2910+
"kind": "class",
2911+
"methods": [
2912+
{
2913+
"name": "hello"
2914+
}
2915+
],
2916+
"name": "PublicClass"
2917+
},
28552918
"jsii-calc.ReferenceEnumFromScopedPackage": {
28562919
"assembly": "jsii-calc",
28572920
"docs": {
@@ -3762,5 +3825,5 @@
37623825
}
37633826
},
37643827
"version": "0.7.13",
3765-
"fingerprint": "mc9Rni6GLgVDz8M6CGHapEiYbWsdEIl7CmBI0Qn+KXU="
3828+
"fingerprint": "WNp0Sw1d2+3FaQD0Fn01K4wYQi7Ms3w+NQatOylD1kM="
37663829
}

0 commit comments

Comments
 (0)