Инструменты для поиска общих ошибок данных в Java - PullRequest
14 голосов
/ 09 октября 2008

У меня есть большая устаревшая система для обслуживания. Кодовая база использует потоки повсюду, и эти потоки совместно используют много изменяемых данных. Я знаю, звучит плохо. Во всяком случае, не отвечайте «переписать все приложение с нуля», или я вам проголосую :-) Я пытался запустить некоторые инструменты статического анализа на базе кода, но ни один из них, похоже, не улавливает этот случай, который часто происходит в нашем исходном коде: несколько потоков читают и записывают переменные, которые не помечены как энергозависимые или вообще не синхронизированы. Обычно это происходит с переменными типа runFlag. Пример этого - на странице Effective Java 2nd edition 260:

public class StopThread
{
    private static boolean stopRequested;
    public static void main(String[] args) throws InterruptedException
    {
        Thread backgroundThread = new Thread(new Runnable()
        {
            public void run()
            {
                int i = 0;
                while (!stopRequested)
                {
                    i++;
                }
            }
        });
        backgroundThread.start();
        Thread.sleep(1000);
        stopRequested = true;
    }
}

Этот пример никогда не заканчивается в Windows / Linux с параметром запуска "-server", заданным Sun JVM. Итак, есть ли (полу) автоматический способ найти эти проблемы, или я должен полностью полагаться на проверки кода?

Ответы [ 5 ]

6 голосов
/ 09 октября 2008

Крис Гриндстафф написал статью FindBugs, часть 2: Написание пользовательских детекторов , в которой он описывает, как использовать BCEL для добавления собственных правил. (BCEL - не единственная библиотека байт-кода, но она используется FindBugs.)

Приведенный ниже код генерирует любые случаи, когда метод обращается к статическому методу или полю. Вы можете запустить его на любом типе, который реализует Runnable .

public class StaticInvocationFinder extends EmptyVisitor {

    @Override
    public void visitMethod(Method obj) {
        System.out.println("==========================");
        System.out.println("method:" + obj.getName());

        Code code = obj.getCode();
        InstructionList instructions = new InstructionList(code.getCode());
        for (Instruction instruction : instructions.getInstructions()) {
            // static field or method
            if (Constants.INVOKESTATIC == instruction.getOpcode()) {
                if (instruction instanceof InvokeInstruction) {
                    InvokeInstruction invokeInstruction = (InvokeInstruction) instruction;
                    ConstantPoolGen cpg = new ConstantPoolGen(obj
                            .getConstantPool());
                    System.out.println("static access:"
                            + invokeInstruction.getMethodName(cpg));
                    System.out.println("      on type:"
                            + invokeInstruction.getReferenceType(cpg));
                }
            }
        }
        instructions.dispose();
    }

    public static void main(String[] args) throws Exception {
        JavaClass javaClass = Repository.lookupClass("StopThread$1");

        StaticInvocationFinder visitor = new StaticInvocationFinder();
        DescendingVisitor classWalker = new DescendingVisitor(javaClass,
                visitor);
        classWalker.visit();
    }

}

Этот код выдает следующее:

==========================
method:<init>
==========================
method:run
static access:access$0
      on type:StopThread

Затем можно было бы отсканировать тип StopThread , найти поле и проверить, является ли оно volatile .

Проверка на синхронизацию возможна, но может оказаться сложной из-за нескольких условий MONITOREXIT. Ходить по стекам вызовов тоже может быть сложно, но тогда это не тривиальная проблема. Тем не менее, я думаю, что было бы относительно легко проверить шаблон ошибки, если бы он был реализован последовательно.

BCEL выглядит скудно задокументированным и очень волосатым, пока не найдете класс BCELifier . Если вы запускаете его в классе, он показывает исходный код Java о том, как построить класс в BCEL. Запуск его на StopThread дает это для генерации синтетического аксессора access $ 0 :

  private void createMethod_2() {
    InstructionList il = new InstructionList();
    MethodGen method = new MethodGen(ACC_STATIC | ACC_SYNTHETIC, Type.BOOLEAN, Type.NO_ARGS, new String[] {  }, "access$0", "StopThread", il, _cp);

    InstructionHandle ih_0 = il.append(_factory.createFieldAccess("StopThread", "stopRequested", Type.BOOLEAN, Constants.GETSTATIC));
    il.append(_factory.createReturn(Type.INT));
    method.setMaxStack();
    method.setMaxLocals();
    _cg.addMethod(method.getMethod());
    il.dispose();
  }
2 голосов
/ 18 ноября 2008

Coverity Thread Analyzer делает свою работу, но это довольно дорого. Похоже, что IBM Multi-Thread Runtime Analysis Tool для Java может их обнаружить, но его настройка несколько сложнее. Это инструменты динамического анализа, которые определяют, к каким фактическим переменным обращались из разных потоков без надлежащей синхронизации или волатильности, поэтому результаты более точны, чем при статическом анализе, и способны обнаружить множество проблем, которые статический анализ не может обнаружить.

Если ваш код в основном или хотя бы частично правильно синхронизирован, исправление проверок параллелизма FindBugs (или другого статического анализа) также может помочь, по крайней мере, правила IS2_INCONSISTENT_SYNC и UG_SYNC_SET_UNSYNC_GET могут быть хорошими для начала.

2 голосов
/ 09 октября 2008

Последняя версия FindBugs попытается проверить, что поля, помеченные аннотацией @GuardedBy, доступны только в пределах соответствующего защитного кода.

1 голос
/ 09 октября 2008

Coverity создает некоторые инструменты статического и динамического анализа, которые могут помочь.

http://www.coverity.com/

1 голос
/ 09 октября 2008

FindBugs и профессиональные инструменты на его основе - ваша лучшая надежда, но не рассчитывайте, что они найдут все проблемы параллелизма в вашем коде.

Если все в такой плохой форме, то вы должны дополнить инструмент анализом эксперта по параллельности Java.

Это сложная проблема, потому что окончательно доказывает правильность существующей, но модифицированной базы кода, вероятно, будет нереально - особенно в условиях одновременного использования.

...