Skip to content
Eric Bodden edited this page Mar 27, 2015 · 4 revisions

Introduction

The Booster is a tool that makes TamiFlex compatible with any static-analysis tool that can analyze Java bytecode. Without the Booster, static analyses need to be TamiFlex-aware in order to make use of the information contained in the log files that TamiFlex produces. The Booster instead takes as input class files and the log file produced by the Play-Out Agent and from this input produces an "enriched" program version. This enriched version contains copies of the reflective calls in the form of standard Java method calls. These calls are guarded by an opaque predicate, i.e. a predicate that is always "false", which means that they never actually execute. Nevertheless, because the predicate is opaque, static analyses will not know that the calls never execute and therefore soundly take them into account during call-graph construction.

Usage

You can use the current version of the Booster using the following command line:

java -jar booster-<version>.jar -p cg reflection-log:out/refl.log -cp out -main-class Main Main

Here we assume that Main is the program's main class and that the directory out contains the class files and reflection log file out/refl.log produced by the Play-out Agent.

In the future, we plan to simplify the Booster's command line.

The Booster can be given additional command line options from Soot.

Code transformation

To give an example, consider the following small example program:

class Main {
	public static void main(String[] args) throws Exception {
		Connection c = new Connection();
		Class clazz = Generator.makeClass(); //returns Foo$42
		Method m = clazz.getMethod("foo",Connection.class);
		m.invoke(null, c); //calls Foo$42.foo(..) 
		c.write("what a risky method call: c is closed!"); 
	}
}
class Foo$42 {
	public static void foo(Connection c) {
		c.close();
	}
}

The code calls Foo$42.foo(c) through reflection. The Booster would transform this code as follows:

public static void main(String[] args) throws Exception {
	Connection c = new Connection();
	Class clazz = Generator.makeClass(); //returned Foo$H1 
	Method m = clazz.getMethod("foo",Connection.class);
	if(Opaque.false()) Foo$H1.foo(c); //materialized call 
	else m.invoke(null, c); //calls Foo$H1.foo() 
	BoosterRuntime.check(0,m); //check if m is known 
	c.write("what a risky method call: c is closed!");
}

public class BoosterRuntime {
private static Set knownTargets = ... // {"0-Foo$H1.foo()"} 
public static void check(int caller, Method m) {
	if(!knownTargets.contains(caller+"-"+m.signature())) {
		Listener.warnMethodInvoke(m); //issue warning
	}	
} ... }

First, note that the Booster uses normalized class names such as Foo$H1, as produced by the Hasher. The Booster added lines 5 and 7 and modified line 6. Line 5 contains the "materialized" call. Because this (previously hidden) call is now present in the bytecode, static-analysis tools will typically pick up the call when constructing their call graph. Line 7 contains the runtime check that warns the programmer when executing reflective calls that the Booster did not take into account because these calls were not previously recorded. The check is very efficient: it amounts to a hash-set look-up in the Booster-generated class BoosterRuntime (bottom part of code). The Booster distinguishes calls by calling context: a target that is known and allowed in one context may yield a warning in another context. The check-method therefore uses a statically generated context id to look up the information for the right context. To issue a warning, the check-method calls a user-definable warning listener.

You may have noticed that the materialized call in line 5 is guarded by a conditional. The expression Opaque.false() resembles an "opaque" predicate, i.e., a Boolean value that is always false, but which no static analysis can evaluate to “always false”. Due to this predicate, the materialized call will never actually execute, however any sound static analysis will safely assume that the call may execute. Because the program still executes the original reflective call in line 6, its original behavior is unchanged. The reader may wonder why the need for opaque predicates and why we do not execute the materialized call directly. Unfortunately, such direct calls may not work if multiple class loaders are involved. Consider this code:

Class c = myLoader.loadClass("C");
Method m = c.getMethod("m");
m.invoke(null,null);
C.m(); //does not use <myLoader>.C but C in current scope

The call at line 3 calls C.m() using reflection, where C is a class loaded by the class loader myLoader. The call at line 4 attempts to perform the same method call directly. Direct method calls, such as in this line, however, will always use the class loader of the class that contains the method call. If this class loader has no access to C then the call C.m() will result in an exception. Executing the original reflective call instead of the materialized call prevents this problem. The Booster is an extension of the Soot bytecode analysis and transformation framework. Nevertheless, the pro- grams that the Booster produces can be processed by a wide range of program-analysis tools, not just by Soot.

Implementation

The Booster is mainly implemented as part of Soot and is maintained with the Soot project (see here). The Booster binary that you find on our downloads page simply uses a this wrapper class around Soot.

Clone this wiki locally