Skip to content

Commit c89d0fa

Browse files
authored
fix(.net): occasional incorrect param type cast (#568)
When structures (as literal JSON blocks) get passed through a "map" argument, a `JObject` would be passed to the method despite the declared type on the .NET code is `IDictionary`, resulting in a crash. This code forcefully converts `JObject` to `IDictionary<string, objetc>` in such code paths. Fixes aws/aws-cdk#3093
1 parent a4de2d8 commit c89d0fa

File tree

17 files changed

+415
-4
lines changed

17 files changed

+415
-4
lines changed

packages/jsii-calc/lib/compliance.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,3 +1726,18 @@ export enum SingletonIntEnum {
17261726
/** Elite! */
17271727
SINGLETON_INT = 1337
17281728
}
1729+
1730+
/**
1731+
* Verifies proper type handling through dynamic overrides.
1732+
*/
1733+
export class DataRenderer {
1734+
constructor() { }
1735+
1736+
public render(data: MyFirstStruct = { anumber: 42, astring: 'bazinga!' }): string {
1737+
return this.renderMap(data);
1738+
}
1739+
1740+
public renderMap(map: { [key: string]: any }): string {
1741+
return JSON.stringify(map, null, 2);
1742+
}
1743+
}

packages/jsii-calc/test/assembly.jsii

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,79 @@
20822082
],
20832083
"name": "ConsumersOfThisCrazyTypeSystem"
20842084
},
2085+
"jsii-calc.DataRenderer": {
2086+
"assembly": "jsii-calc",
2087+
"docs": {
2088+
"stability": "experimental",
2089+
"summary": "Verifies proper type handling through dynamic overrides."
2090+
},
2091+
"fqn": "jsii-calc.DataRenderer",
2092+
"initializer": {
2093+
"docs": {
2094+
"stability": "experimental"
2095+
}
2096+
},
2097+
"kind": "class",
2098+
"locationInModule": {
2099+
"filename": "lib/compliance.ts",
2100+
"line": 1733
2101+
},
2102+
"methods": [
2103+
{
2104+
"docs": {
2105+
"stability": "experimental"
2106+
},
2107+
"locationInModule": {
2108+
"filename": "lib/compliance.ts",
2109+
"line": 1736
2110+
},
2111+
"name": "render",
2112+
"parameters": [
2113+
{
2114+
"name": "data",
2115+
"optional": true,
2116+
"type": {
2117+
"fqn": "@scope/jsii-calc-lib.MyFirstStruct"
2118+
}
2119+
}
2120+
],
2121+
"returns": {
2122+
"type": {
2123+
"primitive": "string"
2124+
}
2125+
}
2126+
},
2127+
{
2128+
"docs": {
2129+
"stability": "experimental"
2130+
},
2131+
"locationInModule": {
2132+
"filename": "lib/compliance.ts",
2133+
"line": 1740
2134+
},
2135+
"name": "renderMap",
2136+
"parameters": [
2137+
{
2138+
"name": "map",
2139+
"type": {
2140+
"collection": {
2141+
"elementtype": {
2142+
"primitive": "any"
2143+
},
2144+
"kind": "map"
2145+
}
2146+
}
2147+
}
2148+
],
2149+
"returns": {
2150+
"type": {
2151+
"primitive": "string"
2152+
}
2153+
}
2154+
}
2155+
],
2156+
"name": "DataRenderer"
2157+
},
20852158
"jsii-calc.DefaultedConstructorArgument": {
20862159
"assembly": "jsii-calc",
20872160
"docs": {
@@ -8778,5 +8851,5 @@
87788851
}
87798852
},
87808853
"version": "0.13.2",
8781-
"fingerprint": "IzBpmwowWT7JOt7LEK9v3wIGmNWcpmqf9O1C927cs8o="
8854+
"fingerprint": "iCTmGcYc3DTgEt3Y8PBPaiJNOI7O9tRRGI49RI6rAwk="
87828855
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,22 @@ public void CorrectlyReturnsFromVoidCallback()
915915
Assert.True(voidCallback.MethodWasCalled);
916916
}
917917

918+
[Fact(DisplayName = Prefix + nameof(CallbacksCorrectlyDeserializeArguments))]
919+
public void CallbacksCorrectlyDeserializeArguments()
920+
{
921+
var obj = new DataRendererSubclass();
922+
Assert.Equal("{\n \"anumber\": 42,\n \"astring\": \"bazinga!\"\n}", obj.Render(null));
923+
}
924+
925+
class DataRendererSubclass : DataRenderer
926+
{
927+
[JsiiMethod("renderMap", returnsJson: "{\"type\":{\"primitive\":\"string\"}}", parametersJson: "[{\"name\":\"map\",\"type\":{\"collection\":{\"kind\":\"map\",\"elementtype\":{\"primitive\":\"any\"}}}}]", isOverride: true)]
928+
public override string RenderMap(IDictionary<string, object> map)
929+
{
930+
return base.RenderMap(map);
931+
}
932+
}
933+
918934
class VoidCallbackImpl : VoidCallback
919935
{
920936
protected override void OverrideMe()

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/CallbackExtensions.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Amazon.JSII.Runtime.Services.Converters;
77
using Newtonsoft.Json.Linq;
88
using System;
9+
using System.Collections.Generic;
910
using System.Linq;
1011
using System.Reflection;
1112

@@ -136,6 +137,22 @@ private static object FromKernel(object obj, IReferenceMap referenceMap)
136137
{
137138
return referenceMap.GetOrCreateNativeReference(new ByRefValue(prop.Value.Value<String>()));
138139
}
140+
141+
/*
142+
* Turning all outstanding JObjects to IDictionary<string, object> (recursively), as the code generator
143+
* will have emitted IDictionary<string, object> for maps of string to <anything>. Not doing so would
144+
* result in an ArgumentError for not being able to convert JObject to IDictionary.
145+
*/
146+
var dict = ((JObject)obj).ToObject<Dictionary<string, object>>();
147+
foreach (var key in dict.Keys)
148+
{
149+
var value = dict[key];
150+
if (value != null && value.GetType() == typeof(JObject))
151+
{
152+
dict[key] = FromKernel(value, referenceMap);
153+
}
154+
}
155+
return dict;
139156
}
140157
return obj;
141158
}
@@ -145,7 +162,7 @@ internal class CallbackResult : OptionalValue
145162
{
146163
public CallbackResult(IOptionalValue optionalValue, object value)
147164
: this(optionalValue?.Type, optionalValue?.IsOptional ?? false, value) {}
148-
165+
149166
private CallbackResult(TypeReference type, bool isOptional, object value): base(type, isOptional)
150167
{
151168
Value = value;

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import software.amazon.jsii.tests.calculator.CalculatorProps;
1515
import software.amazon.jsii.tests.calculator.ClassWithPrivateConstructorAndAutomaticProperties;
1616
import software.amazon.jsii.tests.calculator.Constructors;
17+
import software.amazon.jsii.tests.calculator.DataRenderer;
1718
import software.amazon.jsii.tests.calculator.DerivedStruct;
1819
import software.amazon.jsii.tests.calculator.DoNotOverridePrivates;
1920
import software.amazon.jsii.tests.calculator.DoubleTrouble;
@@ -1037,6 +1038,16 @@ public void variadicMethodCanBeInvoked() {
10371038
assertEquals(Arrays.asList(1, 3, 4, 5, 6), result);
10381039
}
10391040

1041+
@Test
1042+
public void callbacsCorrectlyDeserializeArguments() {
1043+
final DataRenderer renderer = new DataRenderer() {
1044+
public final String renderMap(final Map<String, Object> map) {
1045+
return super.renderMap(map);
1046+
}
1047+
};
1048+
assertEquals("{\n \"anumber\": 42,\n \"astring\": \"bazinga!\"\n}", renderer.render());
1049+
}
1050+
10401051
static class PartiallyInitializedThisConsumerImpl extends PartiallyInitializedThisConsumer {
10411052
@Override
10421053
public String consumePartiallyInitializedThis(final ConstructorPassesThisOut obj,

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

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,79 @@
20822082
],
20832083
"name": "ConsumersOfThisCrazyTypeSystem"
20842084
},
2085+
"jsii-calc.DataRenderer": {
2086+
"assembly": "jsii-calc",
2087+
"docs": {
2088+
"stability": "experimental",
2089+
"summary": "Verifies proper type handling through dynamic overrides."
2090+
},
2091+
"fqn": "jsii-calc.DataRenderer",
2092+
"initializer": {
2093+
"docs": {
2094+
"stability": "experimental"
2095+
}
2096+
},
2097+
"kind": "class",
2098+
"locationInModule": {
2099+
"filename": "lib/compliance.ts",
2100+
"line": 1733
2101+
},
2102+
"methods": [
2103+
{
2104+
"docs": {
2105+
"stability": "experimental"
2106+
},
2107+
"locationInModule": {
2108+
"filename": "lib/compliance.ts",
2109+
"line": 1736
2110+
},
2111+
"name": "render",
2112+
"parameters": [
2113+
{
2114+
"name": "data",
2115+
"optional": true,
2116+
"type": {
2117+
"fqn": "@scope/jsii-calc-lib.MyFirstStruct"
2118+
}
2119+
}
2120+
],
2121+
"returns": {
2122+
"type": {
2123+
"primitive": "string"
2124+
}
2125+
}
2126+
},
2127+
{
2128+
"docs": {
2129+
"stability": "experimental"
2130+
},
2131+
"locationInModule": {
2132+
"filename": "lib/compliance.ts",
2133+
"line": 1740
2134+
},
2135+
"name": "renderMap",
2136+
"parameters": [
2137+
{
2138+
"name": "map",
2139+
"type": {
2140+
"collection": {
2141+
"elementtype": {
2142+
"primitive": "any"
2143+
},
2144+
"kind": "map"
2145+
}
2146+
}
2147+
}
2148+
],
2149+
"returns": {
2150+
"type": {
2151+
"primitive": "string"
2152+
}
2153+
}
2154+
}
2155+
],
2156+
"name": "DataRenderer"
2157+
},
20852158
"jsii-calc.DefaultedConstructorArgument": {
20862159
"assembly": "jsii-calc",
20872160
"docs": {
@@ -8778,5 +8851,5 @@
87788851
}
87798852
},
87808853
"version": "0.13.2",
8781-
"fingerprint": "IzBpmwowWT7JOt7LEK9v3wIGmNWcpmqf9O1C927cs8o="
8854+
"fingerprint": "iCTmGcYc3DTgEt3Y8PBPaiJNOI7O9tRRGI49RI6rAwk="
87828855
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Amazon.JSII.Runtime.Deputy;
2+
using Amazon.JSII.Tests.CalculatorNamespace.LibNamespace;
3+
using System.Collections.Generic;
4+
5+
namespace Amazon.JSII.Tests.CalculatorNamespace
6+
{
7+
/// <summary>Verifies proper type handling through dynamic overrides.</summary>
8+
/// <remarks>stability: Experimental</remarks>
9+
[JsiiClass(nativeType: typeof(DataRenderer), fullyQualifiedName: "jsii-calc.DataRenderer")]
10+
public class DataRenderer : DeputyBase
11+
{
12+
/// <remarks>stability: Experimental</remarks>
13+
public DataRenderer(): base(new DeputyProps(new object[]{}))
14+
{
15+
}
16+
17+
protected DataRenderer(ByRefValue reference): base(reference)
18+
{
19+
}
20+
21+
protected DataRenderer(DeputyProps props): base(props)
22+
{
23+
}
24+
25+
/// <remarks>stability: Experimental</remarks>
26+
[JsiiMethod(name: "render", returnsJson: "{\"type\":{\"primitive\":\"string\"}}", parametersJson: "[{\"name\":\"data\",\"type\":{\"fqn\":\"@scope/jsii-calc-lib.MyFirstStruct\"},\"optional\":true}]")]
27+
public virtual string Render(IMyFirstStruct data)
28+
{
29+
return InvokeInstanceMethod<string>(new object[]{data});
30+
}
31+
32+
/// <remarks>stability: Experimental</remarks>
33+
[JsiiMethod(name: "renderMap", returnsJson: "{\"type\":{\"primitive\":\"string\"}}", parametersJson: "[{\"name\":\"map\",\"type\":{\"collection\":{\"kind\":\"map\",\"elementtype\":{\"primitive\":\"any\"}}}}]")]
34+
public virtual string RenderMap(IDictionary<string, object> map)
35+
{
36+
return InvokeInstanceMethod<string>(new object[]{map});
37+
}
38+
}
39+
}

packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/$Module.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ protected Class<?> resolveClass(final String fqn) throws ClassNotFoundException
3838
case "jsii-calc.ConstructorPassesThisOut": return software.amazon.jsii.tests.calculator.ConstructorPassesThisOut.class;
3939
case "jsii-calc.Constructors": return software.amazon.jsii.tests.calculator.Constructors.class;
4040
case "jsii-calc.ConsumersOfThisCrazyTypeSystem": return software.amazon.jsii.tests.calculator.ConsumersOfThisCrazyTypeSystem.class;
41+
case "jsii-calc.DataRenderer": return software.amazon.jsii.tests.calculator.DataRenderer.class;
4142
case "jsii-calc.DefaultedConstructorArgument": return software.amazon.jsii.tests.calculator.DefaultedConstructorArgument.class;
4243
case "jsii-calc.DeprecatedClass": return software.amazon.jsii.tests.calculator.DeprecatedClass.class;
4344
case "jsii-calc.DeprecatedEnum": return software.amazon.jsii.tests.calculator.DeprecatedEnum.class;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package software.amazon.jsii.tests.calculator;
2+
3+
/**
4+
* Verifies proper type handling through dynamic overrides.
5+
*
6+
* EXPERIMENTAL
7+
*/
8+
@javax.annotation.Generated(value = "jsii-pacmak")
9+
@software.amazon.jsii.Stability(software.amazon.jsii.Stability.Level.Experimental)
10+
@software.amazon.jsii.Jsii(module = software.amazon.jsii.tests.calculator.$Module.class, fqn = "jsii-calc.DataRenderer")
11+
public class DataRenderer extends software.amazon.jsii.JsiiObject {
12+
protected DataRenderer(final software.amazon.jsii.JsiiObject.InitializationMode mode) {
13+
super(mode);
14+
}
15+
/**
16+
* EXPERIMENTAL
17+
*/
18+
@software.amazon.jsii.Stability(software.amazon.jsii.Stability.Level.Experimental)
19+
public DataRenderer() {
20+
super(software.amazon.jsii.JsiiObject.InitializationMode.Jsii);
21+
software.amazon.jsii.JsiiEngine.getInstance().createNewObject(this);
22+
}
23+
24+
/**
25+
* EXPERIMENTAL
26+
*/
27+
@software.amazon.jsii.Stability(software.amazon.jsii.Stability.Level.Experimental)
28+
public java.lang.String render(@javax.annotation.Nullable final software.amazon.jsii.tests.calculator.lib.MyFirstStruct data) {
29+
return this.jsiiCall("render", java.lang.String.class, new Object[] { data });
30+
}
31+
32+
/**
33+
* EXPERIMENTAL
34+
*/
35+
@software.amazon.jsii.Stability(software.amazon.jsii.Stability.Level.Experimental)
36+
public java.lang.String render() {
37+
return this.jsiiCall("render", java.lang.String.class);
38+
}
39+
40+
/**
41+
* EXPERIMENTAL
42+
*/
43+
@software.amazon.jsii.Stability(software.amazon.jsii.Stability.Level.Experimental)
44+
public java.lang.String renderMap(final java.util.Map<java.lang.String, java.lang.Object> map) {
45+
return this.jsiiCall("renderMap", java.lang.String.class, new Object[] { java.util.Objects.requireNonNull(map, "map is required") });
46+
}
47+
}

0 commit comments

Comments
 (0)