使用Proguard完全删除未使用的字段引用(例如,日志记录)

时间:2016-09-16 16:49:19

标签: java logging proguard

我正在尝试从我的应用中删除日志记录。这是一个非常简单的孤立的,但完整的例子:

Java文件(默认包,全部在同一文件中,位于src/source.java

class Logger {
    public void log() {
        new Exception().printStackTrace();
    }
}
class LoggerFactory {
    public static Logger createLogger() {
        return new Logger();
    }
}
class Normal {
    private static final Logger LOG = LoggerFactory.createLogger();
    public static void main(String... args) {
        System.out.println("Normal main");
        LOG.log();
    }
}

Proguard配置(config.pro

-injars src/test.jar
-outjars opt/test.jar
-libraryjars  <java.home>/lib/rt.jar
-dontobfuscate
-optimizationpasses 3

# keep entry points
-keep class ** { public static void main(java.lang.String[]); }
# usual approach to removing logging
-assumenosideeffects class Logger { void log(); }
# attempted solution for my problem:
-assumenosideeffects class LoggerFactory { Logger createLogger(); }
-assumenosideeffects class Normal { Logger LOG; }

构建脚本

set PATH=%JAVA7_HOME%\bin;.\proguard5.2.1\bin\;P:\tools\decompile\jad-1.5.8g-win\

cd src
javac -source 1.7 -target 1.7 *.java
jar cfM test.jar *.class
java -cp test.jar Normal

cd ..
call proguard @config.pro -verbose
java -cp test.jar Normal

cd opt
jar xf test.jar
jad -dead -noconv -nocast -noclass -v -a -o -s java *.class

输出(简化)

$java -cp src/test.jar Normal
Normal main
java.lang.Exception
        at Logger.log(source.java:3)
        at Normal.main(source.java:15)

$proguard @config.pro -verbose
ProGuard, version 4.7
... last optimizing pass prints 0 changes

$java -cp opt/test.jar Normal
Normal main

问题

正如您在上面看到的那样输出是预期的,log()调用没有输出。但是,当我仔细查看生成的.jar文件时,LoggerLoggerFactory都存在,甚至还会使用它们:

class Normal {
    public static transient void main(String args[]) {
        System.out.println("Normal main");
        //    0    0:getstatic       #8   <Field PrintStream System.out>
        //    1    3:ldc1            #1   <String "Normal main">
        //    2    5:invokevirtual   #10  <Method void PrintStream.println(String)>
        Logger _tmp = LOG;
        //    3    8:getstatic       #7   <Field Logger LOG>
        //    4   11:pop             
        //    5   12:return          
    }

    private static final Logger LOG = LoggerFactory.createLogger();
    static {
        //    0    0:invokestatic    #9   <Method Logger LoggerFactory.createLogger()>
        //    1    3:putstatic       #7   <Field Logger LOG>
        //*   2    6:return          
    }
}

如果我看得正确,根本原因就是这两条指令:

        //    3    8:getstatic       #7   <Field Logger LOG>
        //    4   11:pop

如果我可以摆脱那些,那么优化会注意到未使用的LOG字段,并希望完全摆脱初始化程序。而且由于没有使用这些记录器类,它们会缩小。

我错过了什么?这是不可能的,还是我只是使用了错误的选项。如果是这样,我需要做出哪些选择?

2 个答案:

答案 0 :(得分:1)

您已被ProGuard保留选项的细微差别所欺骗!

您正在寻找的规则应该是:

# keep entry points
-keepclasseswithmembers class ** {
    public static void main(java.lang.String[]);
}

# usual approach to removing logging
-assumenosideeffects class Logger { void log(); }

# careful with this one, it's potentially very dangerous
# but in this case it seems necessary (perfect solution probably only with Dexguard)
-assumenosideeffects class Logger { <init>(); }

你必须小心这条规则:

-keep class ** {
    public static void main(java.lang.String[]);
}

它实际上保留了所有类(即使它们是空的)。除此之外,它保留了他们的主要方法(如果有的话)。我用一个规则替换它,只保留一个具有main(String[])方法的类(并且它也保留了方法)。

使用正确的-assumenosideeffects有点复杂。实际上,必须省略Logger的构造函数而不是LoggerFactory.createLogger()。这可能是由于应用优化的顺序,createLogger()方法似乎已经过优化,并被保留的new Logger()取代。

当您不确定选项的问题是什么时,最好使用

-whyareyoukeeping class_specification

选项。它会试着指出你的课程是什么。

编辑:我忘了提到我使用的是ProGuard 5.2.1版。

答案 1 :(得分:0)

您使用的是过时的ProGuard版本。 尝试使用版本5.2.1(可用here),您应该得到预期的结果。