Skip to content

Commit 7e600f9

Browse files
author
Elad Ben-Israel
authored
fix(kernel + java): skip overrides of private methods/properties (#254)
jsii-kernel will ignore override requests for methods/properties that resolve on the object but are not defined in the public API of the type. java runtime will not request overrides for methods/properties that use "private" accessibility. Fixes #244
1 parent 86d02ac commit 7e600f9

File tree

12 files changed

+382
-21
lines changed

12 files changed

+382
-21
lines changed

Diff for: package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: packages/jsii-calc/lib/compliance.ts

+20-1
Original file line numberDiff line numberDiff line change
@@ -871,7 +871,6 @@ class ConcreteClass extends AbstractClass {
871871
}
872872
}
873873

874-
875874
export class AbstractClassReturner {
876875
public giveMeAbstract(): AbstractClass {
877876
return new ConcreteClass();
@@ -895,3 +894,23 @@ export interface MutableObjectLiteral {
895894
export class ClassWithMutableObjectLiteralProperty {
896895
public mutableObject: MutableObjectLiteral = { value: 'default' };
897896
}
897+
898+
export class DoNotOverridePrivates {
899+
private privateMethod(): string {
900+
return 'privateMethod';
901+
}
902+
903+
private privateProperty = 'privateProperty';
904+
905+
public privateMethodValue() {
906+
return this.privateMethod();
907+
}
908+
909+
public privatePropertyValue() {
910+
return this.privateProperty;
911+
}
912+
913+
public changePrivatePropertyValue(newValue: string) {
914+
this.privateProperty = newValue;
915+
}
916+
}

Diff for: packages/jsii-calc/test/assembly.jsii

+35-1
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,40 @@
11981198
}
11991199
]
12001200
},
1201+
"jsii-calc.DoNotOverridePrivates": {
1202+
"assembly": "jsii-calc",
1203+
"fqn": "jsii-calc.DoNotOverridePrivates",
1204+
"initializer": {
1205+
"initializer": true
1206+
},
1207+
"kind": "class",
1208+
"methods": [
1209+
{
1210+
"name": "changePrivatePropertyValue",
1211+
"parameters": [
1212+
{
1213+
"name": "newValue",
1214+
"type": {
1215+
"primitive": "string"
1216+
}
1217+
}
1218+
]
1219+
},
1220+
{
1221+
"name": "privateMethodValue",
1222+
"returns": {
1223+
"primitive": "string"
1224+
}
1225+
},
1226+
{
1227+
"name": "privatePropertyValue",
1228+
"returns": {
1229+
"primitive": "string"
1230+
}
1231+
}
1232+
],
1233+
"name": "DoNotOverridePrivates"
1234+
},
12011235
"jsii-calc.DoubleTrouble": {
12021236
"assembly": "jsii-calc",
12031237
"fqn": "jsii-calc.DoubleTrouble",
@@ -3263,5 +3297,5 @@
32633297
}
32643298
},
32653299
"version": "0.7.6",
3266-
"fingerprint": "BFT/z3s6IMAAkCjnq3nn+dZUxSqp7tx/E54uljg/XvQ="
3300+
"fingerprint": "IrPnQp841TiCOiG/Z2z18s0K8pxTwuMglW1UJ2t1zsM="
32673301
}

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

+81
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import software.amazon.jsii.tests.calculator.Calculator;
1212
import software.amazon.jsii.tests.calculator.CalculatorProps;
1313
import software.amazon.jsii.tests.calculator.DerivedStruct;
14+
import software.amazon.jsii.tests.calculator.DoNotOverridePrivates;
1415
import software.amazon.jsii.tests.calculator.DoubleTrouble;
1516
import software.amazon.jsii.tests.calculator.GiveMeStructs;
1617
import software.amazon.jsii.tests.calculator.IFriendlier;
@@ -837,6 +838,86 @@ public void returnAbstract() {
837838
assertEquals("hello-abstract-property", obj.getReturnAbstractFromProperty().getAbstractProperty());
838839
}
839840

841+
@Test
842+
public void doNotOverridePrivates_method_public() {
843+
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
844+
public String privateMethod() {
845+
return "privateMethod-Override";
846+
}
847+
};
848+
849+
assertEquals("privateMethod", obj.privateMethodValue());
850+
}
851+
852+
@Test
853+
public void doNotOverridePrivates_method_private() {
854+
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
855+
private String privateMethod() {
856+
return "privateMethod-Override";
857+
}
858+
};
859+
860+
assertEquals("privateMethod", obj.privateMethodValue());
861+
}
862+
863+
@Test
864+
public void doNotOverridePrivates_property_by_name_private() {
865+
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
866+
private String privateProperty() {
867+
return "privateProperty-Override";
868+
}
869+
};
870+
871+
assertEquals("privateProperty", obj.privatePropertyValue());
872+
}
873+
874+
@Test
875+
public void doNotOverridePrivates_property_by_name_public() {
876+
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
877+
public String privateProperty() {
878+
return "privateProperty-Override";
879+
}
880+
};
881+
882+
assertEquals("privateProperty", obj.privatePropertyValue());
883+
}
884+
885+
@Test
886+
public void doNotOverridePrivates_property_getter_public() {
887+
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
888+
public String getPrivateProperty() {
889+
return "privateProperty-Override";
890+
}
891+
public void setPrivateProperty(String value) {
892+
throw new RuntimeException("Boom");
893+
}
894+
};
895+
896+
assertEquals("privateProperty", obj.privatePropertyValue());
897+
898+
// verify the setter override is not invoked.
899+
obj.changePrivatePropertyValue("MyNewValue");
900+
assertEquals("MyNewValue", obj.privatePropertyValue());
901+
}
902+
903+
@Test
904+
public void doNotOverridePrivates_property_getter_private() {
905+
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
906+
private String getPrivateProperty() {
907+
return "privateProperty-Override";
908+
}
909+
public void setPrivateProperty(String value) {
910+
throw new RuntimeException("Boom");
911+
}
912+
};
913+
914+
assertEquals("privateProperty", obj.privatePropertyValue());
915+
916+
// verify the setter override is not invoked.
917+
obj.changePrivatePropertyValue("MyNewValue");
918+
assertEquals("MyNewValue", obj.privatePropertyValue());
919+
}
920+
840921
static class MulTen extends Multiply {
841922
public MulTen(final int value) {
842923
super(new Number(value), new Number(10));

Diff for: packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiEngine.java

+4
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,10 @@ private static Collection<JsiiOverride> discoverOverrides(final Class<?> classTo
468468

469469
// add all the methods in the current class
470470
for (Method method : klass.getDeclaredMethods()) {
471+
if (Modifier.isPrivate(method.getModifiers())) {
472+
continue;
473+
}
474+
471475
String methodName = method.getName();
472476

473477
// check if this is a property ("getXXX" or "setXXX", oh java!)

Diff for: packages/jsii-kernel/lib/kernel.ts

+48-15
Original file line numberDiff line numberDiff line change
@@ -474,12 +474,7 @@ export class Kernel {
474474
const objref = this._createObjref(obj, fqn);
475475

476476
// overrides: for each one of the override method names, installs a
477-
// method on the newly created object which represents the remote
478-
// override. Overrides are always async. When an override is called, it
479-
// returns a promise which adds a callback to the pending callbacks
480-
// list. This list is then retrieved by the client (using
481-
// pendingCallbacks() and promises are fulfilled using
482-
// completeCallback(), which in turn, fulfills the internal promise.
477+
// method on the newly created object which represents the remote "reverse proxy".
483478

484479
if (overrides) {
485480
this._debug('overrides', overrides);
@@ -495,19 +490,34 @@ export class Kernel {
495490

496491
methods.add(override.method);
497492

498-
// check that the method being overridden actually exists on the
499-
// class and is an async method.
493+
// check that the method being overridden actually exists
500494
let methodInfo;
501495
if (fqn !== EMPTY_OBJECT_FQN) {
502-
methodInfo = this._tryTypeInfoForMethod(fqn, override.method); // throws if method cannot be found
496+
// error if we can find a property with this name
497+
if (this._tryTypeInfoForProperty(fqn, override.method)) {
498+
throw new Error(`Trying to override property '${override.method}' as a method`);
499+
}
500+
501+
methodInfo = this._tryTypeInfoForMethod(fqn, override.method);
503502
}
504503

505504
this._applyMethodOverride(obj, objref, override, methodInfo);
506505
} else if (override.property) {
507506
if (override.method) { throw new Error(overrideTypeErrorMessage); }
508507
if (properties.has(override.property)) { throw Error(`Duplicate override for property '${override.property}'`); }
509508
properties.add(override.property);
510-
this._applyPropertyOverride(obj, objref, override);
509+
510+
let propInfo: spec.Property | undefined;
511+
if (fqn !== EMPTY_OBJECT_FQN) {
512+
// error if we can find a method with this name
513+
if (this._tryTypeInfoForMethod(fqn, override.property)) {
514+
throw new Error(`Trying to override method '${override.property}' as a property`);
515+
}
516+
517+
propInfo = this._tryTypeInfoForProperty(fqn, override.property);
518+
}
519+
520+
this._applyPropertyOverride(obj, objref, override, propInfo);
511521
} else {
512522
throw new Error(overrideTypeErrorMessage);
513523
}
@@ -521,10 +531,16 @@ export class Kernel {
521531
return `$jsii$super$${name}$`;
522532
}
523533

524-
private _applyPropertyOverride(obj: any, objref: api.ObjRef, override: api.Override) {
534+
private _applyPropertyOverride(obj: any, objref: api.ObjRef, override: api.Override, propInfo?: spec.Property) {
525535
const self = this;
526536
const propertyName = override.property!;
527537

538+
// if this is a private property (i.e. doesn't have `propInfo` the object has a key)
539+
if (!propInfo && propertyName in obj) {
540+
this._debug(`Skipping override of private property ${propertyName}`);
541+
return;
542+
}
543+
528544
this._debug('apply override', propertyName);
529545

530546
// save the old property under $jsii$super$<prop>$ so that property overrides
@@ -570,6 +586,13 @@ export class Kernel {
570586
const self = this;
571587
const methodName = override.method!;
572588

589+
// If this is a private method (doesn't have methodInfo, key resolves on the object), we
590+
// are going to skip the override.
591+
if (!methodInfo && obj[methodName]) {
592+
this._debug(`Skipping override of private method ${methodName}`);
593+
return;
594+
}
595+
573596
// note that we are applying the override even if the method doesn't exist
574597
// on the type spec in order to allow native code to override methods from
575598
// interfaces.
@@ -802,11 +825,10 @@ export class Kernel {
802825
return undefined;
803826
}
804827

805-
private _typeInfoForProperty(fqn: string, property: string): spec.Property {
828+
private _tryTypeInfoForProperty(fqn: string, property: string): spec.Property | undefined {
806829
if (!fqn) {
807830
throw new Error('missing "fqn"');
808831
}
809-
810832
const typeInfo = this._typeInfoForFqn(fqn);
811833

812834
let properties;
@@ -832,10 +854,21 @@ export class Kernel {
832854

833855
// recurse to parent type (if exists)
834856
for (const baseFqn of bases) {
835-
return this._typeInfoForProperty(baseFqn, property);
857+
const ret = this._tryTypeInfoForProperty(baseFqn, property);
858+
if (ret) {
859+
return ret;
860+
}
836861
}
837862

838-
throw new Error(`Type ${typeInfo.fqn} doesn't have a property '${property}'`);
863+
return undefined;
864+
}
865+
866+
private _typeInfoForProperty(fqn: string, property: string): spec.Property {
867+
const typeInfo = this._tryTypeInfoForProperty(fqn, property);
868+
if (!typeInfo) {
869+
throw new Error(`Type ${fqn} doesn't have a property '${property}'`);
870+
}
871+
return typeInfo;
839872
}
840873

841874
private _toSandbox(v: any): any {

Diff for: packages/jsii-kernel/test/test.kernel.ts

+48
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const calcVersion = require('jsii-calc/package.json').version.replace(/\+.+$/, '
1818
// tslint:disable:no-console
1919
// tslint:disable:max-line-length
2020

21+
process.setMaxListeners(9999); // since every kernel instance adds an `on('exit')` handler.
22+
2123
process.on('unhandledRejection', e => {
2224
console.error(e.stack);
2325
process.exit(1);
@@ -890,6 +892,52 @@ defineTest('object literals are returned by reference', async (test, sandbox) =>
890892
sandbox.del({ objref: property });
891893
});
892894

895+
defineTest('overrides: method instead of property with the same name', async (test, sandbox) => {
896+
test.throws(() => {
897+
sandbox.create({ fqn: 'jsii-calc.SyncVirtualMethods', overrides: [
898+
{ method: 'theProperty' }
899+
]});
900+
}, /Trying to override property/);
901+
});
902+
903+
defineTest('overrides: property instead of method with the same name', async (test, sandbox) => {
904+
test.throws(() => {
905+
sandbox.create({ fqn: 'jsii-calc.SyncVirtualMethods', overrides: [
906+
{ property: 'virtualMethod' }
907+
]});
908+
}, /Trying to override method/);
909+
});
910+
911+
defineTest('overrides: skip overrides of private methods', async (test, sandbox) => {
912+
const objref = sandbox.create({ fqn: 'jsii-calc.DoNotOverridePrivates', overrides: [
913+
{ method: 'privateMethod' }
914+
]});
915+
916+
sandbox.callbackHandler = makeSyncCallbackHandler(_ => {
917+
test.ok(false, 'override callback should not be called');
918+
return 'privateMethodBoom!';
919+
});
920+
921+
const result = sandbox.invoke({ objref, method: 'privateMethodValue' });
922+
test.deepEqual(result.result, 'privateMethod');
923+
});
924+
925+
defineTest('overrides: skip overrides of private properties', async (test, sandbox) => {
926+
const objref = sandbox.create({ fqn: 'jsii-calc.DoNotOverridePrivates', overrides: [
927+
{ property: 'privateProperty' }
928+
]});
929+
930+
sandbox.callbackHandler = makeSyncCallbackHandler(_ => {
931+
test.ok(false, 'override callback should not be called');
932+
return 'privatePropertyBoom!';
933+
});
934+
935+
const result = sandbox.invoke({ objref, method: 'privatePropertyValue' });
936+
test.deepEqual(result.result, 'privateProperty');
937+
});
938+
939+
// =================================================================================================
940+
893941
const testNames: { [name: string]: boolean } = { };
894942

895943
async function createCalculatorSandbox(name: string) {

0 commit comments

Comments
 (0)