ProGuard breaks JavaFX application
Asked Answered
C

2

14

I'm trying to obfuscate my JavaFX application but it fails. The generated result does not work and I do not understand why. The resulting jar just fails because the fxml file cannot load all imports anymore (ClassNotFoundException).

The Deployment workflow:

  1. Build runnable jar (in IntelliJ knwon as an artifact)
  2. Obfuscate that jar with ProGuard
  3. Fix some issues in that jar that ProGuard fails to do

1) The minimal example application

The example application 'GuardTest' is a IntelliJ project that consists of 3 classes.

  • sample.Main: contains the application entry point and loads the GUI fxml file 'sample.fxml'
  • sample.Controller: the controller class for 'sample.fxml'
  • controls.CustomControl: A simple javafx control that inherits from HBox. This is used in 'sample.fxml'

The contents of 'sample.fxml':

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>

<?import controls.CustomControl?>

<VBox fx:controller="sample.Controller"
          xmlns:fx="http://javafx.com/fxml">
    <children>
        <CustomControl>
            <children>
                <Button text="Test"></Button>
            </children>
        </CustomControl>
    </children>
</VBox>

2) Obfuscation

Now I use ProGuard for the resulting jar file that is generated from the above project. I use the following settings:

-target 8

-injars ./out/artifacts/JavaFXApp/JavaFXApp.jar

-outjars ./out/obfuscated/Obfuscated.jar
-ignorewarnings

-printmapping ./out/obfuscated/proguard.map
-dontusemixedcaseclassnames
-dontshrink
-dontoptimize
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers

#-flattenpackagehierarchy
-repackageclasses 'p'
-allowaccessmodification

-libraryjars "<java.home>/lib/rt.jar"
-libraryjars "<java.home>/lib/javaws.jar"
-libraryjars "<java.home>/lib/ext/jfxrt.jar"

-adaptresourcefilecontents **.fxml,**.properties,META-INF/MANIFEST.MF,images/*.jar,publicCerts.store,production.version

-keepattributes javafx.fxml.FXML,Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod
-keepclassmembers class * {
    @javafx.fxml.FXML *;
}

-keepclassmembernames public class com.javafx.main.Main, com.nywelt.sharkload.application.Main {
    public static void main(java.lang.String[]);
}
-keepclasseswithmembers public class com.javafx.main.Main, com.product.main.EntryFX, net.license.LicenseEntryPoint {
    public *; public static *;
}

3) Fixing some (obvious) ProGuard failures

The resulting jar file 'Obfuscated.jar' has the following structure:

**Obfuscated.jar**
- META-INF
--> MANIFEST.MF
- p
--> a.class
--> b.class
--> c.class
- sample
--> sample.fxml

The main class starts the GUI by loading the 'sample.fxml' file with the following line:

Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));

Because of that I have to move the 'sample.fxml' file to the folder p as well to make the above line work again. I also fix some issue in the fxml file where ProGuard forgets to change a (now obfuscated) class name.

Now the structure looks like this:

**Obfuscated_fixed.jar**
- META-INF
--> MANIFEST.MF
- p
--> a.class
--> b.class
--> c.class
--> sample.fxml

The sample.fxml file now looks like this:

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>

<?import p.a?>

<VBox fx:controller="p.b"
          xmlns:fx="http://javafx.com/fxml">
    <children>
        <a>
            <children>
                <Button text="Test"></Button>
            </children>
        </a>
    </children>
</VBox>

The Problem

Now this jar should really work again because everything is ok again. But it DOESN'T! The fxml loader fails to load the CustomControl (now named/obfuscated 'a.class'). Why is that?

I get the following error output when starting the jar file (I'm running java version 1.8.0_40):

E:\Eigene Programme\GuardTest\out\obfuscated>java -jar Obfuscated_fixed.jar
Exception in Application start method
Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at sun.launcher.LauncherHelper$FXHelper.main(Unknown Source)
Caused by: java.lang.RuntimeException: Exception in Application start method
        at com.sun.javafx.application.LauncherImpl.launchApplication1(Unknown So
urce)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$152(
Unknown Source)
        at com.sun.javafx.application.LauncherImpl$$Lambda$49/849460928.run(Unkn
own Source)
        at java.lang.Thread.run(Unknown Source)
Caused by: javafx.fxml.LoadException:
file:/E:/Eigene%20Programme/GuardTest/out/obfuscated/Obfuscated_fixed.jar!/p/sam
ple.fxml

        at javafx.fxml.FXMLLoader.constructLoadException(Unknown Source)
        at javafx.fxml.FXMLLoader.importClass(Unknown Source)
        at javafx.fxml.FXMLLoader.processImport(Unknown Source)
        at javafx.fxml.FXMLLoader.processProcessingInstruction(Unknown Source)
        at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
        at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
        at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
        at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
        at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
        at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
        at javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
        at javafx.fxml.FXMLLoader.load(Unknown Source)
        at p.c.start(Main.java:13)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$159
(Unknown Source)
        at com.sun.javafx.application.LauncherImpl$$Lambda$52/663980963.run(Unkn
own Source)
        at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$172(Unknown
 Source)
        at com.sun.javafx.application.PlatformImpl$$Lambda$46/410424423.run(Unkn
own Source)
        at com.sun.javafx.application.PlatformImpl.lambda$null$170(Unknown Sourc
e)
        at com.sun.javafx.application.PlatformImpl$$Lambda$48/1149216748.run(Unk
nown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$171(Unknown S
ource)
        at com.sun.javafx.application.PlatformImpl$$Lambda$47/1963387170.run(Unk
nown Source)
        at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
        at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
        at com.sun.glass.ui.win.WinApplication.lambda$null$145(Unknown Source)
        at com.sun.glass.ui.win.WinApplication$$Lambda$36/237061348.run(Unknown
Source)
        ... 1 more
Caused by: java.lang.ClassNotFoundException
        at javafx.fxml.FXMLLoader.loadType(Unknown Source)
        ... 26 more

E:\Eigene Programme\GuardTest\out\obfuscated>Pause
Drücken Sie eine beliebige Taste . . .

Setting the default class loader in the main class with

FXMLLoader.setDefaultClassLoader(this.getClass().getClassLoader());

does not help either.

Project Files

Here you can find the example project (IntelliJ): https://www.dropbox.com/s/ot51spvwk6lzo4k/GuardTest.zip?dl=0

The generated jar artifact by IntelliJ is compiled to: ./out/artifacts/JavaFXApp/JavaFXApp.jar

The obfuscated Jar is found under: ./out/obfuscated/Obfuscated.jar

The obfuscated but fixed (at least it should be) jar as described above: ./out/obfuscated/Obfuscated_fixed.jar

And to show that the import statement in the 'sample.fxml' file causes the problem I removed my custom control from the fxml file and saved that to the (working) jar: ./out/obfuscated/Obfuscated_fixed_work.jar

I'm sorry for the long question. I hope you will help me anyway :)

Chignon answered 27/3, 2015 at 18:0 Comment(0)
C
11

I have found the solution! The problem is that FXML cannot import classes that do not start with an upper case letter. Therefore one has to provide an own list of available names that ProGuard uses for obfuscating. This is done by:

-classobfuscationdictionary obfuscationClassNames.txt

With obfuscationClassNames.txt containing the line seperated list of available class names:

A
B
C
D
...
Chignon answered 31/3, 2015 at 17:37 Comment(2)
Thanks! Worked for me as well. I'd think there'd be some simpler option for ProGuard thoughTalapoin
Nice but where do I keep this file? or how do I link it to the Obfuscating process started in proguardGui?Terti
C
0

I used the same steps. I can see imports are updated into fxml file but their use is not.

Getting the exception:

javafx.fxml.LoadException: MenuBarControl is not a valid type.

But If I see the fxml file imports are updated but not their use.

< ? import q.A ? >
< ? import r.A ? >

 <VBox fx:id="top">
    <MenuBarControl fx:id="menuBarControl"/>
  </VBox>
Chorion answered 8/11, 2015 at 7:25 Comment(3)
That is an additional problem with ProGuard when I remember correctly. You need to configure ProGuard to output the mapping file (which class has which obfuscated name) with the option '-printmapping' and change it manually after obfuscation.Chignon
This can easily achieved by avoiding the <?import ... > statements and write the full class name in the XML tag. My current problem is that the fx:ids aren't applied to my controller class, so I get NullPointerExceptions... Any solutions for that? @ChignonMignonmignonette
Did you try to add -keepattributes and -keepclassmembers to the ProGuard settings as shown in my original question above? That should solve the problem since classmembers that are annotated as fxml should not get obfuscated.Chignon

© 2022 - 2024 — McMap. All rights reserved.