Skip to content

Commit

Permalink
Refactor CallGraph to not be tied directly to CDI. Create wrapping se…
Browse files Browse the repository at this point in the history
…rvice.
  • Loading branch information
Col-E committed Dec 6, 2024
1 parent e801402 commit 6788587
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import dev.xdark.jlinker.Result;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Handle;
Expand All @@ -17,11 +16,9 @@
import software.coley.recaf.RecafConstants;
import software.coley.recaf.analytics.logging.DebuggingLogger;
import software.coley.recaf.analytics.logging.Logging;
import software.coley.recaf.cdi.WorkspaceScoped;
import software.coley.recaf.info.ClassInfo;
import software.coley.recaf.info.JvmClassInfo;
import software.coley.recaf.info.member.MethodMember;
import software.coley.recaf.services.Service;
import software.coley.recaf.util.MultiMap;
import software.coley.recaf.util.threading.ThreadPoolFactory;
import software.coley.recaf.workspace.model.Workspace;
Expand All @@ -32,12 +29,12 @@

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.stream.Stream;

Expand All @@ -48,56 +45,38 @@
* @author Matt Coley
* @see MethodVertex
*/
@WorkspaceScoped
public class CallGraph implements Service, WorkspaceModificationListener, ResourceJvmClassListener {
public static final String SERVICE_ID = "graph-calls";
public class CallGraph implements WorkspaceModificationListener, ResourceJvmClassListener {
private static final DebuggingLogger logger = Logging.get(CallGraph.class);
private final ExecutorService threadPool = ThreadPoolFactory.newFixedThreadPool("call-graph", 1, true);
private final CachedLinkResolver resolver = new CachedLinkResolver();
private final Map<JvmClassInfo, LinkedClass> classToLinkerType = Collections.synchronizedMap(new IdentityHashMap<>());
private final Map<JvmClassInfo, ClassMethodsContainer> classToMethodsContainer = Collections.synchronizedMap(new IdentityHashMap<>());
private final MultiMap<String, MethodRef, Set<MethodRef>> unresolvedDeclarations = MultiMap.from(
Collections.synchronizedMap(new HashMap<>()),
() -> Collections.synchronizedSet(new HashSet<>()));
new ConcurrentHashMap<>(),
() -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
private final MultiMap<String, CallingContext, Set<CallingContext>> unresolvedReferences = MultiMap.from(
Collections.synchronizedMap(new HashMap<>()),
() -> Collections.synchronizedSet(new HashSet<>()));
new ConcurrentHashMap<>(),
() -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
private final ObservableBoolean isReady = new ObservableBoolean(false);
private final CallGraphConfig config;
private final Workspace workspace;
private final ClassLookup lookup;
private boolean initialized;

/**
* @param config
* Graphing config options.
* @param workspace
* Workspace to pull data from.
*/
@Inject
public CallGraph(@Nonnull CallGraphConfig config, @Nonnull Workspace workspace) {
this.config = config;
public CallGraph(@Nonnull Workspace workspace) {
this.workspace = workspace;
lookup = new ClassLookup(workspace);

// Only initialize & register listeners if active.
// Allow toggling the active config later to activate the service.
config.getActive().addChangeListener((ob, old, cur) -> setActive(cur));
setActive(config.getActive().getValue());
lookup = new ClassLookup(workspace);
}

private void setActive(boolean active) {
// Always reset ready state.
isReady.setValue(false);

// Add/remove self as listener.
if (active) {
workspace.addWorkspaceModificationListener(this);
workspace.getPrimaryResource().addResourceJvmClassListener(this);
initialize(workspace);
} else {
workspace.removeWorkspaceModificationListener(this);
workspace.getPrimaryResource().removeResourceJvmClassListener(this);
}
/**
* @return {@code true} when {@link #initialize()} has been called.
*/
public boolean isInitialized() {
return initialized;
}

/**
Expand Down Expand Up @@ -145,10 +124,17 @@ private LinkedClass linked(@Nonnull JvmClassInfo classInfo) {
}

/**
* @param workspace
* Workspace to {@link #visit(JvmClassInfo)} all classes of.
* Initialize the graph.
*/
private void initialize(@Nonnull Workspace workspace) {
public void initialize() {
// Only allow calls to initialize the graph once
if (initialized) return;
initialized = true;

// Register modification listeners so that we can update the graph when class state changes.
workspace.addWorkspaceModificationListener(this);
workspace.getPrimaryResource().addResourceJvmClassListener(this);

// Initialize asynchronously, and mark 'isReady' if completed successfully
CompletableFuture.runAsync(() -> {
for (WorkspaceResource resource : workspace.getAllResources(false)) {
Expand Down Expand Up @@ -450,18 +436,6 @@ MultiMap<String, MethodRef, Set<MethodRef>> getUnresolvedDeclarations() {
return unresolvedDeclarations;
}

@Nonnull
@Override
public String getServiceId() {
return SERVICE_ID;
}

@Nonnull
@Override
public CallGraphConfig getServiceConfig() {
return config;
}

/**
* Models the calling context to some method.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package software.coley.recaf.services.callgraph;

import jakarta.annotation.Nonnull;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import software.coley.observables.ObservableBoolean;
import software.coley.recaf.config.BasicConfigContainer;
import software.coley.recaf.config.BasicConfigValue;
import software.coley.recaf.config.ConfigGroups;
import software.coley.recaf.services.ServiceConfig;

Expand All @@ -16,20 +13,9 @@
*/
@ApplicationScoped
public class CallGraphConfig extends BasicConfigContainer implements ServiceConfig {
private final ObservableBoolean active = new ObservableBoolean(true);

@Inject
public CallGraphConfig() {
super(ConfigGroups.SERVICE_ANALYSIS, CallGraph.SERVICE_ID + CONFIG_SUFFIX);
// Add values
addValue(new BasicConfigValue<>("active", boolean.class, active));
}

/**
* @return Active state of call graph service.
*/
@Nonnull
public ObservableBoolean getActive() {
return active;
super(ConfigGroups.SERVICE_ANALYSIS, CallGraphService.SERVICE_ID + CONFIG_SUFFIX);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package software.coley.recaf.services.callgraph;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import software.coley.recaf.analytics.logging.DebuggingLogger;
import software.coley.recaf.analytics.logging.Logging;
import software.coley.recaf.cdi.EagerInitialization;
import software.coley.recaf.services.Service;
import software.coley.recaf.services.workspace.WorkspaceCloseListener;
import software.coley.recaf.services.workspace.WorkspaceManager;
import software.coley.recaf.services.workspace.WorkspaceOpenListener;
import software.coley.recaf.workspace.model.Workspace;

import java.util.concurrent.CompletableFuture;

/**
* Service offering the creation of {@link CallGraph call graphs} for workspaces.
*
* @author Matt Coley
* @see CallGraph
*/
@EagerInitialization
@ApplicationScoped
public class CallGraphService implements Service, WorkspaceOpenListener, WorkspaceCloseListener {
public static final String SERVICE_ID = "graph-calls";
private static final DebuggingLogger logger = Logging.get(CallGraphService.class);
private final CallGraphConfig config;
private CallGraph currentWorkspaceGraph;

/**
* @param workspaceManager
* Manager to register listeners for, in order to manage a shared graph for the current workspace.
* @param config
* Graphing config options.
*/
@Inject
public CallGraphService(@Nonnull WorkspaceManager workspaceManager, @Nonnull CallGraphConfig config) {
this.config = config;

workspaceManager.addWorkspaceOpenListener(this);
workspaceManager.addWorkspaceCloseListener(this);
}

/**
* @param workspace
* Workspace to pull classes from.
*
* @return New call graph model for the given workspace.
*/
@Nonnull
public CallGraph newGraph(@Nonnull Workspace workspace) {
return new CallGraph(workspace);
}

/**
* @return Call graph model for the {@link WorkspaceManager#getCurrent() current workspace} or {@code null}
* if no workspace is currently open.
*/
@Nullable
public CallGraph getCurrentWorkspaceGraph() {
CallGraph graph = currentWorkspaceGraph;

// Lazily initialize the graph so that we don't do a full graph
if (!graph.isInitialized())
CompletableFuture.runAsync(graph::initialize);

return graph;
}

@Override
public void onWorkspaceOpened(@Nonnull Workspace workspace) {
currentWorkspaceGraph = newGraph(workspace);
}

@Override
public void onWorkspaceClosed(@Nonnull Workspace workspace) {
currentWorkspaceGraph = null;
}

@Nonnull
@Override
public String getServiceId() {
return SERVICE_ID;
}

@Nonnull
@Override
public CallGraphConfig getServiceConfig() {
return config;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ void testUnresolvedCall() {

@Nonnull
static CallGraph graph(@Nonnull Workspace workspace) {
CallGraph graph = new CallGraph(new CallGraphConfig(), workspace);
CallGraph graph = new CallGraph(workspace);

// Need to wait until async population of graph contents is done.
ObservableBoolean ready = graph.isReady();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import software.coley.recaf.path.ClassPathNode;
import software.coley.recaf.path.PathNode;
import software.coley.recaf.services.callgraph.CallGraph;
import software.coley.recaf.services.callgraph.CallGraphService;
import software.coley.recaf.services.cell.CellConfigurationService;
import software.coley.recaf.services.navigation.Actions;
import software.coley.recaf.services.navigation.ClassNavigable;
Expand All @@ -25,6 +26,7 @@

import java.util.Collection;
import java.util.Collections;
import java.util.Objects;

/**
* Container pane for two {@link MethodCallGraphPane} for inbound and outbound calls.
Expand All @@ -37,11 +39,12 @@ public class MethodCallGraphsPane extends TabPane implements ClassNavigable, Upd
private ClassPathNode path;

@Inject
public MethodCallGraphsPane(@Nonnull Workspace workspace, @Nonnull CallGraph callGraph,
public MethodCallGraphsPane(@Nonnull Workspace workspace, @Nonnull CallGraphService callGraphService,
@Nonnull TextFormatConfig format, @Nonnull Actions actions,
@Nonnull CellConfigurationService configurationService) {
currentMethodInfo = new SimpleObjectProperty<>();

CallGraph callGraph = Objects.requireNonNull(callGraphService.getCurrentWorkspaceGraph(), "Graph not created");
getTabs().add(creatTab(workspace, callGraph, configurationService, format, actions, MethodCallGraphPane.CallGraphMode.CALLS, currentMethodInfo));
getTabs().add(creatTab(workspace, callGraph, configurationService, format, actions, MethodCallGraphPane.CallGraphMode.CALLERS, currentMethodInfo));

Expand Down
1 change: 0 additions & 1 deletion recaf-ui/src/main/resources/translations/en_US.lang
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,6 @@ service.analysis.comments-config=Comments
service.analysis.comments-config.enable-display=Display comments in decompilation
service.analysis.comments-config.word-wrapping-limit=Word wrap limit
service.analysis.graph-calls-config=Call graph
service.analysis.graph-calls-config.active=Enable on workspaces open
service.analysis.graph-inheritance-config=Inheritance graph
service.analysis.jphantom-generator-config=JPhantom
service.analysis.jphantom-generator-config.generate-workspace-phantoms=Generate and append phantoms to workspaces
Expand Down
1 change: 0 additions & 1 deletion recaf-ui/src/main/resources/translations/pl_PL.lang
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,6 @@ service.analysis.comments-config=Komentarze
service.analysis.comments-config.enable-display=Wyświetlaj komentarze w dekompilacji
service.analysis.comments-config.word-wrapping-limit=Limit zawijania słów
service.analysis.graph-calls-config=Graf wywołań
service.analysis.graph-calls-config.active=Włącz przy otwieraniu obszarów roboczych
service.analysis.graph-inheritance-config=Graf dziedziczenia
service.analysis.jphantom-generator-config=JPhantom
service.analysis.jphantom-generator-config.generate-workspace-phantoms=Generuj i dołącz fantomy do obszarów roboczych
Expand Down
1 change: 0 additions & 1 deletion recaf-ui/src/main/resources/translations/sv_SE.lang
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,6 @@ tree.hidelibs=Dölj bibliotek
service=Alla tjänster
service.analysis=Analys
service.analysis.graph-calls-config=Samtalsgraf
service.analysis.graph-calls-config.active=Aktivera vid öppning av arbetsmiljöer
service.analysis.graph-inheritance-config=Hierarkigraf
service.analysis.search-config=Sök
service.analysis.entry-points=Ingångspunkter
Expand Down
1 change: 0 additions & 1 deletion recaf-ui/src/main/resources/translations/zh_CN.lang
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,6 @@ service.analysis.comments-config=注释
service.analysis.comments-config.enable-display=在反编译中显示注释
service.analysis.comments-config.word-wrapping-limit=换行限制
service.analysis.graph-calls-config=调用图
service.analysis.graph-calls-config.active=在工作空间打开时启用
service.analysis.graph-inheritance-config=继承图
service.analysis.jphantom-generator-config=JPhantom
service.analysis.jphantom-generator-config.generate-workspace-phantoms=生成并添加phantoms到工作空间
Expand Down

0 comments on commit 6788587

Please sign in to comment.