Findbugs: персонализиран детектор

Пиша персонализиран детектор във Findbugs. Искам да знам дали има някакъв начин, по който мога да следя ASTORE и съответната инструкция ALOAD? Тоест, ако ASTORE 3 се появи в моя байткод, искам първо да идентифицирам, че това е инструкция ASTORE, а след това нейния индекс (в този случай: 3 ) и потърсете инструкция ALOAD със същия индекс (в този случай инструкция ALOAD 3).

Например в байт кода, показан по-долу, искам да прочета инструкцията ASTORE 8 (показва се на ред #29) и да видя дали има някаква инструкция ALOAD с индекс 8. Т.е., ALOAD 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 на null, или 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