Bug: Two (or more) class objects may be loaded for a specific class

10 views
Skip to first unread message

Udo

unread,
Jul 26, 2016, 1:44:18 PM7/26/16
to Java2Script
We found a bug that made our Java2Script-based application fail randomly.

Introduction

Early in the startup phase the file “package.js” is loaded. It maps some classes to package files that contain their implementation. Here an excerpt:

    var base = ClazzLoader.getClasspathFor("java.*");
    ClazzLoader.jarClasspath(base + "core.z.js", ["java.lang.Void", "$.reflect.AccessibleObject”, …]);

Here the ClassLoader is told to load the file “core.z.js” when any of the listed types (“java.lang.Void”…) is needed. “core.z.js” implements these types.

However this list is not complete. E.g. the list does not contain “java.util.RandomAccess,” also defined in core.z.js:

$_I(java.util, "RandomAccess");

The Problem

With both having one definition of RandomAccess in “core.z.js” and one definition in the standalone file “RandomAccess.js” we can run into a race condition. 

Here an example using the class HashSet that implements RandomAccess.

(In the following “|======|” represents the time a file is loaded, e.g. as displayed in the "Network" tab of Google Chrome's Developer Tools.)

Case A

First the “good” case: 

core.z.js        |======|  
HashSet.js            |====|
(RandomAccess.js             (1))         

(1) RandomAccess.js is not loaded as class RandomAccess is already defined (through core.z.js). The definition loaded from “core.z.js” is used for HashSet.

Case B

Now the “bad” case: 

core.z.js        |========================|(3)
HashSet.js            |====|
RandomAccess.js            |====|(2)

(2) RandomAccess.js is loaded as class RandomAccess is not yet defined (“core.z.js” is not yet completely loaded ). The definition of class RandomAccess, loaded from “RandomAccess.js”, is used for HashSet.
(3) core.z.js is completely loaded and defines class RandomAccess again. This overwrites the definition done in (2).


Consequences of Case B

When we are running into case B we have two definitions of RandomAccess in memory:
  • HashSet still references the “old” one, loaded from “RandomAccess.js" but 
  • the system references the one loaded from “core.z.js”. 

This leads to problems when the identity of types is used to compare types. 

E.g. Clazz.getInheritedLevel starts with this code: 

Clazz.getInheritedLevel = function (clazzTarget, clazzBase) {
if (clazzTarget === clazzBase) {
return 0;
}

So if I now want to check if HashSet inherits/implements RandomAccess I will get false because I compare the “old” RandomAccess  from HashSet with the “current” RandomAccess, which are not identical. (BTW: changing the “===“ to “==“ will not solve the problem.)

Reproducibility

Originally the bug was only observed “randomly”, by certain users. As it turned out the reason had to do with the quality of the network connection. This bug can only be observed reliably when “throttling” the network traffic [1]. This will make sure loading the “core.z.js” file takes longer and we get case “B”. When running in a “Regular 3G” setting (with 100ms latency and 250-750kb/s) I get a 100% failure rate. 

Solution (Partial)

We solved the problem by checking if a type is already defined before setting it in Clazz.decorateFunction:


} else if (prefix.__PKG_NAME__ != null) {
// e.g. Clazz.declareInterface (org.eclipse.ui, "ICorePlugin", 
// org.eclipse.ui.IPlugin);
qName = prefix.__PKG_NAME__ + "." + name;
if (prefix[name] && !mayBeRedefined(qName)) {
return;
}
prefix[name] = clazzFun;
if (prefix === java.lang) {
window[name] = clazzFun;
}
} else {
// e.g. Clazz.declareInterface (org.eclipse.ui.Plugin, "ICorePlugin", 
// org.eclipse.ui.IPlugin);
qName = prefix.__CLASS_NAME__ + "." + name;
if (prefix[name] && !mayBeRedefined(qName)) {
return;
}
prefix[name] = clazzFun;
}



Notice the extra check for mayBeRedefined(qName). We need to add this check because it is OK to redefine "nested interfaces" of super classes. For now mayBeRedefined(qName) looks like this:

/* 
Some types may be redefined. 
Mainly these are nested types of interfaces. When implementing the interface
nested types are initialized with the types from the interface.
See Clazz.implementsProperties for details.
*/
function mayBeRedefined(qName) {
return qName == "java.util.Hashtable.Entry" ||
qName == "java.util.HashMap.Entry";
}



This part of the solution does not look nice, but is sufficient for our application. To provide a more general solution a deeper knowledge of the inner workings of Java2Script seem to be necessary.


Udo





[1]: The “Network” tab of Chrome’s Developer Tools provide an easy way to simulate slow network connections.

Zhou Renjian

unread,
Nov 3, 2016, 11:50:58 AM11/3/16
to Java2Script
OK. Never too late to get it fixed nicely.

This is a bug of inconsistence between packing *.z.js and package.js.

/net.sf.j2s.java.core/src/java/package.js should be modified as

coreZ = base + "core.z.js";
ClazzLoader.jarClasspath (coreZ, [
"java.lang.Void",
"$.reflect.AccessibleObject",
"$.AnnotatedElement",
"$.GenericDeclaration",
"$.InvocationHandler",
"$.Member",
"$.Modifier",
"$.Constructor",
"$.Field",
"$.Method",
"$.Array",
"java.util.Date",
"$.EventObject",
"$.EventListener",
"$.EventListenerProxy",
"$.Iterator",
"$.ListIterator",
"$.Enumeration",
"$.Collection",
"$.Set",
"$.Map",
"$.List",
"$.Queue",
"$.RandomAccess",
"java.net.URLEncoder",
"$.URLDecoder",
"net.sf.j2s.ajax.HttpRequest",
"$.ARunnable",
"$.AClass",
"$.ASWTClass",

The above orange lines are missing.

Adding RandomAccess class declaration with core.z.js, then any dependency of RandomAccess will try to load core.z.js instead. No standalone RandomAccess.js will be loaded.

Regards,
Zhou Renjian
package.js
Reply all
Reply to author
Forward
0 new messages