компиляция и запуск пользовательского кода с JavaCompiler и ClassLoader - PullRequest
6 голосов
/ 29 января 2010

Я пишу веб-приложение для изучения Java. Используя, какие пользователи могут скомпилировать свой код на моем сервере + запустить этот код. Компилировать легко с помощью JavaCompiler:

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
    CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, prepareFile(nazwa, content));

    task.call();

    List<String> returnErrors = new ArrayList<String>();
    String tmp = new String();
    for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
        tmp = String.valueOf(diagnostic.getLineNumber());
        tmp += " msg: " + diagnostic.getMessage(null);
        returnErrors.add(tmp.replaceAll("\n", " "));
    }

Мне удается загрузить класс с кодом:

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);

    try {
        URL[] urls = {new URL("file:///root/"), new URL("file://C:\\serv\\Apache Tomcat 6.0.20\\bin\\")};
        ClassLoader cl_old = Thread.currentThread().getContextClassLoader();
        ClassLoader cl_new = new URLClassLoader(urls, cl_old);
        Class compiledClass = cl_new.loadClass(CLASS_NAME);
        Method myMethod = compiledClass.getMethod(METHOD_NAME);
        Object tmp = myMethod.invoke(null);
    } catch (Exception ex) {
        Logger.getLogger(ITaskCompile.class.getName()).log(Level.SEVERE, null, ex);
    }

Как я могу защитить свое приложение от бесконечных циклов и злых учеников;)

  1. есть ли способ запустить этот код со временем жизни?
  2. есть ли риск утечки памяти, и что я могу сделать, чтобы это исправить.
  3. это хорошее решение, или вы можете предложить что-то лучше?

ТНХ. Цим

Ответы [ 4 ]

2 голосов
/ 29 января 2010

Как я могу защитить свое приложение от бесконечных циклов и злых учеников;)

Вы не можете в одной JVM. Со злыми студентами особенно трудно иметь дело, потому что умные найдут способ подорвать ваши механизмы контроля.

1) есть ли способ запустить этот код со временем жизни?

Нет, если вы не запустите его в отдельной JVM.

2) есть ли риск утечки памяти, и что я могу сделать, чтобы это исправить.

Да, есть, и вы ничего не можете с этим поделать (кроме отдельных JVM). Фактически, это было бы проблемой, даже если бы вы могли уничтожить программы учеников, которые застряли в циклах и т. Д. Существует много способов, которыми приложение может вызвать утечку памяти / ресурсов в библиотеках классов Java ... даже после приложения Сам закончил и был GC'ed.

3) это хорошее решение, или вы можете предложить что-то лучшее?

Запустите каждое приложение ученика в отдельной JVM, которую вы запускаете со своего сервера, используя Process и друзей. Вам нужно будет написать материал для конкретной операционной системы, чтобы установить ограничения по времени выполнения и убить студенческие приложения, которые зашли в тупик. Кроме того, у вас возникают всевозможные проблемы, и вы не можете случайно снизить производительность хост-машины, запустив слишком много JVM.

Лучший ответ - предоставить каждому учащемуся настольный компьютер или виртуальную машину и позволить им делать свое дело.

1 голос
/ 31 января 2010

Мое текущее решение выглядит так:

код запуска:

<code>@RequestMapping("/student/runITask.action")
public String student_runITask(@ModelAttribute(value = "program") ProgramToCompile program, ModelMap map) {
    //1. code compile
    ITaskCompile itcompile = new ITaskCompile();
    List<String> errorList = itcompile.compileTask(program.getClassname(), program.getProgram());
    Date tmp = new Date();
    this.setPathName(program.getClassname() + tmp.hashCode());
    //2. if compiled... 
    if (errorList.size() < 1) {
        try {
            String[] cmd = {"/bin/sh", "-c", "java -Xmx16M -Xms2M -cp /root/ " + program.getClassname() + "> " + getPathName() + ".data"};
            Runtime rt = Runtime.getRuntime();
            final Process proc = rt.exec(cmd);
            Thread.sleep(1000);
            proc.destroy();
            if (proc.exitValue() > 0) {
                try {
                    killJavaProcesses();
                    map.addAttribute("comment", "Endless LOOP!");
                } catch (Exception ex1) {
                    Logger.getLogger(CompileITaskControler.class.getName()).log(Level.SEVERE, null, ex1);
                }
            } else {
                StringBuffer fileData = new StringBuffer(1000);
                BufferedReader reader = new BufferedReader(new FileReader("/root/" + getPathName() + ".data"));
                char[] buf = new char[1024];
                int numRead = 0;
                while ((numRead = reader.read(buf)) != -1) {
                    fileData.append(buf, 0, numRead);
                }
                reader.close();
                map.addAttribute("comment","Output: <br/><br/><br/><pre>"+fileData.toString()+"
"); } } catch (Exex ex) { пытаться { killJavaProcesses (); map.addAttribute ("comment", "Endless LOOP!"); } catch (Exception ex1) { Logger.getLogger (CompileITaskControler.class.getName ()). Log (Level.SEVERE, null, ex1); } } } еще { map.addAttribute ("errorList", errorList); map.addAttribute («комментарий», «ПРОГРАММА NIE ZOSTAŁ URUCHOMIONY»); } // 3. вернуть вернуть DISPLAY_COMP_MSG; }

где killJavaProcesses () выглядит так

public void killJavaProcesses() throws IOException, InterruptedException {
    String[] getProcessList = {"/bin/sh", "-c", "ps | grep java"};
    String[] killProcessByIdList = {"/bin/sh", "-c", "kill -9 "};
    Runtime rt = Runtime.getRuntime();
    Process proc = rt.exec(getProcessList);
    InputStream inputstream = proc.getInputStream();
    InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
    BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
    String line2;
    String kill = new String();
    while ((line2 = bufferedreader.readLine()) != null) {
        kill += line2 + "\n";
    }
    proc.destroy();
    String arraykill[] = kill.split("\n");
    String element2kill = "";
    String[] tmp;
    if (arraykill.length >= 1) {
        element2kill = arraykill[arraykill.length - 1].trim().split(" ")[0];
    }
    killProcessByIdList[2] += element2kill;
    Process proc2 = rt.exec(killProcessByIdList);
    proc2.waitFor();
}

Я не могу убить процесс другим. Использование proc.destroy () не работает на компьютерах с Ubuntu / Win XP. Теперь я попытаюсь настроить и использовать SecurityManager .

1 голос
/ 29 января 2010

Добавляя к ответу Калеба, обязательно запустите целевую JVM со строгим ограничением кучи (например, -Xmx16M). И, конечно, вы захотите ограничить число работающих JVM.

1 голос
/ 29 января 2010

есть ли способ запустить этот код со временем жизни?

Создать процесс, который отслеживает дочерние процессы и завершает его, если это занимает слишком много времени.

есть ли риск утечки памяти, и что я могу сделать, чтобы это исправить.

Вы должны быть в состоянии сделать это, в некоторой степени, контролируя, сколько памяти выделяется (например, параметр -Xmx для JVM от Sun).

это хорошее решение, или вы можете предложить что-то лучшее?

Я не уверен, что было предложено решение, но вот мысль. Установите SecurityManager , который сильно ограничивает возможности исполняемого кода, такие как доступ к файловой системе, процессы вызова и т. Д. Объедините это с процессом, который отслеживает время ожидания, ограничивает выделенную память, запускает приложение под отдельная учетная запись пользователя и т. д., и я думаю, что вы можете иметь что-то работоспособное.

То, что вы ищете, возможно, но не полностью, если вы ограничены только Java.

...