H4File registration fails due to a class initialization cycle

#481 · open · 2 comments

View on GitHub ↗

mattjala

HDFView does not register the HDF4 file format at startup, and consequently HDF4 files are not recognized and cannot be opened. With debug logging enabled the HDF4 format registration failure can be seen: ``` DEBUG FileFormat - FILE_TYPE_HDF4 instance failure: java.lang.NullPointerException: Cannot invoke "org.slf4j.Logger.trace(String, Object[])" because "hdf.object.h4.H4File.log" is null at hdf.object.h4.H4File.<init>(H4File.java:162) at hdf.object.h4.H4File.<init>(H4File.java:96) at hdf.object.FileFormat.<clinit>(FileFormat.java:202) at hdf.view.ViewProperties.load(ViewProperties.java:1502) at hdf.view.HDFView.<init>(HDFView.java:267) ``` The cause is that ViewProperties.load() walks the module.fileformat.* preferences and calls Class.forName on each value (ViewProperties.java:1502). For HDF4 that is hdf.object.h4.H4File, and it is the first touch of either class. Because H4File extends FileFormat (H4File.java:46), the JVM has to initialize FileFormat first, which runs FileFormat's static block. That block calls Class.forName("hdf.object.h4.H4File") and newInstance() (FileFormat.java:201-202). H4File's initialization is already in progress on this same thread, so the JVM returns immediately instead of waiting for it to finish. The result is that H4File's constructor runs while H4File's own static initialization is still parked at the superclass step. Its private static final Logger log (H4File.java:49) has not been assigned yet, so the log.trace call at H4File.java:162 throws an NPE. Only HDF4 is affected, since H5File's constructor never touches the log. The initialization cycle applies to any format class that gets loaded first, but H4File is the only one that dereferences its logger before initialization has completed. The cheapest fix is to remove the log.trace at H4File.java:162, or guard it. That would get HDF4 working, but the issue will crop back up the next time a log statement is added. A longer term fix is to stop FileFormat's static initializer from instantiating its own subclasses. Registering formats lazily, or moving registration to an explicit init call, would suffice.

Comments

jhendersonHDF

What context does this come up under? For example, the 3.4.1 release of HDFView can view HDF4 files fine. Is this a recent problem with source builds?

mattjala

Ah, the class initialization bug doesn't actually end up preventing HDF4 file opening. After the exception gets thrown from FileFormat's static initializer, ViewProperties.load() goes on to create a new instance of H4File and add the HDF4 format at (ViewProperties.java:1513-1515). By that point H4File is initialized, so H4 files able to be opened just fine. I think the associated fix should still go in eventually, but we can probably drop the priority. It also looks like whether the cycle happens at all depends on whether a properties file exists. The properties file is version-scoped (.hdfview + VERSION, ViewProperties.java:64), and HDFView writes the module.fileformat.* entries from registered format keys on exit (ViewProperties.java:1802) by walking the registered format keys. On the first launch of a new version that file is empty, so the module.fileformat loop never runs, and the FileFormat class is initialized later in the HDFView constructor without any mid-initialization problems. That said, the entire process of writing built-in file formats to an external properties file and reading them back doesn't seem to gain HDFView anything. The mechanism looks like it's supposed to be for third-party formats. We may want to skip persisting the default file formats there.