使用 Proguard 完全删除未使用的字段引用(例如日志记录)
Fully remove unused field references (e.g. logging) with Proguard
我正在尝试从我的应用程序中删除日志记录。这是一个非常简单的 isolated,但完整的示例:
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
文件时,Logger
和 LoggerFactory
都存在,甚至被使用:
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
字段,并希望完全摆脱初始化程序。并且由于不会使用这些记录器 类,它们将被缩小。
我错过了什么?这是不可能的还是我只是使用了错误的选项。如果是这样,我需要什么选择才能实现它?
您被 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[]);
}
它实际上保留了所有 classes(即使它们是空的)。除此之外,它保留了他们的主要方法(如果有的话)。我将其替换为仅保留具有 main(String[])
方法的 class 的规则(它也保留该方法)。
正确的用法-assumenosideeffects
有点复杂。实际上有必要省略 Logger
的构造函数而不是 LoggerFactory.createLogger()
。这可能是由于应用优化的顺序,createLogger()
方法似乎被优化掉并被 new Logger()
取代,后者将被保留。
当你不确定你的选项有什么问题时,最好使用
-whyareyoukeeping class_specification
选项。它会尝试向您指出 class.
的原因
编辑:我忘了说我使用的是 ProGuard 5.2.1 版本。
您使用的 ProGuard 版本已过时。
尝试使用版本 5.2.1(可用 here),您应该会得到预期的结果。
我正在尝试从我的应用程序中删除日志记录。这是一个非常简单的 isolated,但完整的示例:
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
文件时,Logger
和 LoggerFactory
都存在,甚至被使用:
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
字段,并希望完全摆脱初始化程序。并且由于不会使用这些记录器 类,它们将被缩小。
我错过了什么?这是不可能的还是我只是使用了错误的选项。如果是这样,我需要什么选择才能实现它?
您被 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[]);
}
它实际上保留了所有 classes(即使它们是空的)。除此之外,它保留了他们的主要方法(如果有的话)。我将其替换为仅保留具有 main(String[])
方法的 class 的规则(它也保留该方法)。
正确的用法-assumenosideeffects
有点复杂。实际上有必要省略 Logger
的构造函数而不是 LoggerFactory.createLogger()
。这可能是由于应用优化的顺序,createLogger()
方法似乎被优化掉并被 new Logger()
取代,后者将被保留。
当你不确定你的选项有什么问题时,最好使用
-whyareyoukeeping class_specification
选项。它会尝试向您指出 class.
的原因编辑:我忘了说我使用的是 ProGuard 5.2.1 版本。
您使用的 ProGuard 版本已过时。 尝试使用版本 5.2.1(可用 here),您应该会得到预期的结果。