Findbugs: пользовательский детектор

Я пишу пользовательский детектор в Findbugs. Я хочу знать, есть ли способ отслеживать ASTORE и соответствующую инструкцию ALOAD? То есть, если ASTORE 3 встречается в моем байт-коде, я хочу сначала определить, что это инструкция ASTORE, а затем ее индекс (в данном случае: 3 ) и найдите инструкцию ЗАГРУЗКА с таким же индексом (в данном случае инструкция ЗАГРУЗКА 3).

Например, в байт-коде, показанном ниже, я хочу прочитать инструкцию ASTORE 8 (появляющуюся в строке № 29) и посмотреть, есть ли какая-либо инструкция ALOAD с индекс 8. То есть, ЗАГРУЗИТЬ 8 (это можно увидеть в строке № 73).

  29: astore        8
  31: aload_1       
  32: iconst_0      
  .
  .
  .
  .
  .
  .
  60: ldc           #54                 // String number
  62: aload         11
  64: invokeinterface #56,  3           // InterfaceMethod javax/servlet/http/HttpSession.setAttribute:(Ljava/lang/String;Ljava/lang/Object;)V
  69: aload         12
  71: aload         7
  73: aload         8
  75: invokeinterface #62,  3           // InterfaceMethod com/ibm/itim/ws/services/WSSessionService.getNumber:(Ljava/lang/String;Ljava/lang/String;)Lcom/ibm/itim/ws/model/WSSession;
  80: astore        14

Кроме того, если я найду соответствующую инструкцию ALOAD, я хочу проверить, какой метод вызывается. Я знаю, что это можно проверить с помощью метода sawOpcode(), как показано ниже:

    if (seen == INVOKEINTERFACE){...}

Короче говоря, я хочу сделать что-то вроде этого:

псевдокод

    public void sawOpcode(int seen) {
    if (seen == ASTORE){
        //code to identify its index i;  i.e, ASTORE i
        if(seen == ALOAD_i){
            //if the corresponding ALOAD instruction is found...
            if(seen == INVOKEINTERFACE){

                // Identify the method invoked

            }
        }

Не знаю, правильный ли описанный выше подход.


person Manoj    schedule 02.06.2015    source источник
comment
Как разработчик FindBugs, я могу вам помочь, но в настоящее время ваш вопрос немного неоднозначен. Я предполагаю, что вас интересует не каждое отдельное ASTORE, а поиск некоторых конкретных случаев (например, ASTORE конкретной строки, или ASTORE нулевого значения, или ASTORE результата предыдущего вызова GETFIELD или предыдущего метода). Не могли бы вы точнее описать эту часть: за какими ценностями вы охотитесь? Обратите внимание, что даже если у вас есть пара ALOAD, это не означает, что вы загружаете одно и то же значение, так как может быть цель ветвления (начало цикла и т. д.). Обычно следует отслеживать значения, а не регистры.   -  person Tagir Valeev    schedule 02.06.2015
comment
Я ищу ASTORE определенной строки. Например. Строковое имя = request.getParameter(имя). Этот оператор транслируется в байт-коде как ALOAD 1. LDC "name". INVOKEINTERFACE. ASTORE 7. Теперь я хочу отследить этот ASORE 7, чтобы найти ALOAD 7. @Tagir   -  person Manoj    schedule 02.06.2015
comment
Я отредактировал свой ответ и предоставил более подробный пример. Не стесняйтесь спрашивать, есть ли у вас проблемы.   -  person Tagir Valeev    schedule 02.06.2015


Ответы (1)


Для простых случаев лучше расширить OpcodeStackDetector. Этот абстрактный класс поддерживает отслеживание значений стека и хранит информацию о них. Вам вообще не нужно заботиться об ASTORE, ALOAD и т. д. Просто проверьте INVOKEINTERFACE. Например, если вы хотите найти места, где последний параметр метода является возвращаемым значением другого метода, вы можете сделать следующее:

public void sawOpcode(int seen) {
    if(seen == INVOKEINTERFACE && getMethodDescriptorOperand().getSlashedClassName()
        .equals("com/ibm/itim/ws/services/WSSessionService") &&
        getMethodDescriptorOperand().getName().equals("getNumber'))
    Item topStackItem = getStack().getStackItem(0);
    XMethod returnOf = topStackItem.getReturnValueOf();
    if(returnOf != null && returnOf.getName().equals("getParameter"))
        // here we go
    }
}

Вы можете изменить 0 на другое число в вызове getStackItem, чтобы получить и другие операнды. К сожалению, таким образом вы можете знать, что значение является возвратом метода getParameter, но не знаете, какие аргументы использовались в этом методе.

Если вам нужно отслеживать более сложные ситуации, то лучше использовать ValueNumberAnalysis. Это простая, но мощная концепция: она просто присваивает один и тот же номер значениям, которые статически доказаны как одинаковые. Предположим, вы хотите отслеживать все параметры запроса. Подготовимся к входу в метод (например, в visitCode):

private ValueNumberDataflow vna;
private Map<ValueNumber, String> vnToParameterName;

@Override
public void visit(Code code) {
    try {
        this.vna = getClassContext().getValueNumberDataflow(getMethod());
    } catch (DataflowAnalysisException | CFGBuilderException e) {
        bugReporter.logError("Unable to get VNA for "+getMethodDescriptor(), e);
        return;
    }
    this.vnToParameterName = new HashMap<>();
    super.visit(code);
}

Map будет использоваться для хранения значений и соответствующих имен параметров. Это можно сделать в sawOpcode:

@Override
public void sawOpcode(int seen) {
    if(seen == INVOKEINTERFACE) {
        if(getNameConstantOperand().equals("getParameter") && 
                getSigConstantOperand().equals("(Ljava/lang/String;)Ljava/lang/String;")
                /* && check the class if necessary */) {
            Object topValue = getStack().getStackItem(0).getConstant();
            if(topValue instanceof String) { // known parameter name like "name"
                // Iterate over locations corresponding to current PC 
                // (usually only one such location exists)
                for(Location location : vna.getCFG()
                        .getLocationsContainingInstructionWithOffset(getPC())) {
                    try {
                        // This frame contains value numbers 
                        // right after the INVOKEINTERFACE execution
                        ValueNumberFrame frame = vna.getFactAfterLocation(location);
                        // ValueNumber corresponding to the top stack value: 
                        // the return value of getParameters() method 
                        ValueNumber vn = frame.getTopValue();
                        vnToParameterName.put(vn, (String) topValue);
                    } catch (DataflowAnalysisException e) {
                        return;
                    }
                }
            }
        }
    }
}

Итак, теперь вы можете использовать эту карту. Добавьте еще немного кода в sawOpcode:

if(seen == INVOKEINTERFACE && getMethodDescriptorOperand().getSlashedClassName()
        .equals("com/ibm/itim/ws/services/WSSessionService") &&
        getMethodDescriptorOperand().getName().equals("getNumber"))
    for(Location location : vna.getCFG()
            .getLocationsContainingInstructionWithOffset(getPC())) {
        try {
            // This frame contains value numbers 
            // right before the INVOKEINTERFACE execution
            ValueNumberFrame frame = vna.getFactAtLocation(location);
            // ValueNumber corresponding to the top stack value: 
            // the last parameter for getNumber method
            ValueNumber vn = frame.getStackValue(0);
            String parameterName = vnToParameterName.get(vn);
            if(parameterName != null) {
                // hurrah: this parameter is in fact 
                // the return value of getParameter(parameterName)
            }
        } catch (DataflowAnalysisException e) {
            return;
        }
    }

Я не тестировал этот код, поэтому возможны небольшие проблемы. Обратите внимание, что ValueNumberAnalysis довольно мощная штука. Он способен не только отслеживать ASTORE/ALOAD, но и сколько угодно пересохранять это значение в другую переменную и даже (с некоторыми ограничениями) сохранять в поле с последующей загрузкой. Конечно, это будет работать и в том случае, если вы вообще не используете локальные переменные (например, getNumber(request.getParameter("name"))).

person Tagir Valeev    schedule 02.06.2015