Skip to content

Commit

Permalink
Add a JRT filesystem and add the current JDK to the classpath using it.
Browse files Browse the repository at this point in the history
  • Loading branch information
shartte committed Dec 20, 2023
1 parent 1af203b commit 3a9de49
Show file tree
Hide file tree
Showing 6 changed files with 457 additions and 25 deletions.
53 changes: 32 additions & 21 deletions src/main/java/ApplyParchmentToSourceJar.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import com.intellij.core.CoreApplicationEnvironment;
import com.intellij.core.CoreJavaFileManager;
import com.intellij.core.JavaCoreApplicationEnvironment;
import com.intellij.core.JavaCoreProjectEnvironment;
import com.intellij.lang.jvm.facade.JvmElementProvider;
Expand All @@ -12,6 +11,7 @@
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileSystem;
import com.intellij.openapi.vfs.impl.ZipHandler;
import com.intellij.pom.java.InternalPersistentJavaLanguageLevelReaderService;
import com.intellij.pom.java.LanguageLevel;
Expand All @@ -23,17 +23,19 @@
import com.intellij.psi.impl.PsiElementFinderImpl;
import com.intellij.psi.impl.PsiNameHelperImpl;
import com.intellij.psi.impl.PsiTreeChangePreprocessor;
import com.intellij.psi.impl.file.impl.JavaFileManager;
import com.intellij.psi.impl.source.tree.JavaTreeGenerator;
import com.intellij.psi.impl.source.tree.TreeGenerator;
import com.intellij.psi.util.JavaClassSupers;
import modules.CoreJrtFileSystem;
import namesanddocs.NameAndDocSourceLoader;
import namesanddocs.NamesAndDocsDatabase;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -58,20 +60,28 @@ public class ApplyParchmentToSourceJar implements AutoCloseable {
private boolean enableJavadoc = true;
private final Disposable rootDisposable;

public ApplyParchmentToSourceJar(NamesAndDocsDatabase namesAndDocs) throws IOException {
public ApplyParchmentToSourceJar(Path javaHome, NamesAndDocsDatabase namesAndDocs) throws IOException {
this.namesAndDocs = namesAndDocs;
tempDir = Files.createTempDirectory("applyparchment");
this.rootDisposable = Disposer.newDisposable();
System.setProperty("idea.home.path", tempDir.toAbsolutePath().toString());

// IDEA requires a config directory, even if it's empty
PathManager.setExplicitConfigPath(tempDir.toAbsolutePath().toString());
Registry.markAsLoaded(); // Avoids warnings about config not being loaded

var appEnv = new JavaCoreApplicationEnvironment(rootDisposable);
var appEnv = new JavaCoreApplicationEnvironment(rootDisposable) {
@Override
protected VirtualFileSystem createJrtFileSystem() {
return new CoreJrtFileSystem();
}
};
initAppExtensionsAndServices(appEnv);

javaEnv = new JavaCoreProjectEnvironment(rootDisposable, appEnv);

ClasspathSetup.addJdkModules(javaHome, javaEnv);

project = javaEnv.getProject();

initProjectExtensionsAndServices(project);
Expand All @@ -81,10 +91,11 @@ public ApplyParchmentToSourceJar(NamesAndDocsDatabase namesAndDocs) throws IOExc
psiManager = PsiManager.getInstance(project);
}


public static void main(String[] args) throws Exception {
System.setProperty("java.awt.headless", "true");

Path inputPath = null, outputPath = null, namesAndDocsPath = null;
Path inputPath = null, outputPath = null, namesAndDocsPath = null, librariesPath = null;
boolean enableJavadoc = true;
int queueDepth = 50;

Expand All @@ -105,6 +116,13 @@ public static void main(String[] args) throws Exception {
}
outputPath = Paths.get(args[++i]);
break;
case "--libraries":
if (i + 1 >= args.length) {
System.err.println("Missing argument for --libraries");
System.exit(1);
}
librariesPath = Paths.get(args[++i]);
break;
case "--names":
if (i + 1 >= args.length) {
System.err.println("Missing argument for --names");
Expand Down Expand Up @@ -142,7 +160,15 @@ public static void main(String[] args) throws Exception {

var namesAndDocs = NameAndDocSourceLoader.load(namesAndDocsPath);

try (var applyParchment = new ApplyParchmentToSourceJar(namesAndDocs)) {
// Add the Java Runtime we are currently running in
var javaHome = Paths.get(System.getProperty("java.home"));

try (var applyParchment = new ApplyParchmentToSourceJar(javaHome, namesAndDocs)) {
// Add external libraries to classpath
if (librariesPath != null) {
ClasspathSetup.addLibraries(librariesPath, applyParchment.javaEnv);
}

applyParchment.setMaxQueueDepth(queueDepth);
applyParchment.setEnableJavadoc(enableJavadoc);
applyParchment.apply(inputPath, outputPath);
Expand All @@ -169,21 +195,6 @@ public void apply(Path inputPath, Path outputPath) throws IOException, Interrupt

javaEnv.addSourcesToClasspath(sourceJarRoot);

var javaFileManager = (CoreJavaFileManager) JavaFileManager.getInstance(project);
javaFileManager.addToClasspath(sourceJarRoot);

// Files.readAllLines(Paths.get(librariesPath))
// .stream()
// .filter(l -> l.startsWith("-e="))
// .map(l -> l.substring(3))
// .map(File::new)
// .forEach(file -> {
// if (!file.exists()) {
// throw new UncheckedIOException(new FileNotFoundException(file.getAbsolutePath()));
// }
// javaEnv.addJarToClassPath(file);
// });

try (var zin = new ZipInputStream(Files.newInputStream(inputPath));
var fout = Files.newOutputStream(outputPath);
var asyncZout = new OrderedWorkQueue(new ZipOutputStream(fout), maxQueueDepth)) {
Expand Down
95 changes: 95 additions & 0 deletions src/main/java/ClasspathSetup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import com.intellij.core.JavaCoreProjectEnvironment;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.io.URLUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Properties;

public final class ClasspathSetup {
private ClasspathSetup() {
}

public static void addJdkModules(Path jdkHome, JavaCoreProjectEnvironment javaEnv) {
var jrtFileSystem = javaEnv.getEnvironment().getJrtFileSystem();

VirtualFile jdkVfsRoot = jrtFileSystem.findFileByPath(jdkHome.toAbsolutePath() + URLUtil.JAR_SEPARATOR);
if (jdkVfsRoot == null) {
System.err.println("Failed to load VFS-entry for JDK home " + jdkHome + ". Is it missing?");
return;
}

var modulesFolder = jdkVfsRoot.findChild("modules");
if (modulesFolder == null) {
System.err.println("VFS for JDK " + jdkHome + " doesn't have a modules subfolder");
return;
}

int moduleCount = 0;
List<String> modules = readModulesFromReleaseFile(jdkHome);
if (modules != null) {
for (String module : modules) {
var moduleRoot = modulesFolder.findChild(module);
if (moduleRoot == null || !moduleRoot.isDirectory()) {
System.err.println("Couldn't find module " + module + " even though it was listed in the release file of JDK " + jdkHome);
} else {
javaEnv.addSourcesToClasspath(moduleRoot);
moduleCount++;
}
}
} else {

for (VirtualFile jrtChild : modulesFolder.getChildren()) {
if (jrtChild.isDirectory()) {
javaEnv.addSourcesToClasspath(jrtChild);
moduleCount++;
}
}
}

System.out.println("Added " + moduleCount + " modules from " + jdkHome);
}

public static void addLibraries(Path librariesPath, JavaCoreProjectEnvironment javaEnv) throws IOException {
var libraryFiles = Files.readAllLines(librariesPath)
.stream()
.filter(l -> l.startsWith("-e="))
.map(l -> l.substring(3))
.map(File::new)
.toList();

for (var libraryFile : libraryFiles) {
if (!libraryFile.exists()) {
throw new UncheckedIOException(new FileNotFoundException(libraryFile.getAbsolutePath()));
}
javaEnv.addJarToClassPath(libraryFile);
System.out.println("Added " + libraryFile);
}
}

/**
* Reads the "release" file found at the root of normal JDKs
*/
private static @Nullable List<String> readModulesFromReleaseFile(@NotNull Path jrtBaseDir) {
try (InputStream stream = Files.newInputStream(jrtBaseDir.resolve("release"))) {
Properties p = new Properties();
p.load(stream);
String modules = p.getProperty("MODULES");
if (modules != null) {
return StringUtil.split(StringUtil.unquoteString(modules), " ");
}
} catch (IOException | IllegalArgumentException e) {
return null;
}
return null;
}
}
60 changes: 57 additions & 3 deletions src/main/java/GatherReplacementsVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiJavaDocumentedElement;
import com.intellij.psi.PsiLambdaExpression;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiRecursiveElementVisitor;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypes;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.SyntaxTraverser;
import com.intellij.psi.search.GlobalSearchScope;
Expand Down Expand Up @@ -97,8 +101,13 @@ public void visitElement(@NotNull PsiElement element) {
if (psiParameter.getNameIdentifier() == null) {
continue;
}
var paramData = methodData.getParameter(i);
if (paramData != null) {

// Parchment stores parameter indices based on the index of the parameter in the actual compiled method
// to account for synthetic parameter not found in the source-code, we must adjust the index accordingly.
var jvmIndex = getJvmIndex(psiParameter, i);

var paramData = methodData.getParameter(jvmIndex);
if (paramData != null && paramData.getName() != null) {
// Replace parameters within the method body
activeParameters.put(psiParameter, paramData);

Expand Down Expand Up @@ -136,6 +145,28 @@ public void visitElement(@NotNull PsiElement element) {
element.acceptChildren(this);
}

private int getJvmIndex(PsiParameter psiParameter, int index) {
var declarationScope = psiParameter.getDeclarationScope();
if (declarationScope instanceof PsiMethod psiMethod) {

// Try to account for hidden parameters only present in bytecode since the
// mapping data refers to parameters using those indices
if (psiMethod.isConstructor() && psiMethod.getContainingClass() != null && psiMethod.getContainingClass().isEnum()) {
index += 2;
} else if (psiMethod.getContainingClass() != null && psiMethod.getContainingClass() != null
&& !psiMethod.getContainingClass().hasModifierProperty(PsiModifier.STATIC)) {
index += 1;
}

return index;
} else if (declarationScope instanceof PsiLambdaExpression psiLambda) {
// Naming lambdas doesn't really work
return index;
} else {
return -1;
}
}

private void applyJavadoc(PsiJavaDocumentedElement method, List<String> javadoc, List<Replacement> replacements) {
if (!enableJavadoc) {
return;
Expand Down Expand Up @@ -224,7 +255,7 @@ private NamesAndDocsForMethod getMethodData(@Nullable PsiMethod psiMethod) {
var classData = getClassData(psiMethod.getContainingClass());
if (classData != null) {
var methodName = psiMethod.getName();
var methodSignature = ClassUtil.getAsmMethodSignature(psiMethod);
var methodSignature = getBinaryMethodSignature(psiMethod);
methodData = Optional.ofNullable(classData.getMethod(methodName, methodSignature));
}

Expand All @@ -233,6 +264,29 @@ private NamesAndDocsForMethod getMethodData(@Nullable PsiMethod psiMethod) {
}
}

public static String getBinaryMethodSignature(PsiMethod method) {
StringBuilder signature = new StringBuilder();
signature.append("(");
for (PsiParameter param : method.getParameterList().getParameters()) {
var binaryPresentation = ClassUtil.getBinaryPresentation(param.getType());
if (binaryPresentation.isEmpty()) {
System.err.println("Failed to create binary representation for type " + param.getType().getCanonicalText());
binaryPresentation = "ERROR";
}
signature.append(binaryPresentation);
}
signature.append(")");
var returnType = Optional.ofNullable(method.getReturnType()).orElse(PsiTypes.voidType());
var returnTypeRepresentation = ClassUtil.getBinaryPresentation(returnType);
if (returnTypeRepresentation.isEmpty()) {
System.err.println("Failed to create binary representation for type " + returnType.getCanonicalText());
returnTypeRepresentation = "ERROR";
}
signature.append(returnTypeRepresentation);
return signature.toString();
}


/**
* An adapted version of {@link ClassUtil#formatClassName(PsiClass, StringBuilder)} where Inner-Classes
* use a $ separator while formatClassName separates InnerClasses with periods from their parent.
Expand Down
Loading

0 comments on commit 3a9de49

Please sign in to comment.