2

我正在尝试使用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。

4

1 回答 1

0

如果您想使用一组新的类,我建议不要使用相同的类缓存。

String BSourceCode = loadFromFiles(); //class definition loaded
for( someIterationCondition ){
   String ASourceCode = mutation();  //some operation of generation/manipulation/mutation of ASourceCode
   ClassLoader classLoader = new ClassLoader() { }; 
   CachedCompiler compiler = new CachedCompiler(null, null)
   Class AClass = compiler.loadFromJava( classLoader, "A" , ASourceCode);
   Class BClass = compiler.loadFromJava( classLoader, "B" , BSourceCode);
}

这将每次使用一个新的缓存,并且不受之前测试中加载的类的影响。

于 2016-12-29T14:41:59.693 回答