Skip to content

Commit

Permalink
Move per-compile phantom generation into compiler service
Browse files Browse the repository at this point in the history
Having it in the UI was kinda dumb and made it so that the 'java to bytecode' assembler window didn't have phantom support.
  • Loading branch information
Col-E committed Jan 11, 2025
1 parent 7da81ca commit 8998c1d
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -204,20 +204,21 @@ public List<String> getOuterClassBreadcrumbs() {
return breadcrumbs = Collections.emptyList();

int maxOuterDepth = 10;
breadcrumbs = new ArrayList<>();
List<String> list = new ArrayList<>();
int counter = 0;
while (currentOuter != null) {
if (++counter > maxOuterDepth) {
breadcrumbs.clear(); // assuming some obfuscator is at work, so breadcrumbs might be invalid.
list.clear(); // assuming some obfuscator is at work, so breadcrumbs might be invalid.
break;
}
breadcrumbs.addFirst(currentOuter);
list.addFirst(currentOuter);
String targetOuter = currentOuter;
currentOuter = innerClasses.stream()
.filter(i -> i.getInnerClassName().equals(targetOuter))
.filter(i -> i.getInnerClassName().equals(targetOuter) && i.getOuterClassName() != null)
.map(InnerClassInfo::getOuterClassName)
.findFirst().orElse(null);
}
breadcrumbs = Collections.unmodifiableList(list);
}
return breadcrumbs;
}
Expand Down
21 changes: 18 additions & 3 deletions recaf-core/src/main/java/software/coley/recaf/info/ClassInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,16 @@ default Stream<String> parentTypesStream() {
* This List <strong>MUST</strong> be sorted in order of the outermost first.
* The last element is the outer of the class itself.
* <br>
* For an example, if our class is 'C' then this list will be [A, B]:
* <pre>
* For an example, if our class is 'C' then this list will be {@code [foo/A, foo/A$B]}:
* <pre>{@code
* package foo;
*
* class A {
* class B {
* class C {} // This class
* }
* }
* </pre>
* }</pre>
*
* @return Breadcrumbs of the outer class.
*/
Expand All @@ -147,6 +149,19 @@ default boolean isInnerClass() {
return getOuterClassName() != null || getOuterMethodName() != null;
}

/**
* @param className
* Name of a supposed outer class.
*
* @return {@code true} if this class is an inner class of the given outer class.
*/
default boolean isInnerClassOf(@Nonnull String className) {
// If we don't start with that class name, we can't possibly be an inner class.
if (!getName().startsWith(className + "$"))
return false;
return getOuterClassBreadcrumbs().contains(className);
}

/**
* @return {@code true} when this class is an anonymous inner class of another class.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import software.coley.recaf.analytics.logging.Logging;
import software.coley.recaf.info.JvmClassInfo;
import software.coley.recaf.services.Service;
import software.coley.recaf.services.phantom.GeneratedPhantomWorkspaceResource;
import software.coley.recaf.services.phantom.PhantomGenerationException;
import software.coley.recaf.services.phantom.PhantomGenerator;
import software.coley.recaf.util.LookupUtil;
import software.coley.recaf.workspace.model.Workspace;
import software.coley.recaf.workspace.model.resource.WorkspaceResource;
Expand All @@ -25,6 +28,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

import static java.nio.charset.StandardCharsets.UTF_8;

Expand All @@ -43,10 +47,13 @@ public class JavacCompiler implements Service {
private static final DebuggingLogger logger = Logging.get(JavacCompiler.class);
private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
private static int minTargetVersion = 7;
private final PhantomGenerator phantomGenerator;
private final JavacCompilerConfig config;

@Inject
public JavacCompiler(JavacCompilerConfig config) {
public JavacCompiler(@Nonnull PhantomGenerator phantomGenerator,
@Nonnull JavacCompilerConfig config) {
this.phantomGenerator = phantomGenerator;
this.config = config;
}

Expand Down Expand Up @@ -100,6 +107,25 @@ public CompilerResult compile(@Nonnull JavacArguments arguments,
Collections.emptyList() : workspace.getAllResources(true);
if (supplementaryResources != null)
virtualClassPath = Lists.combine(virtualClassPath, supplementaryResources);

// Generate phantom classes if the workspace does not already have phantoms in it.
if (workspace != null && config.getGeneratePhantoms().getValue() && workspace.getSupportingResources().stream()
.noneMatch(resource -> resource instanceof GeneratedPhantomWorkspaceResource)) {
try {
// Only scan the target class and any of its inner classes for content to fill in.
List<JvmClassInfo> classesToScan = workspace.findJvmClasses(c -> c.getName().equals(className) || c.isInnerClassOf(className)).stream()
.map(p -> p.getValue().asJvmClass())
.collect(Collectors.toList());
WorkspaceResource phantomResource = phantomGenerator.createPhantomsForClasses(workspace, classesToScan);
int generatedCount = phantomResource.getJvmClassBundle().size();
if (generatedCount > 0)
logger.debug("Generated {} phantoms for pre-compile", generatedCount);
virtualClassPath = Lists.add(virtualClassPath, phantomResource);
} catch (PhantomGenerationException ex) {
logger.warn("Failed to generate phantoms for compilation against '{}'", className, ex);
}
}

List<CompilerDiagnostic> diagnostics = new ArrayList<>();
JavacListener listenerWrapper = createRecordingListener(listener, diagnostics);
JavaFileManager fmFallback = compiler.getStandardFileManager(listenerWrapper, Locale.getDefault(), UTF_8);
Expand Down Expand Up @@ -144,7 +170,7 @@ else if (downsampleTarget >= 0)
logger.warn("Cannot downsample beyond Java {}", JavacCompiler.MIN_DOWNSAMPLE_VER);
return new CompilerResult(compilations, diagnostics);
} catch (RuntimeException ex) {
logger.debugging(l -> l.error("Compilation of '{}' crashed: {}", className, ex));
logger.debugging(l -> l.error("Compilation of '{}' crashed", className, ex));
return new CompilerResult(ex);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import software.coley.observables.ObservableBoolean;
import software.coley.observables.ObservableInteger;
import software.coley.recaf.analytics.logging.Logging;
import software.coley.recaf.info.ClassInfo;
import software.coley.recaf.info.InnerClassInfo;
import software.coley.recaf.info.JvmClassInfo;
import software.coley.recaf.info.builder.JvmClassInfoBuilder;
Expand All @@ -33,9 +32,6 @@
import software.coley.recaf.services.decompile.JvmDecompiler;
import software.coley.recaf.services.info.association.FileTypeSyntaxAssociationService;
import software.coley.recaf.services.navigation.Actions;
import software.coley.recaf.services.phantom.GeneratedPhantomWorkspaceResource;
import software.coley.recaf.services.phantom.PhantomGenerationException;
import software.coley.recaf.services.phantom.PhantomGenerator;
import software.coley.recaf.services.source.AstResolveResult;
import software.coley.recaf.services.source.AstService;
import software.coley.recaf.ui.config.KeybindingConfig;
Expand All @@ -58,10 +54,7 @@
import software.coley.recaf.workspace.model.Workspace;
import software.coley.recaf.workspace.model.bundle.Bundle;
import software.coley.recaf.workspace.model.bundle.JvmClassBundle;
import software.coley.recaf.workspace.model.resource.WorkspaceResource;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand All @@ -85,8 +78,6 @@ public class JvmDecompilerPane extends AbstractDecompilePane {
private final ObservableInteger javacDownsampleTarget;
private final ObservableBoolean javacDebug;
private final ModalPaneComponent overlayModal = new ModalPaneComponent();
private final PhantomGenerator phantomGenerator;
private final JavacCompilerConfig javacConfig;
private final JavacCompiler javac;

@Inject
Expand All @@ -100,14 +91,11 @@ public JvmDecompilerPane(@Nonnull DecompilerPaneConfig config,
@Nonnull DecompilerManager decompilerManager,
@Nonnull JavacCompiler javac,
@Nonnull JavacCompilerConfig javacConfig,
@Nonnull PhantomGenerator phantomGenerator,
@Nonnull Actions actions) {
super(config, searchBar, astService, contextActionSupport, languageAssociation, decompilerManager);
this.phantomGenerator = phantomGenerator;
this.javacDebug = new ObservableBoolean(javacConfig.getDefaultEmitDebug().getValue());
this.javacTarget = new ObservableInteger(javacConfig.getDefaultTargetVersion().getValue());
this.javacDownsampleTarget = new ObservableInteger(javacConfig.getDefaultDownsampleTargetVersion().getValue());
this.javacConfig = javacConfig;
this.javac = javac;

// Install tools container with configurator
Expand Down Expand Up @@ -154,37 +142,6 @@ private void save() {
// Invoke compiler with data.
String infoName = info.getName();
CompletableFuture.supplyAsync(() -> {
// Generate phantoms for missing references in this class, if enabled and only if
// there are no generated phantom resources already in the workspace.
// This should be time-capped by 'completeOnTimeout' to prevent lock-ups.
List<WorkspaceResource> phantomResources;
if (javacConfig.getGeneratePhantoms().getValue() && workspace.getSupportingResources().stream()
.noneMatch(resource -> resource instanceof GeneratedPhantomWorkspaceResource)) {
ClassInfo currentInfo = path.getValue();
Collection<JvmClassInfo> classesToScan;
if (currentInfo.getInnerClasses().isEmpty()) {
classesToScan = Collections.singleton(currentInfo.asJvmClass());
} else {
classesToScan = workspace.findJvmClasses(c -> c.getName().startsWith(currentInfo.getName())).stream()
.map(p -> p.getValue().asJvmClass())
.collect(Collectors.toList());
}
try {
WorkspaceResource resource = phantomGenerator.createPhantomsForClasses(workspace, classesToScan);
phantomResources = Collections.singletonList(resource);
int generatedCount = resource.getJvmClassBundle().size();
if (generatedCount > 0)
logger.debug("Generated {} phantoms for pre-compile", generatedCount);
} catch (PhantomGenerationException ex) {
logger.warn("Failed to generate phantoms for compilation against '{}'", currentInfo.getName(), ex);
phantomResources = null;
}
} else {
phantomResources = null;
}
return phantomResources;
}, compilePool).completeOnTimeout(null, 2, TimeUnit.SECONDS).thenApplyAsync(phantomResources -> {
// Populate javac args
boolean debug = javacDebug.getValue();
JavacArgumentsBuilder builder = new JavacArgumentsBuilder()
.withVersionTarget(useConfiguredVersion(info))
Expand All @@ -194,10 +151,8 @@ private void save() {
.withDebugLineNumbers(debug)
.withClassSource(editor.getText())
.withClassName(infoName);

// Run javac with args + phantoms
return javac.compile(builder.build(), workspace, phantomResources, null);
}, compilePool).whenCompleteAsync((result, throwable) -> {
return javac.compile(builder.build(), workspace, null);
}, compilePool).completeOnTimeout(null, 2, TimeUnit.SECONDS).whenCompleteAsync((result, throwable) -> {
// Handle results.
// - Success --> Update content in the containing bundle
// - Failure --> Show error + diagnostics to user
Expand Down

0 comments on commit 8998c1d

Please sign in to comment.