-
-
Notifications
You must be signed in to change notification settings - Fork 67
Immutability
SootUp has been designed with the goal of immutability in mind. This makes sharing objects between several entities easier, because there is no need to worry about unintended changes to other entities.
Due to the immutability goal, many classes do not have setters anymore. For example, a Body
does not have a method setStmts(List<Stmt> stmts)
. Instead, a method called withStmts(List<Stmt> stmts)
has been added. This does not modify the original instance, but returns a copy that has different stmts
than the original instance. This concept of so-called with
-ers can be found all throughout SootUp. A simplified example:
class Body {
final List<Stmt> stmts;
final List<Local> locals;
Body(List<Stmt> stmts, List<Local> locals) {
this.stmts = stmts;
this.locals = locals;
}
Body withStmts(List<Stmt> stmts) { return new Body(stmts, this.locals); }
Body withLocals(List<Local> locals) { return new Body(this.stmts, locals); }
}
To understand this concept in more detail, consider the following example. The application we're analyzing with SootUp contains a class Dog
that has two methods wagTail()
and bark()
. The implementation of bark()
prints out "woof". We'd like to localize this dog class and make it print "wuff" for a German audience. This can be achieved by modifying the stmts
contained by the Body
of the SootMethod
belonging to the SootClass
. The following code shows how this can be done:
SootClass dogClass = ...;
SootMethod barkMethod = dogClass.getMethod(...);
JAssignStmt wuffAssign = new JAssignStmt(
new Local("$s0", DefaultIdentifierFactory.getClassType("java.lang.String")),
new StringConstant("wuff"),
new NoPositionInformation()
);
SootClass germanDogClass = dogClass.withReplacedMethod(
barkMethod,
barkMethod.withBodyStmts(newStmts -> newStmts.set(0, wuffAssign))
);
As illustrated in the below diagram, this creates a new JAssignStmt
that assigns "wuff" to the local variable $s0
that contained "woof" before. As entities are immutable, we can't modify the stmts
of the initial barkMethod()
. Therefore, barkMethod.withBodyStmts(...)
returns a new SootMethod
in which the statement at position 0
was replaced by the wuffAssign
statement we just created. Finally, we need to create a new SootClass
for the German dog, because the original SootClass
also can't be modified. dogClass.withReplacedMethod(...)
does this by replacing the method defined by its first argument with the second argument.
The diagram also illustrates which entities are shared and which have to be newly created. Shared entities are indicated by their green background. In this case, the entire wagTail()
method can be shared between the two SootClasses as it was not modified at all. On the other hand, the bark()
method has been copied with the exception of the last two statements as we haven't modified those.
In some rare cases, it was not possible to remove setters entirely yet. As users are not supposed to use these, they have been made private and exposed through dedicated accessor classes. For example:
class NotQuiteImmutable {
// ...
private void setFoo(Foo foo) { /* ... */ }
public static class $Accessor {
/** For internal use only **/
@Deprecated public static void setFoo(NotQuiteImmutable n, Foo foo) {
n.setFoo(foo);
}
}
}
The deliberately obscure name is supposed to discourage users from using these methods, as they are meant to be internal APIs. While this cannot guarantee that users will not use them regardless, the documentation makes clear that this is unsupported and may be removed in the future. The goal is to get rid of this mechanism as soon as all algorithms that rely on them have been migrated. Additionally, SootUp needs to ensure that a user does not see any changes to objects they consider immutable.
In some cases, we are forced to return objects conforming to mutable APIs for pragmatic reasons. A lot of APIs return lists and other collections of data, but unfortunately the JDK's Collections API is mutable by default. This means that we cannot hide methods such as List<T>.add(T t)
from users; however, we expect users not to call them. To prevent the use of these methods, publicly exposed collections should throw exceptions when mutating methods are called. This can be easily achieved by using the various Collections.unmodifiableXYZ(...)
APIs to create a read-only view of a read-write collection or ImmutableUtils.immutableXYZ(...)
to create fully immutable collections.
-
Goals
-
Documentation
-
Coding Conventions