我正在尝试使用openHFT/java-runtime-compiler来改进我的变异测试工具,从大量使用磁盘访问到仅使用内存中编译。
在变异测试中,有两种类: A. 变异类,它的定义将不断被操纵/改变和重新编译的类。 B. 其他类,定义不会改变的类,即测试用例类,或者变异类需要的其他类。
通过使用 openHFT/java-runtime-compiler,可以使用下面的代码轻松完成,它是通过为变异类和其他类的每次重新编译创建一个新的类加载器。
String BSourceCode = loadFromFiles(); //class definition loaded
for( someIterationCondition ){
String ASourceCode = mutation(); //some operation of generation/manipulation/mutation of ASourceCode
ClassLoader classLoader = new ClassLoader() { };
Class AClass = CompilerUtils.CACHED_COMPILER.loadFromJava( classLoader, "A" , ASourceCode);
Class BClass = CompilerUtils.CACHED_COMPILER.loadFromJava( classLoader, "B" , BSourceCode);
}
这很好用,每次编译 A 类的新定义时,AClass都会适应新定义。
但是,如果顺序颠倒,这将不起作用,就像下面的代码(首先加载BClass然后AClass),有时需要,比如当AClass使用BClass时。类 A 的重新编译,不会适应新的定义,并且将始终使用用于编译类 A 的第一个定义。
String BSourceCode = loadFromFiles(); //class definition loaded
for( someIterationCondition ){
String ASourceCode = mutation(); //some operation of generation/manipulation/mutation of ASourceCode
ClassLoader classLoader = new ClassLoader() { };
Class BClass = CompilerUtils.CACHED_COMPILER.loadFromJava( classLoader, "B" , BSourceCode);
Class AClass = CompilerUtils.CACHED_COMPILER.loadFromJava( classLoader, "A" , ASourceCode);
}
我怀疑我需要修改openHFT/java-runtime-compiler 库中的loadFromJava类(代码如下)。我已经尝试省略这些行
//if (clazz != null)
//return clazz;
我希望每次调用loadFromJava时总是重新编译所有源代码(甚至是已经编译的源代码) 。但它给出了错误的结果。
请帮助我指出使其生效所需的更改。
public Class loadFromJava(@NotNull ClassLoader classLoader,
@NotNull String className,
@NotNull String javaCode,
@Nullable PrintWriter writer) throws ClassNotFoundException {
Class clazz = null;
Map<String, Class> loadedClasses;
synchronized (loadedClassesMap) {
loadedClasses = loadedClassesMap.get(classLoader);
if (loadedClasses == null){
loadedClassesMap.put(classLoader, loadedClasses = new LinkedHashMap<String, Class>());
}else{
clazz = loadedClasses.get(className);
}
}
PrintWriter printWriter = (writer == null ? DEFAULT_WRITER : writer);
if (clazz != null)
return clazz;
for (Map.Entry<String, byte[]> entry : compileFromJava(className, javaCode, printWriter).entrySet()) {
String className2 = entry.getKey();
synchronized (loadedClassesMap) {
if (loadedClasses.containsKey(className2))
continue;
}
byte[] bytes = entry.getValue();
if (classDir != null) {
String filename = className2.replaceAll("\\.", '\\' + File.separator) + ".class";
boolean changed = writeBytes(new File(classDir, filename), bytes);
if (changed) {
LOG.info("Updated {} in {}", className2, classDir);
}
}
Class clazz2 = CompilerUtils.defineClass(classLoader, className2, bytes);
synchronized (loadedClassesMap) {
loadedClasses.put(className2, clazz2);
}
}
synchronized (loadedClassesMap) {
loadedClasses.put(className, clazz = classLoader.loadClass(className));
}
return clazz;
}
非常感谢您的帮助。
已编辑
谢谢彼得劳里,我已经尝试过你的建议,但它给出了相同的结果,A 类坚持使用的第一个定义(在第一次迭代中),并且无法更改/使用新定义(在下一次迭代中) .
我收集了症状,可能的解释是第一次迭代(第一次编译/加载类)与下一次迭代有一些不同的处理。从那里我尝试了几件事。
第一个症状
那是当我在 loadFromJava 中放置一个输出行(System.out.println)(下)
Class clazz = null;
Map<String, Class> loadedClasses;
synchronized (loadedClassesMap) {
loadedClasses = loadedClassesMap.get(classLoader);
if (loadedClasses == null){
loadedClassesMap.put(classLoader, loadedClasses = new LinkedHashMap<String, Class>());
System.out.println("loadedClasses Null "+className);
}else{
clazz = loadedClasses.get(className);
if(clazz == null)
System.out.println("clazz Null "+className);
else
System.out.println("clazz not Null "+className);
}
}
输出给出:
1st Iteration (new ClassLoader and new CachedCompiler)
(when loading B):loadedClasses Null
(when loading A):clazz Null
next Iteration (new ClassLoader and new CachedCompiler)
(when loading B):loadedClasses Null
(when loading A):clazz Not Null
在第一次迭代中,它给出了正确的输出,“loadClasses Null”(加载 B 时),因为 loadedClassesMap 没有 classLoader,并给出“clazz Null”(加载 A 时),因为 loadedClassesMap 有 classLoader 但没有t 具有 A 类名。
但是在下一次迭代中,(加载 A 时)它输出“clazz Not Null”,似乎 A 类名已经存储在 loadedClassesMap.get(classLoader) 中,这是不应该发生的。我试图在 CachedCompiler 构造函数中清除加载的ClassesMap。
loadedClassesMap.clear();
但它给出了 LinkageError: loader (instance of main/Utama$2): 尝试重复的类定义。
第二个症状
第一次迭代中更强烈的差异症状是当我检查 s_fileManager 缓冲区时。
1st Iteration (new ClassLoader and new CachedCompiler)
(when load B):CompilerUtils.s_fileManager.getAllBuffers().size()=1
(when load A):CompilerUtils.s_fileManager.getAllBuffers().size()=2
Next Iteration (new ClassLoader and new CachedCompiler)
(when load B):CompilerUtils.s_fileManager.getAllBuffers().size()=2
(when load A):CompilerUtils.s_fileManager.getAllBuffers().size()=2
第一次迭代正如预期的那样,但在下一次迭代中,s_fileManager 缓冲区似乎已经达到了 2 的大小,并且没有重置为 0。
我试图在 CachedCompiler 构造函数(如下)中清除 FileManager 缓冲区,
CompilerUtils.s_fileManager.clearBuffers();
但它给出了ExceptionInInitializerError。