Инструменты для поиска общих ошибок Mutable в Java

У меня есть большая устаревшая система для поддержки. Кодовая база использует потоки по всему месту, и эти потоки разделяют множество изменяемых данных. Я знаю, звучит плохо. Во всяком случае, не отвечайте "переписывайте все приложение с нуля" или я проголосую за вас:-) Я попытался запустить некоторые инструменты статического анализа на базе кода, но ни один из них, похоже, не поймает этот случай, который встречается много в нашем исходном коде: несколько потоков - это чтение и запись переменных, которые не помечены как изменчивые или синхронизированные вообще. Обычно это происходит в переменных типа "runFlag". Примером этого является "Эффективное Java 2-е издание", стр. 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 с параметром запуска "-сервера", данным Sun JVM. Итак, есть ли какой-либо (полуавтоматический) способ найти эти проблемы или я должен полностью полагаться на обзоры кода?

Ответы

Ответ 1

Chris Grindstaff написал статью 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, найти поле и проверить, не является ли он изменчивым.

Проверка синхронизации возможна, но может оказаться сложной из-за нескольких условий 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

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

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

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

http://www.coverity.com/