2016-09-27 2 views
2

Я работаю над REPL для собственного языка программирования. Он реализован поверх компилятора, который он использует для генерации байт-кода для ввода и преобразования его в экземпляр Class<?> с использованием метода sun.misc.Unsafe.defineClass(String, byte[], int, int, ClassLoader, ProtectionDomain). Соответствующий код выглядит следующим образом (не относящихся к делу деталей, таких как обработка исключений опущенных):Определите несколько классов во время выполнения с помощью Unsafe.defineClass

void compileAndLoad(List<ICompilable> compilables) 
{ 
    List<Class<?>> classes = ...; 
    for (ICompilable c : compilables) 
    { 
     classes.add(compile(compilable)); 
    } 
    for (Class<?> c : classes) 
    { 
     UNSAFE.ensureClassInitialized(c); 
    } 
} 

// CLASS_LOADER = Enclosing.class.getClassLoader() 
// PROTECTION_DOMAIN = Enclosing.class.getClassLoader() 

Class<?> compile(ICompilable compilable) 
{ 
    byte[] bytecode = genBytecode(compilable); 
    String name = compilable.getFullName() // e.g. 'foo.bar.Baz' 
    return UNSAFE.defineClass(name, bytes, 0, bytes.length, CLASS_LOADER, PROTECTION_DOMAIN); 
} 

Say вход требует несколько классов, чтобы быть скомпилированы и загружены.

> class A { interface B { }; func b() = new B { /* anonymous class */ } } 

compilables список имеет содержание

[ repl.Result_0, repl.Result_0$A, repl.Result_0$A$0, repl.Result_0$A$B ] 

repl.Result_0$A класс зависит от (анонимной) класса repl.Result_0$A$0 и repl.Result_0$B класса и ссылки их имена в байткод. При определении его с помощью Unsafe, произойдет следующее сообщение об ошибке:

java.lang.NoClassDefFoundError: repl/Result_0$A$B 
    at sun.misc.Unsafe.defineClass(Native Method) 
    at MyClass.compile(MyClass.java:42) 
    // ... snip 
Caused by: java.lang.ClassNotFoundException: repl.Result_0$A$B 
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381) 
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424) 
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) 
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357) 
    ... 9 more 

Я знаю, что это может быть решена путем изменения порядка списка и определения repl.Result_0$A$B первый, но это не будет общим решением, так как там могут быть ссылки с B -> A как Что ж.

Есть ли способ определить и загрузить несколько классов с использованием Unsafe.defineClass, не вызывая ошибок проверки для неразрешенных классов?

+1

Вы пытались использовать компилятор Java API, вместо Unsafe? Вы можете создать собственную реализацию JavaFileObject, которая предоставляет байт-код, а затем использовать собственный ClassLoader для загрузки классов с помощью FileObjects ... Я не знаю достаточно вашего компилятора, чтобы узнать, будет ли это работать, но я использую этот подход для компилировать классы во время выполнения, и я никогда не сталкивался с этой проблемой. – Renato

+0

Мне нравится парень с таким интересом;) Во всяком случае, Ренато прав, используйте некоторую библиотеку, чтобы правильно сформировать класс, а затем используйте обычный загрузчик классов для их загрузки. –

+0

Почему вы используете Unsafe вместо ClassLoader.defineClass? –

ответ

2

Ваша проблема не относится к Unsafe.defineClass, но относится к программной логике. Всякий раз, когда вы «нажимаете« »несколько новых классов, независимо от того, используете ли вы ClassLoader.defineClass или Unsafe.defineClass, вам необходимо избегать ссылок на прямые ссылки, что исключает наличие циклов в ваших зависимостях классов.

Для конкретных предполагаемых случаев использования Unsafe.defineClass, например. рефлексивные аксессоры, существует четкое направление зависимостей и, следовательно, нет проблем, но для вашего случая использования это неправильный инструмент. Вы должны определить загрузчик классов, который позволяет JVM «pull» «классы при необходимости, например.

void compileAndLoad(List<ICompilable> compilables) { 
    Map<String,byte[]> compiled = new HashMap<>(compilables.size()); 
    for(ICompilable c: compilables) 
     compiled.put(c.getFullName(), genBytecode(c)); 
    ClassLoader l = new ClassLoader(CLASS_LOADER) { 
     @Override 
     protected Class<?> findClass(String name) throws ClassNotFoundException { 
      byte[] code = compiled.get(name); 
      if(code == null) throw new ClassNotFoundException(name); 
      return defineClass(name, code, 0, code.length); 
     } 
    }; 
    // the code below this line is questionable; it seems you are relying 
    // on the side effects of a class initializer 
    for(String name: compiled.keySet()) try { 
     Class.forName(name, true, l); 
    } catch (ClassNotFoundException ex) { throw new AssertionError(ex); } 
} 

Обратите внимание, что код использует Class.forName вместо loadClass для обеспечения инициализации в исходный код делает. Обычно код не должен полагаться на немедленную инициализацию, но вы не используете загруженные классы для чего-либо еще, поэтому неясно, с чем заменить. Обычной процедурой было бы использовать loadClass для класса, предназначенного для последующего использования, и вернуть его; инициализация (и загрузка и инициализация зависимостей) произойдет при ее фактическом использовании.

Далее отметим, что весь код работает без использования Unsafe ...

Смежные вопросы