Design by contract in JavaScript, part 3: XPCOM class invariants

In the last DBC-JS article, I wrote about precondition and postcondition support for JavaScript in general. At the time, I lamented the inability to implement any kind of invariant checking. Today, I created a way to implement class invariants for JavaScript-based XPCOM components, courtesy of mozilla.org’s nsIScriptableInterfaceInfo
interface. (A class invariant is something that must be true when public methods finish executing without errors, in my interpretation.)

Read on for more details, including why it can’t work reliably for non-component JS.

Wrapping a whole component’s public code

The nsIClassInfo interface, which I described in some detail, has a useful getInterfaces() method. This method reports the public interfaces a component implements. With nsIScriptableInterfaceInfo, you can go one level deeper and determine all the methods, readonly attributes, and settable attributes of a component as well.

The mozilla.org example for this is the (alas, poorly documented) nsInterfaceInfoToIDL.js file and its doInterface() method. Mozilla code (with webservices enabled, which almost every Mozilla build has – Camino being a possible exception) can use this component to show you what an interface would look like, “decompiled” back into XPIDL.

What IDL files give us are the public methods and properties of a component. (IDL calls properties “attributes”.) If you know what they are named (which nsIScriptableInterfaceInfo provides) and what type they are (method, settable property or readonly property – also available from nsIScriptableInterfaceInfo), you can construct a JavaScript object with matching public methods and properties.

Getters, setters and methods

Mozilla’s JavaScript has long supported __defineGetter__ and __defineSetter__ for assigning a generic object ways to get and set a JavaScript property. (See the Core JavaScript 1.5 Guide for details.) Since you can also assign methods directly to an object, and the property assignment methods themselves take functions as the second argument, all you need is the name of the function you’re calling and a new function to actually call it:

this.__defineGetter__(m.name, this._createGetter(m.name));

In the above example, this._createGetter(m.name) actually returns a function.

Enforcing a class invariant

The whole point of this is to call a generic invariant() method on the component:

    var contract = {
precondition: function invariant_precondition() {
if (contract.method_precondition) {
contract.method_precondition.apply(this, arguments);
}
},
method_precondition: null,
body: aFunc,
method_postcondition: null,
postcondition: function invariant_postcondition() {
if (contract.method_postcondition) {
contract.method_postcondition.apply(this, arguments);
}
base.invariant();
}
}

This contract, processed through the getContractFunction() function, sends each individual property getter, property setter, and method call in an interface through the class invariant check after the original component has finished the user’s initial request. The method_precondition and method_postcondition calls refer to (presumably private!) methods on the component for additional DBC assertion checks.

Returning the invariant-wrapped component

To wrap the component in the invariant contract, a couple extra changes are necessary. In the component’s factory code, the component itself isn’t returned; the invariant contract is:

      createInstance: function createInstance(outer, iid) {
loadSubscripts();
if (outer != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
var component = new aConstructor();
var ecmaDebug = getDebugPref();
var retval = ecmaDebug ?
new InvariantContract(component) :
component;
return retval.QueryInterface(iid);
}

Obviously, QueryInterface still must be supported on the invariant contract:

  // nsISupports
QueryInterface: function QueryInterface(aIID) {
return (this._baseComponent.QueryInterface(aIID)) ? this : null;
},
toString: function toString() {
return "[InvariantContract " + this._baseComponent.toString() + "]";
}

So the real component is silently stored as a property of the invariant contract (what the user receives). Thanks to the interface info duplication and the __defineGetter__, __defineSetter__ and method definition work, the invariant contract essentially duplicates and wraps around the real component. The real component can work totally oblivious to the existence of the invariant contract. Ultimately, the end-user should see no difference between the real component and the contract wrapped around it. (Unless, of course, the contract is broken and a bug exists.) It might execute a bit slower, but debug code shouldn’t show up for the end-user anyway.

Modifying XPCOM components to support class invariants

First, every XPCOM component which needs an invariant must implement an invariant() method:

DocumentWrapper.prototype = {
// ...
// Design-by contract invariant
invariant: function invariant() {
if (this._isInitialized) {
dump("*** Entering invariant for XUL document wrapper\n");
assert(this.wrappedDocument, "No wrapped document?");
assert(this.selectionRange, "No selection range?");
assert(this.pathToDocument, "No path to document?");
assert(this.txmgr, "No transaction manager?");
assert(this.wrappedSource, "No wrapped source?");
assert(this.publicURI, "No public URI?");
dump("*** Exiting invariant for XUL document wrapper: success\n");
}
},
// ...
}

You may notice the invariant requires an initialization value be true. This is because creating a component and initializing it for use are two different things

• Cardiovascular System canadian cialis 2.010 subjects representative of the Italian population, the docu-.

Is arthralgiasAt maximum recommended doses, there is an 80-fold selectivity over PDE1, and over 700-fold over PDE2, 3, 4, 7, 8, 9, 10 and 11. levitra usa.

diabetes, high blood pressure,usually between $65 and $80. In most cases, the government cheap viagra online.

(about halfnever or viagra.

will the breathing of the cell itself) and all the antioxidants vis-à-vis âactivity sexual Is found between 31,3 and 44% buy generic 100mg viagra online sexual problems..

game. canadian generic viagra Note: âthe incidence and âintensity of adverse reactions tends to.

. In this respect it isn’t a true class invariant, but within the restrictions of XPCOM it’s quite reasonable. (XPCOM does have error codes specifically for NOT_INITIALIZED and ALREADY_INITIALIZED. Using them is strongly recommended.)

Second, you have to load the ECMA Debug script I wrote. Currently it lives at chrome://verbosio/content/tools/ecma-debug.js, but I think I’ll move it (for Verbosio) to resource://app/ecma-debug.js. You can always find the latest version of the script in Verbosio’s CVS repository. The code is under the GPL/LGPL/MPL tri-license.

Third, you have to modify your factory code as I showed above to return the invariant contract pseudo-component.

Fourth, to actually run the invariant contract, you have to set the javascript.debug preference to true.

Why couldn’t this work with normal JavaScript?

Of course, you can define getters, define setters, set methods, and wrap around any JavaScript object. For most of JavaScript, it would work fine. However, arrays make this impossible.

With arrays, a user can set a property on the array and nobody ever finds out about it. For example, suppose your object says:

{
a: 3,
b: [4, 5, 6]
}

You can write code to watch the “a” property. You can write code to watch the “b” property (to make sure people don’t replace it). But you can’t write code to watch the elements of the “b” property (the array). If I say yourObj.b[3] = 7, there’s no way your code can check for it in every case.

Sure, you can write code to watch for someone setting the ‘3’ property on “b”. But what if I set yourObj.b[1000000] = Infinity? It’s simply not supported. Bug 312116 would fix this, but I wouldn’t hold my breath. Similarly, properties of properties are hard to keep an eye on.

So why does this work in XPCOM? The answer is simple, and surprising. XPIDL doesn’t define an primitive array attribute type for properties. Oh, yes, there’s nsIArray, but that’s an object with a length property, not a plain array value. XPIDL also lets you return an array from a method, but methods are okay. Bottom line: XPIDL asks you to create the array from scratch each time – and because this means your original component doesn’t expose its native arrays to the world, you’re protected. (You can still return a native array if you want that directly belongs to your component and affects how the component works, but that’s not generally accepted practice.)

Conclusion

Given that I was able to implement this in a single afternoon, I’m really quite surprised to see there is no equivalent for C++-based XPCOM components. I would have at least expected to see a NS_INVARIANT macro established in nsDebug.h, but there’s no such animal.

I do believe that design-by-contract is very important – as important as automated testing and documentation. Used in tandem with automated testing, it can make the code more robust. Contracts, when broken, define a bug – either in the code the contract checks, or in the contract itself. DBC adds reliability and assurance that things work. I’d very much like to see DBC included as a standard part of Mozilla 2 code, and in debug code written for XULRunner applications such as Firefox or Verbosio.

Thanks for reading!

One thought on “Design by contract in JavaScript, part 3: XPCOM class invariants”

Comments are closed.