Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions src/main/java/net/openhft/compiler/CachedCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import org.slf4j.LoggerFactory;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import java.io.*;
Expand Down Expand Up @@ -172,6 +171,15 @@
@NotNull String javaCode,
final @NotNull PrintWriter writer,
MyJavaFileManager fileManager) {
return compileFromJava(className, javaCode, writer, fileManager, null);
}

@NotNull
private Map<String, byte[]> compileFromJava(@NotNull String className,
@NotNull String javaCode,
final @NotNull PrintWriter writer,
MyJavaFileManager fileManager,
@Nullable StringBuilder diagnostics) {
validateClassName(className);
Iterable<? extends JavaFileObject> compilationUnits;
if (sourceDir != null) {
Expand All @@ -187,11 +195,12 @@
compilationUnits = new ArrayList<>(javaFileObjects.values()); // To prevent CME from compiler code
}
// reuse the same file manager to allow caching of jar files
boolean ok = s_compiler.getTask(writer, fileManager, new DiagnosticListener<JavaFileObject>() {
@Override
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
writer.println(diagnostic);
boolean ok = s_compiler.getTask(writer, fileManager, diagnostic -> {
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
String message = diagnostic.toString();
writer.println(message);
if (diagnostics != null) {
diagnostics.append(message).append(System.lineSeparator());
}
}
}, options, null, compilationUnits).call();
Expand Down Expand Up @@ -222,7 +231,7 @@
* @return the loaded class instance
* @throws ClassNotFoundException if definition fails
*/
public Class<?> loadFromJava(@NotNull ClassLoader classLoader,

Check failure on line 234 in src/main/java/net/openhft/compiler/CachedCompiler.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 16 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=OpenHFT_Java-Runtime-Compiler&issues=AZ6IJukFWgmgQXRRyEnP&open=AZ6IJukFWgmgQXRRyEnP&pullRequest=180
@NotNull String className,
@NotNull String javaCode,
@Nullable PrintWriter writer) throws ClassNotFoundException {
Expand All @@ -245,7 +254,11 @@
fileManager = getFileManager(standardJavaFileManager);
fileManagerMap.put(classLoader, fileManager);
}
final Map<String, byte[]> compiled = compileFromJava(className, javaCode, printWriter, fileManager);
StringBuilder diagnostics = new StringBuilder();
final Map<String, byte[]> compiled = compileFromJava(className, javaCode, printWriter, fileManager, diagnostics);
if (!compiled.containsKey(className)) {
throw missingCompiledClassException(className, compiled.keySet(), diagnostics.toString());
}
for (Map.Entry<String, byte[]> entry : compiled.entrySet()) {
String className2 = entry.getKey();
validateClassName(className2);
Expand Down Expand Up @@ -327,6 +340,21 @@
return candidate.toFile();
}

private static ClassNotFoundException missingCompiledClassException(String className,
Set<String> compiledClassNames,
String diagnostics) {
String diagnosticText = diagnostics.trim();
String message;
if (!diagnosticText.isEmpty()) {
message = "Compilation failed for " + className
+ System.lineSeparator() + diagnosticText;
} else {
message = "Compilation did not produce requested class " + className
+ ". Compiled classes: " + compiledClassNames;
}
return new ClassNotFoundException(message, new IllegalStateException(message));
}

private static PrintWriter createDefaultWriter() {
OutputStreamWriter writer = new OutputStreamWriter(System.err, StandardCharsets.UTF_8);
return new PrintWriter(writer, true) {
Expand Down
Comment thread
sam-ross marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
package net.openhft.compiler;

import org.junit.Test;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;

import static org.junit.Assert.*;

public class CachedCompilerModuleClassLoaderDiagnosticsTest {

@Test
public void moduleLikeLoaderCanLoadClassAfterSuccessfulDefineClass() throws Exception {
ModuleLikeClassLoader loader = new ModuleLikeClassLoader();
CachedCompiler compiler = new CachedCompiler(null, null);

Class<?> clazz = compiler.loadFromJava(loader,
"app.Generated",
"package app; public class Generated { public int value() { return 42; } }");

assertEquals("app.Generated", clazz.getName());
assertSame(clazz, loader.loadClass("app.Generated"));
assertEquals(42, clazz.getDeclaredMethod("value").invoke(clazz.getDeclaredConstructor().newInstance()));
}

@Test
public void compileFailureForLoaderOnlyDependencyReportsJavacDiagnostics() throws Exception {
ModuleLikeClassLoader loader = new ModuleLikeClassLoader();
defineLoaderOnlyDependency(loader);

assertSame("Sanity check: the supplied class loader can see the dependency",
loader.loadClass("app.Dto"),
Class.forName("app.Dto", false, loader));

CachedCompiler compiler = new CachedCompiler(null, null);
StringWriter diagnostics = new StringWriter();

ClassNotFoundException thrown = assertThrows(ClassNotFoundException.class,
() -> compiler.loadFromJava(loader,
"app.GeneratedUsesDto",
"package app; public class GeneratedUsesDto { app.Dto dto; }",
new PrintWriter(diagnostics)));

assertTrue("Thrown exception should identify compilation failure: " + thrown.getMessage(),
thrown.getMessage().contains("Compilation failed for app.GeneratedUsesDto"));
assertTrue("Thrown exception should include javac missing-symbol diagnostics: " + thrown.getMessage(),
thrown.getMessage().contains("cannot find symbol"));
assertTrue("Thrown exception should include the dependency javac could not resolve: " + thrown.getMessage(),
thrown.getMessage().contains("Dto"));
assertNotNull("Thrown exception should carry a diagnostic cause", thrown.getCause());
assertTrue("javac diagnostics should mention the dependency that the compiler could not resolve: "
+ diagnostics,
diagnostics.toString().contains("Dto"));
assertNull("The generated class should not have been defined after javac failure",
loader.findLoaded("app.GeneratedUsesDto"));
}

@Test
public void successfulCompileWithoutRequestedClassReportsMissingOutput() {
ModuleLikeClassLoader loader = new ModuleLikeClassLoader();
CachedCompiler compiler = new CachedCompiler(null, null);

ClassNotFoundException thrown = assertThrows(ClassNotFoundException.class,
() -> compiler.loadFromJava(loader,
"app.Expected",
"package app; class Different {}"));

assertTrue("Thrown exception should identify the missing requested class: " + thrown.getMessage(),
thrown.getMessage().contains("Compilation did not produce requested class app.Expected"));
assertTrue("Thrown exception should identify the class javac actually produced: " + thrown.getMessage(),
thrown.getMessage().contains("app.Different"));
assertNotNull("Thrown exception should carry a diagnostic cause", thrown.getCause());
assertNull("The requested class should not have been defined",
loader.findLoaded("app.Expected"));
}

private static void defineLoaderOnlyDependency(ModuleLikeClassLoader loader) throws Exception {
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
assertNotNull("System compiler required", javac);

try (StandardJavaFileManager standardManager = javac.getStandardFileManager(null, null, null)) {
CachedCompiler bytecodeCompiler = new CachedCompiler(null, null);
MyJavaFileManager fileManager = new MyJavaFileManager(standardManager);
Map<String, byte[]> classes = bytecodeCompiler.compileFromJava(
"app.Dto",
"package app; public class Dto {}",
fileManager);
byte[] dtoBytes = classes.get("app.Dto");
assertNotNull(dtoBytes);
CompilerUtils.defineClass(loader, "app.Dto", dtoBytes);
}
}

private static final class ModuleLikeClassLoader extends ClassLoader {
private static final String APP_PREFIX = "app.";

ModuleLikeClassLoader() {
super(CachedCompilerModuleClassLoaderDiagnosticsTest.class.getClassLoader());
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (!name.startsWith(APP_PREFIX)) {
return super.loadClass(name, resolve);
}
return findClass(name, resolve);
}

private Class<?> findClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> loaded = findLoadedClass(name);
if (loaded != null) {
if (resolve) {
resolveClass(loaded);
}
return loaded;
}
throw new ClassNotFoundException(name + " from [Module \"deployment.repro.war\" from Service Module Loader]");
}

Class<?> findLoaded(String name) {
return findLoadedClass(name);
}
}
}