SimAnalyzer is an analyzer that computes the values of primitives and basic objects where possible. Additional features like dead code detection are also available. The analyzer is highly configurable so that it can be customized to your personal use case with relative ease.
- Highly configurable
- Provide custom type comparator for inheritance, required for accurate frame generation if custom types are defined in the analyzed code.
- Provide custom exception factory, allowing for custom handling of resolvable errors.
- Provide custom static-invoke factory, allowing custom defined return values of static method calls.
- Provide custom static-get factory, allowing custom defined values of static fields.
- Detect dead code
- By default dead code is skipped entirely, resulting in
null
values for frames at the indices of instructions within dead code blocks.
- By default dead code is skipped entirely, resulting in
- Track instructions that contribute to values
- Values track the instructions that directly contribute to their value
- Track basic control flow blocks
Add Jitpack to your repositories
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Add SimAnalyzer dependency (where VERSION
is the latest version)
<dependency>
<groupId>com.github.Col-E</groupId>
<artifactId>SimAnalyzer</artifactId>
<version>VERSION</version>
</dependency>
implementation 'com.github.Col-E:SimAnalyzer:VERSION'
// Override SimAnalyzer's provider methods to add additional functionality or
// to enhance existing function with outside information provided by you
SimAnalzer analyzer = new SimAnalyzer(new SimInterpreter()) {
@Override
protected ResolvableExceptionFactory createExceptionFactory() {
// Allow overriding error-resolving logic
return super.createExceptionFactory();
}
@Override
protected StaticInvokeFactory createStaticInvokeFactory() {
// Allow managing the values of static invoke calls
return super.createStaticInvokeFactory();
}
@Override
protected StaticGetFactory createStaticGetFactory() {
// Allow managing the values of static invoke calls
return super.createStaticGetFactory();
}
@Override
protected TypeChecker createTypeChecker() {
// Allow better type checking, default uses system classpath
return super.createTypeChecker();
}
@Override
protected TypeResolver createTypeResolver() {
// Allow common type resolution, defaults to only merging exactly equal types
return super.createTypeChecker();
}
@Override
protected ParameterFactory createParameterFactory() {
// Allow the interpreter to be fed literal values for the parameters of the analyzed method
return super.createParameterFactory();
}
};
// Determine if we want to skip dead-code blocks
analyzer.setSkipDeadCodeBlocks(true / false);
// Determine if we want to throw unresolved errors, or keep them silent
analyzer.setThrowUnresolvedAnalyzerErrors(true / false);
To easily create a TypeChecker
implementation you can use the built-in hierarchy graph tool InheritanceGraph
// Setup the graph
InheritanceGraph graph = new InheritanceGraph();
graph.addClasspath(); // add all files loaded in the classpath
graph.addModulePath(); // add all files on the module path (Java 9+)
graph.addClass(new File("example.class")); // add single class
graph.addClass(Files.readAllBytes(Paths.get("example.class"))); // add bytecode
graph.addArchive(new File("example.jar")); // add jar or jmod (java module)
graph.addDirectory(new File("directory/with/classes-or-jars")); // add directory (recursive)
graph.add("child", Arrays.asList("parent1", "parent2")); // manually specify child/parent relations
// Use the graph
@Override
protected TypeChecker createTypeChecker() {
return (parent, child) -> graph.getAllParents(child.getInternalName())
.contains(parent.getInternalName());
}
The same can be done for a TypeResolver
:
// Using the same "graph" object from above
@Override
protected TypeResolver createTypeResolver() {
return new TypeResolver() {
@Override
public Type common(Type type1, Type type2) {
String common = graph.getCommon(type1.getInternalName(), type2.getInternalName());
if (common != null)
return Type.getObjectType(common);
return TypeUtil.OBJECT_TYPE;
}
@Override
public Type commonException(Type type1, Type type2) {
String common = graph.getCommon(type1.getInternalName(), type2.getInternalName());
if (common != null)
return Type.getObjectType(common);
return TypeUtil.EXCEPTION_TYPE;
}
};
}
There are two primary exception types. There is the default ASM AnalyzerException
and SimAnalyzer's ResolableAnalyzerException
.
AnalyzerException: Thrown when problems occurred in analysis that could not be resolved
ResolableAnalyzerException: Logged interally when problems occurred in analysis and checked after analysis finishes to determine if the problem has been resolved. A problem can be resolved when ASM's analyzer revisits some frames and their values due to the nature of its control flow handling.
- If a problem is unresolved, frames will still have been generated.
- Unresolved errors will be thrown unless
analyzer.setThrowUnresolvedAnalyzerErrors(false);
is set.
If any other exception type is thrown, please open a bug report with the full stacktrace.
- ASM-Analysis JavaDoc - ASM analysis javadocs.
- Using ASM-Analysis to remove dead code - An example use case for SimAnalyzer to remove dead code.