Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(kernel + java): skip overrides of private methods/properties #254

Merged
merged 6 commits into from
Oct 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion packages/jsii-calc/lib/compliance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,6 @@ class ConcreteClass extends AbstractClass {
}
}


export class AbstractClassReturner {
public giveMeAbstract(): AbstractClass {
return new ConcreteClass();
Expand All @@ -895,3 +894,23 @@ export interface MutableObjectLiteral {
export class ClassWithMutableObjectLiteralProperty {
public mutableObject: MutableObjectLiteral = { value: 'default' };
}

export class DoNotOverridePrivates {
private privateMethod(): string {
return 'privateMethod';
}

private privateProperty = 'privateProperty';

public privateMethodValue() {
return this.privateMethod();
}

public privatePropertyValue() {
return this.privateProperty;
}

public changePrivatePropertyValue(newValue: string) {
this.privateProperty = newValue;
}
}
36 changes: 35 additions & 1 deletion packages/jsii-calc/test/assembly.jsii
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,40 @@
}
]
},
"jsii-calc.DoNotOverridePrivates": {
"assembly": "jsii-calc",
"fqn": "jsii-calc.DoNotOverridePrivates",
"initializer": {
"initializer": true
},
"kind": "class",
"methods": [
{
"name": "changePrivatePropertyValue",
"parameters": [
{
"name": "newValue",
"type": {
"primitive": "string"
}
}
]
},
{
"name": "privateMethodValue",
"returns": {
"primitive": "string"
}
},
{
"name": "privatePropertyValue",
"returns": {
"primitive": "string"
}
}
],
"name": "DoNotOverridePrivates"
},
"jsii-calc.DoubleTrouble": {
"assembly": "jsii-calc",
"fqn": "jsii-calc.DoubleTrouble",
Expand Down Expand Up @@ -3263,5 +3297,5 @@
}
},
"version": "0.7.6",
"fingerprint": "BFT/z3s6IMAAkCjnq3nn+dZUxSqp7tx/E54uljg/XvQ="
"fingerprint": "IrPnQp841TiCOiG/Z2z18s0K8pxTwuMglW1UJ2t1zsM="
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import software.amazon.jsii.tests.calculator.Calculator;
import software.amazon.jsii.tests.calculator.CalculatorProps;
import software.amazon.jsii.tests.calculator.DerivedStruct;
import software.amazon.jsii.tests.calculator.DoNotOverridePrivates;
import software.amazon.jsii.tests.calculator.DoubleTrouble;
import software.amazon.jsii.tests.calculator.GiveMeStructs;
import software.amazon.jsii.tests.calculator.IFriendlier;
Expand Down Expand Up @@ -837,6 +838,86 @@ public void returnAbstract() {
assertEquals("hello-abstract-property", obj.getReturnAbstractFromProperty().getAbstractProperty());
}

@Test
public void doNotOverridePrivates_method_public() {
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
public String privateMethod() {
return "privateMethod-Override";
}
};

assertEquals("privateMethod", obj.privateMethodValue());
}

@Test
public void doNotOverridePrivates_method_private() {
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
private String privateMethod() {
return "privateMethod-Override";
}
};

assertEquals("privateMethod", obj.privateMethodValue());
}

@Test
public void doNotOverridePrivates_property_by_name_private() {
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
private String privateProperty() {
return "privateProperty-Override";
}
};

assertEquals("privateProperty", obj.privatePropertyValue());
}

@Test
public void doNotOverridePrivates_property_by_name_public() {
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
public String privateProperty() {
return "privateProperty-Override";
}
};

assertEquals("privateProperty", obj.privatePropertyValue());
}

@Test
public void doNotOverridePrivates_property_getter_public() {
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
public String getPrivateProperty() {
return "privateProperty-Override";
}
public void setPrivateProperty(String value) {
throw new RuntimeException("Boom");
}
};

assertEquals("privateProperty", obj.privatePropertyValue());

// verify the setter override is not invoked.
obj.changePrivatePropertyValue("MyNewValue");
assertEquals("MyNewValue", obj.privatePropertyValue());
}

@Test
public void doNotOverridePrivates_property_getter_private() {
DoNotOverridePrivates obj = new DoNotOverridePrivates() {
private String getPrivateProperty() {
eladb marked this conversation as resolved.
Show resolved Hide resolved
return "privateProperty-Override";
}
public void setPrivateProperty(String value) {
throw new RuntimeException("Boom");
}
};

assertEquals("privateProperty", obj.privatePropertyValue());

// verify the setter override is not invoked.
obj.changePrivatePropertyValue("MyNewValue");
assertEquals("MyNewValue", obj.privatePropertyValue());
}

static class MulTen extends Multiply {
public MulTen(final int value) {
super(new Number(value), new Number(10));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,10 @@ private static Collection<JsiiOverride> discoverOverrides(final Class<?> classTo

// add all the methods in the current class
for (Method method : klass.getDeclaredMethods()) {
if (Modifier.isPrivate(method.getModifiers())) {
continue;
}

String methodName = method.getName();

// check if this is a property ("getXXX" or "setXXX", oh java!)
Expand Down
63 changes: 48 additions & 15 deletions packages/jsii-kernel/lib/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,12 +474,7 @@ export class Kernel {
const objref = this._createObjref(obj, fqn);

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

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

methods.add(override.method);

// check that the method being overridden actually exists on the
// class and is an async method.
// check that the method being overridden actually exists
let methodInfo;
if (fqn !== EMPTY_OBJECT_FQN) {
methodInfo = this._tryTypeInfoForMethod(fqn, override.method); // throws if method cannot be found
// error if we can find a property with this name
if (this._tryTypeInfoForProperty(fqn, override.method)) {
throw new Error(`Trying to override property '${override.method}' as a method`);
}

methodInfo = this._tryTypeInfoForMethod(fqn, override.method);
}

this._applyMethodOverride(obj, objref, override, methodInfo);
} else if (override.property) {
if (override.method) { throw new Error(overrideTypeErrorMessage); }
if (properties.has(override.property)) { throw Error(`Duplicate override for property '${override.property}'`); }
properties.add(override.property);
this._applyPropertyOverride(obj, objref, override);

let propInfo: spec.Property | undefined;
if (fqn !== EMPTY_OBJECT_FQN) {
// error if we can find a method with this name
if (this._tryTypeInfoForMethod(fqn, override.property)) {
throw new Error(`Trying to override method '${override.property}' as a property`);
}

propInfo = this._tryTypeInfoForProperty(fqn, override.property);
}

this._applyPropertyOverride(obj, objref, override, propInfo);
} else {
throw new Error(overrideTypeErrorMessage);
}
Expand All @@ -521,10 +531,16 @@ export class Kernel {
return `$jsii$super$${name}$`;
}

private _applyPropertyOverride(obj: any, objref: api.ObjRef, override: api.Override) {
private _applyPropertyOverride(obj: any, objref: api.ObjRef, override: api.Override, propInfo?: spec.Property) {
const self = this;
const propertyName = override.property!;

// if this is a private property (i.e. doesn't have `propInfo` the object has a key)
if (!propInfo && propertyName in obj) {
this._debug(`Skipping override of private property ${propertyName}`);
eladb marked this conversation as resolved.
Show resolved Hide resolved
return;
}

this._debug('apply override', propertyName);

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

// If this is a private method (doesn't have methodInfo, key resolves on the object), we
// are going to skip the override.
if (!methodInfo && obj[methodName]) {
this._debug(`Skipping override of private method ${methodName}`);
eladb marked this conversation as resolved.
Show resolved Hide resolved
return;
}

// note that we are applying the override even if the method doesn't exist
// on the type spec in order to allow native code to override methods from
// interfaces.
Expand Down Expand Up @@ -802,11 +825,10 @@ export class Kernel {
return undefined;
}

private _typeInfoForProperty(fqn: string, property: string): spec.Property {
private _tryTypeInfoForProperty(fqn: string, property: string): spec.Property | undefined {
if (!fqn) {
throw new Error('missing "fqn"');
}

const typeInfo = this._typeInfoForFqn(fqn);

let properties;
Expand All @@ -832,10 +854,21 @@ export class Kernel {

// recurse to parent type (if exists)
for (const baseFqn of bases) {
return this._typeInfoForProperty(baseFqn, property);
const ret = this._tryTypeInfoForProperty(baseFqn, property);
if (ret) {
return ret;
}
}

throw new Error(`Type ${typeInfo.fqn} doesn't have a property '${property}'`);
return undefined;
}

private _typeInfoForProperty(fqn: string, property: string): spec.Property {
const typeInfo = this._tryTypeInfoForProperty(fqn, property);
if (!typeInfo) {
throw new Error(`Type ${fqn} doesn't have a property '${property}'`);
}
return typeInfo;
}

private _toSandbox(v: any): any {
Expand Down
48 changes: 48 additions & 0 deletions packages/jsii-kernel/test/test.kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const calcVersion = require('jsii-calc/package.json').version.replace(/\+.+$/, '
// tslint:disable:no-console
// tslint:disable:max-line-length

process.setMaxListeners(9999); // since every kernel instance adds an `on('exit')` handler.

process.on('unhandledRejection', e => {
console.error(e.stack);
process.exit(1);
Expand Down Expand Up @@ -890,6 +892,52 @@ defineTest('object literals are returned by reference', async (test, sandbox) =>
sandbox.del({ objref: property });
});

defineTest('overrides: method instead of property with the same name', async (test, sandbox) => {
test.throws(() => {
sandbox.create({ fqn: 'jsii-calc.SyncVirtualMethods', overrides: [
{ method: 'theProperty' }
]});
}, /Trying to override property/);
});

defineTest('overrides: property instead of method with the same name', async (test, sandbox) => {
test.throws(() => {
sandbox.create({ fqn: 'jsii-calc.SyncVirtualMethods', overrides: [
{ property: 'virtualMethod' }
]});
}, /Trying to override method/);
});

defineTest('overrides: skip overrides of private methods', async (test, sandbox) => {
const objref = sandbox.create({ fqn: 'jsii-calc.DoNotOverridePrivates', overrides: [
{ method: 'privateMethod' }
]});

sandbox.callbackHandler = makeSyncCallbackHandler(_ => {
test.ok(false, 'override callback should not be called');
return 'privateMethodBoom!';
});

const result = sandbox.invoke({ objref, method: 'privateMethodValue' });
test.deepEqual(result.result, 'privateMethod');
});

defineTest('overrides: skip overrides of private properties', async (test, sandbox) => {
const objref = sandbox.create({ fqn: 'jsii-calc.DoNotOverridePrivates', overrides: [
{ property: 'privateProperty' }
]});

sandbox.callbackHandler = makeSyncCallbackHandler(_ => {
test.ok(false, 'override callback should not be called');
return 'privatePropertyBoom!';
});

const result = sandbox.invoke({ objref, method: 'privatePropertyValue' });
test.deepEqual(result.result, 'privateProperty');
});

// =================================================================================================

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

async function createCalculatorSandbox(name: string) {
Expand Down
Loading