-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wip: functional pure function analysis
- Loading branch information
1 parent
365bee4
commit ee95cde
Showing
30 changed files
with
557 additions
and
1,433 deletions.
There are no files selected for viewing
49 changes: 49 additions & 0 deletions
49
...uscator.obfuscator.pureanalysis/src/main/java/dev/skidfuscator/pureanalysis/Analyzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package dev.skidfuscator.pureanalysis; | ||
|
||
import org.objectweb.asm.tree.AbstractInsnNode; | ||
import org.objectweb.asm.tree.ClassNode; | ||
import org.objectweb.asm.tree.MethodNode; | ||
|
||
public abstract class Analyzer { | ||
protected final PurityContext context; | ||
protected final PurityAnalyzer analyzer; | ||
protected final String name; | ||
|
||
protected Analyzer(String name, PurityContext context, PurityAnalyzer analyzer) { | ||
this.name = name; | ||
this.context = context; | ||
this.analyzer = analyzer; | ||
} | ||
|
||
public abstract PurityReport analyze(Context ctx); | ||
|
||
protected PurityReport pure() { | ||
return new PurityReport(true, name, null, null); | ||
} | ||
|
||
protected PurityReport impure(String reason, AbstractInsnNode insn) { | ||
return new PurityReport(false, name, reason, insn); | ||
} | ||
|
||
protected PurityReport impure(String reason) { | ||
return new PurityReport(false, name, reason, null); | ||
} | ||
|
||
public static class Context { | ||
MethodNode method; | ||
ClassNode classNode; | ||
|
||
public Context(MethodNode method, ClassNode classNode) { | ||
this.method = method; | ||
this.classNode = classNode; | ||
} | ||
|
||
public MethodNode method() { | ||
return method; | ||
} | ||
|
||
public ClassNode parent() { | ||
return classNode; | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...dfuscator.obfuscator.pureanalysis/src/main/java/dev/skidfuscator/pureanalysis/Purity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package dev.skidfuscator.pureanalysis; | ||
|
||
public enum Purity { | ||
IMPURE, | ||
PURE, | ||
MUD; | ||
|
||
boolean isPure() { | ||
return this == PURE || this == MUD; | ||
} | ||
} |
116 changes: 27 additions & 89 deletions
116
...r.obfuscator.pureanalysis/src/main/java/dev/skidfuscator/pureanalysis/PurityAnalyzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,112 +1,50 @@ | ||
package dev.skidfuscator.pureanalysis; | ||
|
||
import dev.skidfuscator.pureanalysis.condition.PurityCondition; | ||
import org.objectweb.asm.tree.ClassNode; | ||
import org.objectweb.asm.tree.MethodNode; | ||
import dev.skidfuscator.pureanalysis.impl.*; | ||
import org.objectweb.asm.tree.*; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
public class PurityAnalyzer { | ||
private final ConcurrentHashMap<String, Boolean> pureClasses = new ConcurrentHashMap<>(); | ||
private final ConcurrentHashMap<String, Boolean> methodPurityCache = new ConcurrentHashMap<>(); | ||
private final Set<Analyzer> analyzers = new HashSet<>(); | ||
private final PurityContext context; | ||
private final ClassHierarchyAnalyzer hierarchyAnalyzer; | ||
private final List<PurityCondition> conditions; | ||
|
||
// ThreadLocal set to track methods being analyzed in the current thread | ||
private final ThreadLocal<Set<String>> methodsUnderAnalysis = ThreadLocal.withInitial(HashSet::new); | ||
|
||
public PurityAnalyzer(ClassLoader classLoader) { | ||
this.hierarchyAnalyzer = new ClassHierarchyAnalyzer(classLoader); | ||
this.conditions = new ArrayList<>(); | ||
} | ||
|
||
public void addCondition(PurityCondition condition) { | ||
conditions.add(condition); | ||
public PurityAnalyzer(ClassHierarchyAnalyzer hierarchyAnalyzer) { | ||
this.hierarchyAnalyzer = hierarchyAnalyzer; | ||
this.context = new PurityContext(this); | ||
initializeAnalyzers(); | ||
} | ||
|
||
public void registerPureClass(String className) { | ||
pureClasses.put(className, true); | ||
public PurityContext getContext() { | ||
return context; | ||
} | ||
|
||
public boolean isPureClass(String className) { | ||
return pureClasses.getOrDefault(className, false); | ||
private void initializeAnalyzers() { | ||
analyzers.add(new TypeInstructionAnalyzer(context, this)); | ||
analyzers.add(new MethodInstructionAnalyzer(context, this)); | ||
analyzers.add(new FieldInstructionAnalyzer(context, this)); | ||
analyzers.add(new DynamicInstructionAnalyzer(context, this)); | ||
analyzers.add(new NativeMethodAnalyzer(context, this)); | ||
analyzers.add(new PrimitiveParametersAnalyzer(context, this)); | ||
} | ||
|
||
public boolean isPureMethod(String owner, String name, String desc) { | ||
String key = owner + "." + name + desc; | ||
|
||
// If the method is currently being analyzed, assume it's pure to break recursion | ||
if (methodsUnderAnalysis.get().contains(key)) { | ||
return true; | ||
} | ||
|
||
return methodPurityCache.getOrDefault(key, false); | ||
} | ||
|
||
public boolean analyzeMethod(MethodNode method, ClassNode classNode) { | ||
String methodKey = classNode.name + "." + method.name + method.desc; | ||
|
||
// If the method is already cached, return the cached result | ||
Boolean cachedResult = methodPurityCache.get(methodKey); | ||
if (cachedResult != null) { | ||
return cachedResult; | ||
} | ||
|
||
// If we're already analyzing this method, return true to break recursion | ||
Set<String> currentMethods = methodsUnderAnalysis.get(); | ||
if (currentMethods.contains(methodKey)) { | ||
return true; | ||
} | ||
|
||
// Add this method to the set of methods being analyzed | ||
currentMethods.add(methodKey); | ||
|
||
try { | ||
// Evaluate all conditions | ||
boolean isPure = true; | ||
for (PurityCondition condition : conditions) { | ||
boolean result = condition.evaluateAndPrint(method, classNode, this); | ||
if (!result) { | ||
isPure = false; | ||
break; | ||
} | ||
} | ||
public PurityReport analyzeMethodPurity(MethodNode method, ClassNode classNode) { | ||
final PurityReport report = new PurityReport(true, "Method Analysis", null, null); | ||
final Analyzer.Context methodCtx = new Analyzer.Context( | ||
method, | ||
classNode | ||
); | ||
|
||
// Cache the result | ||
methodPurityCache.put(methodKey, isPure); | ||
return isPure; | ||
} finally { | ||
// Remove this method from the set of methods being analyzed | ||
currentMethods.remove(methodKey); | ||
if (currentMethods.isEmpty()) { | ||
methodsUnderAnalysis.remove(); | ||
} | ||
for (Analyzer analyzer : analyzers) { | ||
report.addNested(analyzer.analyze(methodCtx)); | ||
} | ||
|
||
return report; | ||
} | ||
|
||
public ClassHierarchyAnalyzer getHierarchyAnalyzer() { | ||
return hierarchyAnalyzer; | ||
} | ||
|
||
private final Set<String> analyzedClasses = ConcurrentHashMap.newKeySet(); | ||
|
||
public void analyzeClass(String className) throws IOException { | ||
if (analyzedClasses.contains(className)) { | ||
return; | ||
} | ||
|
||
ClassNode classNode = hierarchyAnalyzer.getClass(className); | ||
|
||
// Analyze all methods in the class | ||
for (MethodNode method : classNode.methods) { | ||
analyzeMethod(method, classNode); | ||
} | ||
|
||
analyzedClasses.add(className); | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
...or.obfuscator.pureanalysis/src/main/java/dev/skidfuscator/pureanalysis/PurityContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package dev.skidfuscator.pureanalysis; | ||
|
||
import java.util.*; | ||
|
||
import org.objectweb.asm.tree.ClassNode; | ||
|
||
public class PurityContext { | ||
private final Set<String> impureObjects = new HashSet<>(); | ||
private final Map<String, Purity> pureStaticMethods = new HashMap<>(); | ||
private final Map<String, Purity> pureMethods = new HashMap<>(); | ||
private final Map<String, Set<String>> hierarchyCache = new HashMap<>(); | ||
|
||
private final PurityAnalyzer analyzer; | ||
|
||
public PurityContext(PurityAnalyzer analyzer) { | ||
this.analyzer = analyzer; | ||
} | ||
|
||
private Set<String> computeHierarchy(String type) { | ||
try { | ||
Set<String> hierarchy = new HashSet<>(); | ||
String current = type; | ||
|
||
while (current != null && !current.equals("java/lang/Object")) { | ||
hierarchy.add(current); | ||
ClassNode classNode = analyzer.getHierarchyAnalyzer().getClass(current); | ||
current = classNode.superName; | ||
|
||
// Add interfaces | ||
for (String iface : classNode.interfaces) { | ||
hierarchy.add(iface); | ||
// Recursively add interface hierarchies | ||
hierarchy.addAll(getHierarchy(iface)); | ||
} | ||
} | ||
|
||
return hierarchy; | ||
} catch (Exception e) { | ||
return Collections.singleton(type); | ||
} | ||
} | ||
|
||
public Set<String> getHierarchy(String type) { | ||
return hierarchyCache.computeIfAbsent(type, this::computeHierarchy); | ||
} | ||
|
||
public void markImpure(String type) { | ||
impureObjects.add(type); | ||
} | ||
|
||
public boolean isPure(String type) { | ||
return !impureObjects.contains(type); | ||
} | ||
|
||
public boolean isPureStaticMethod(String signature) { | ||
return pureStaticMethods.getOrDefault(signature, Purity.MUD).isPure(); | ||
} | ||
|
||
public void addPureStaticMethod(String signature) { | ||
pureStaticMethods.put(signature, Purity.PURE); | ||
} | ||
|
||
public boolean isPureMethods(String signature) { | ||
return pureMethods.getOrDefault(signature, Purity.MUD).isPure(); | ||
} | ||
|
||
public void addPureMethods(String signature) { | ||
pureMethods.put(signature, Purity.PURE); | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
...tor.obfuscator.pureanalysis/src/main/java/dev/skidfuscator/pureanalysis/PurityReport.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package dev.skidfuscator.pureanalysis; | ||
|
||
import org.objectweb.asm.tree.AbstractInsnNode; | ||
import org.objectweb.asm.tree.FieldInsnNode; | ||
import org.objectweb.asm.tree.MethodInsnNode; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class PurityReport { | ||
private final boolean pure; | ||
private final String condition; | ||
private final String reason; | ||
private final AbstractInsnNode failedInsn; | ||
private final List<PurityReport> nested = new ArrayList<>(); | ||
|
||
public PurityReport(boolean pure, String condition, String reason, AbstractInsnNode failedInsn) { | ||
this.pure = pure; | ||
this.condition = condition; | ||
this.reason = reason; | ||
this.failedInsn = failedInsn; | ||
} | ||
|
||
public void addNested(PurityReport report) { | ||
nested.add(report); | ||
} | ||
|
||
public boolean isPure() { | ||
return pure && nested.stream().allMatch(PurityReport::isPure); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
StringBuilder sb = new StringBuilder(); | ||
toString(sb, 0); | ||
return sb.toString(); | ||
} | ||
|
||
private void toString(StringBuilder sb, int depth) { | ||
String indent = " ".repeat(depth); | ||
sb.append(indent).append(condition).append(": ").append(pure ? "PURE" : "IMPURE"); | ||
if (!pure && reason != null) { | ||
sb.append("\n").append(indent).append("Reason: ").append(reason); | ||
if (failedInsn != null) { | ||
sb.append("\n").append(indent).append("At instruction: ").append(formatInstruction(failedInsn)); | ||
} | ||
} | ||
for (PurityReport nested : this.nested) { | ||
sb.append("\n"); | ||
nested.toString(sb, depth + 1); | ||
} | ||
} | ||
|
||
private String formatInstruction(AbstractInsnNode insn) { | ||
if (insn instanceof MethodInsnNode) { | ||
MethodInsnNode min = (MethodInsnNode) insn; | ||
return String.format("%s.%s%s", min.owner, min.name, min.desc); | ||
} | ||
if (insn instanceof FieldInsnNode) { | ||
FieldInsnNode fin = (FieldInsnNode) insn; | ||
return String.format("%s.%s:%s", fin.owner, fin.name, fin.desc); | ||
} | ||
return insn.toString(); | ||
} | ||
} |
46 changes: 0 additions & 46 deletions
46
...ureanalysis/src/main/java/dev/skidfuscator/pureanalysis/condition/CompositeCondition.java
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.