30

The class BasicLabelUI in javax/swing/plaf/basic is affected by a confirmed bug. In my application I need functionality provided by the fixed version (filed for v9). Due to both legal and technical reasons, I'm still bound to the affected JDK version.

My approach was to create a package javax/swing/plaf/basic inside my project, containing the fixed version.

How can I force my project to favor my included version of the class over the defective class in the installed JDK?

This has to be somewhat portable as the fixed class also has to be working on customer side and the defective class in the JDK installation has to be disregarded. Therefore, I dont want to modify the JDK, but rather bypass this particular class.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
bogus
  • 867
  • 6
  • 14
  • 24
  • Strangely, I am unable to reproduce this bug in Java 1.7.0_75 or 1.8.0_65, in Windows 7, using the code from the bug submission. I tried modifying it to use the default look-and-feel; I tried adding use of EventQueue.invokeLater to the main method. (I was hoping to experiment with an InputMap-based workaround.) – VGR Nov 10 '15 at 15:32
  • I can reproduce the erroneous behaviour using the code from http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7172652. I'm using 1.8.0_45 – bogus Nov 10 '15 at 15:40

2 Answers2

26

As mentioned by the other answers, you could in theory of course unzip your JVM's rt.jar file and replace the file with a compatible bugfixed version.

Any classes of the Java Class library such as those of Swing are loaded by the bootstrap class loader which looks up its classes from this rt.jar. You can generally not prepend classes to this classpath without adding them to this file. There is a (non-standard) VM option

-Xbootclasspath/jarWithPatchedClass.jar:path

where you would prepend a jar file that includes the patched version, but this does not necessarily work on any Java virtual machine. Also, it is illegal to deploy an application that changes this hehavior! As it is stated in the official documentation:

Do not deploy applications that use this option to override a class in rt.jar because this violates the Java Runtime Environment binary code license.

If you however appended a class to the bootstrap class loader (what is possible without using non-standard APIs by using the instrumentation API), the runtime would still load the original class as the bootstrap class loader in this case searches the rt.jar first. It is therefore impossible to "shadow" the broken class without modifying this file.

Finally, it is always illegal to distribute a VM with a patched file, i.e. putting it into a production system for a customer. The license agreement states clearly that you need to

[...] distribute the [Java runtime] complete and unmodified and only bundled as part of your applets and applications

Changing the VM that you distribute is therefore not recommended as you might face legal consequences when this is ever uncovered.

Of course, you can in theory build your own version of the OpenJDK but you could not call the binary Java anymore when you distribute it and I assume that your customer would not allow for this by what you suggest in your answer. By experience, many secure environments compute hashes of binaries before execution what would prohibit both approaches of tweaking the executing VM.

The easiest solution for you would probably be the creation of a Java agent that you you add to your VM process on startup. In the end, this is very similar to adding a library as a class path dependency:

java -javaagent:bugFixAgent.jar -jar myApp.jar

A Java agent is capable of replacing a class's binary representation when the application is started and can therefore change the implementation of the buggy method.

In your case, an agent would look something like the following where you need to include the patched class file as a ressource:

public static class BugFixAgent {
  public static void premain(String args, Instrumentation inst) {
    inst.addClassFileTransformer(new ClassFileTransformer() {
      @Override
      public byte[] transform(ClassLoader loader, 
                              String className, 
                              Class<?> classBeingRedefined, 
                              ProtectionDomain protectionDomain, 
                              byte[] classfileBuffer) {
        if (className.equals("javax/swing/plaf/basic/BasicLabelUI")) {
          return patchedClassFile; // as found in the repository
          // Consider removing the transformer for future class loading
        } else {
          return null; // skips instrumentation for other classes
        }
      }
    });
  }
}

The javadoc java.lang.instrumentation package offers a detail description of how to build and implement a Java agent. Using this approach, you can use the fixed version of the class in question without breaking the license agreement.

From experience, Java agents are a great way for fixing temporary bugs in third party libraries and in the Java Class Library without needing to deploy changes in your code or even being required to deploy a new version for a customer. As a matter of fact, this is a typical use case for using a Java agent.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • If you are going to modify the JVM behavior, there is a simpler way. But I think this is breaking the (I guess) customer's rules. – Stephen C Nov 10 '15 at 14:07
  • I do not think that a customer has a problem when instrumenting the classes of a process that runs a Swing application (i.e. most likely a dedicated VM). In the end, most customers want a bug-free version. What you suggest is however a legal breach of the Java license agreement. – Rafael Winterhalter Nov 10 '15 at 14:11
  • You didn't read my Answer fully. See 1st paragraph after the line. – Stephen C Nov 10 '15 at 14:27
  • 2
    I suggested THREE things. And recommend one of them ... which is definitely not not illegal. Besides, my suggestion #1 is only a violation of the Oracle binary license if the Oracle binary license applies. It does not if you build from the OpenJDK sources (GPLv2) and follow the GPL rules. Read these ... http://openjdk.java.net/legal/ – Stephen C Nov 10 '15 at 14:35
  • The OP stated that migrating VM version is not an option what excludes your suggestions of either building your own VM or tweaking an existing VM (disregarding the fact that you are not legally allowed to alter binaries). If you meant the `-X` option that I also suggested, you should be more specific with your answer. Nothing of what the OP stated suggests that using a Java agent would be inapplicable for the use case. Finally, your first sentence says that what the OP wants is not possible what is neither true and contradicted by your own answer, thus my disagreement. – Rafael Winterhalter Nov 10 '15 at 14:47
  • @RafaelWinterhalter So does this BugFixAgent overrule the ill-conditioned class in the JDK? – bogus Nov 10 '15 at 14:54
  • @bogus Yes, both approaches will work. The agent works on any standard VM, the "prepend to class path" solution only works on current versions of HotSpot. – Rafael Winterhalter Nov 10 '15 at 14:57
  • The prepend to classpath approach ALSO works on older versions of Hotspot, and will probably continue to work in future versions .... unless you have evidence that they are going to withdraw this feature. – Stephen C Nov 11 '15 at 21:44
  • Also, -Xbootstrapclasspath is available on (at least) the IBM and Oracle JRockit java command. It is not exclusive to Hotspot as you imply. – Stephen C Nov 11 '15 at 22:10
  • I imply that X-prefixed options are not supported. sun.misc.Unsafe exists on all platforms, yet it goes away. – Rafael Winterhalter Nov 11 '15 at 23:13
2

How can I force my project to favor my included version of the class over the defective class in the installed JDK?

Simple answer - you can't. At least, not while strictly obeying the constraint that you should use the affected Java version.

Assuming that you can identify an appropriate version in the OpenJDK source repos, it would be possible to build your own flavor of the Java libraries with a bug patched. However, that won't be real Java. Certainly, it won't qualify as "the affected Java version" that you are constrained to use. (And besides, you are committing yourself to an endless cycle of reapplying your patch to each new patch release of the current version of Java ...)

It is also possible in theory to put a modified version of some Java standard library class into a JAR and prepend it to the JVM's bootstrap classpath using the -Xbootclasspath command line option. But that is tantamount to changing "the affected Java version" too.

Doing it by using a Java agent to use a patched version of the class is breaking the rules too. And it is more complicated. (If you are going to break your rules, do it the easy way ...)


If you and your customers do decide that tweaking the JVM is an acceptable solution, then doing it via the bootstrap classpath is probably the simplest and cleanest approach. And it is DEFINITELY legal1.

However, I'd recommend that you find a workaround for the bug until a version of Java 9 with your fix is released.


1 - Actually, even the build-from-modified-source approach is legal, because the Oracle Binary license does not apply to that. The Binary license is about distributing a modified version of an Oracle binary. The other possible issue is that you may be violating the terms for using the Java trademark(s) if you distribute a version that is incompatible with "true" Java, and call your distro "Java". The solution to that is ... don't call it "Java"!

However, don't just follow my advice. Ask a lawyer. Better yet, don't do it at all. It is unnecessarily complicated.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • You say that *[d]oing it by using a Java agent to use a patched version of the class is breaking the rules too* what is not true. You can instrument whatever bootstrap class you want to. – Rafael Winterhalter Nov 10 '15 at 14:04
  • This isn't instrumentation. This is deliberately changing the behavior of the JVM. Unless the customer agrees, you are breaking *their* rules; i.e. the one that says that Java version X must be supported. Why is it bad? Suppose, they are going to mix your code with other code that requires the same class / method to behave in the standard way? – Stephen C Nov 10 '15 at 14:06
  • An equivalent question to mine is: If there are two classes with identical names, same interfaces and the same package name - how can you set which one is recognized and which one is disregarded? – bogus Nov 10 '15 at 14:12
  • @bogus The bootstrap class loader which is responsible for loading Swing classes is always queried first for the class and shadows any other class of the same existant. If two classes of the same name (illegal when "hardcoded" into a VM distribution), the first class found on this class path is loaded. The instrumentation API allows you to prepend classes to the bootstrap class path but these classes would then be discovered last such that the buggy class would be loaded. – Rafael Winterhalter Nov 10 '15 at 14:14
  • @StephenC How would your approach be different in this regard? Instrumentation only affects the instrumented application, changing the VM changes the global behavior of a Java VM which is why it is not permitted by Oracle. The agent is always less intrusive compared to a global patch. – Rafael Winterhalter Nov 10 '15 at 14:16
  • @RafaelWinterhalter *"How would your approach be different in this regard?"* - It is less code to write, and easier for the customer to audit. *"The agent is always less intrusive compared to a global patch."* - Agreed. I don't recommend a global patch. I recommend using tweaking the bootclasspath. (It is a JVM command line option ...) – Stephen C Nov 10 '15 at 14:22
  • @RafaelWinterhalter - You can't make a call on what the customer wants with out asking the customer. (And this is not >>your<< customer.) For instance, the customer may have to get this application past network security or operations reviewers for whom 3rd-party patches to JVMs are anathema. – Stephen C Nov 10 '15 at 14:30
  • Additionally: "The license for this software does not allow the redistribution of beta and other pre-release versions." Oracle's patch is going to be released in v9. – bogus Nov 11 '15 at 10:59
  • @bogus As long as you do not refer to it as "Java". – Rafael Winterhalter Nov 11 '15 at 23:15