3

I'm working on a Java reverse engineering project, where I am analyzing bytecode and trying to identify classes, methods and fields using the ASM framework. After identifying these I transform some classes to implement my custom interfaces with, e.g., getter methods.

For identifying classes, I have created an abstract class Analyzer with an abstract method for running the analysis on a collection of classes. Child classes of Analyzer try to identify a certain class/field/method of interest. For instance, AnimalAnalyzer attempts to identify the class which represents an animal.

After running the analyzers, I inject my own interfaces and their implementations into the classes. Similarly to Analyzer, I have created an Injector interface with an inject(List<Analyzer> analyzers) method. For each class that I want to inject there is a separate class which implements the interface, e.g., AnimalInjector. The injector iterates the list of Analyzers, which contain the results of their analysis, and uses the relevant ones by checking their type with the instanceof operator. By "relevant ones" I mean the analyzers which contain the information needed to generate the bytecode with.

public abstract class Analyzer {    
    public abstract void run(ClassCollection classCollection);
}

public class AnimalAnalyzer extends Analyzer {
    private ClassNode identifiedClass = null;
    @Override
    public void run(ClassCollection classCollection) {
        for (ClassNode classNode : classCollection.getAllClassNodes()) {
             boolean isMatch = doesClassMatch(classNode); // method omitted from example
             if (isMatch) {
                 identifiedClass = classNode;
                 break;
             }
        }
    }
}
public interface Injector {
    void inject(List<Analyzer> analyzers);
}

public class AnimalInjector implements Injector {
    @Override
    public void inject(List<Analyzer> analyzers) {
        AnimalAnalyzer animalAnalyzer = null;
        for (Analyzer analyzer : analyzers) {
            if (analyzer instanceof AnimalAnalyzer) {
                animalAnalyzer = (AnimalAnalyzer) analyzer;
            }
        }
        if (animalAnalyzer == null) {
            throw new IllegalStateException("AnimalAnalyzer not found in list");
        }
        ... (bytecode generation and injection)
    }
}

One of the reasons that I chose to do it this way is that the analyzers have to be run in a specific order. For example, to identify a Dog which extends Animal, the latter has to be found first. For this purpose I created MasterAnalyzer, which stores a list of the analyzers in a specific order. When run is invoked on this analyzer, it calls run on the analyzers in the list in the right order.

public class MasterAnalyzer extends Analyzer {
    private List<Analyzer> analyzers = new ArrayList<>();

    public MasterAnalyzer() {
        analyzers.add(new AnimalAnalyzer());
        analyzers.add(new DogAnalyzer());
        ... (more analyzers)
    }

    public List<Analyzer> getAnalyzers() {
        return analyzers;
    }

    @Override
    public void run(ClassCollection classCollection) {
        for (Analyzer analyzer : analyzers) {
            analyzer.run(classCollection);
        }
    }
}

The injectors, in turn, can all be run with the same command: inject(masterAnalyzers.getAnalyzers()). While this makes it easy at the client side, it causes the injectors to know more than they need to know. For instance, let's assume that we also have a CatAnalyzer. Now, when we run the DogInjector, the list of analyzers will include the CatAnalyzer, even though the DogInjector does not care about it. This, along with the nasty instanceof usages, causes red signals to me. Now I am reaching out to you more experienced developers for some tips on how to improve my design. While searching for alternative ways, I found the Visitor pattern, which could be applicable here. However, I am still unsure how to apply it and whether it would improve my design or not.

0

If I understand correctly, the MasterAnalyzer class just contains a static list of all of the available analyzers, and this list's order matter for the invocation's order.

Moreover, you want that specific Injectors will ignore certain Analyzers when they ask it from the MasterAnalyzer.

I will advise you to make the MasterAnalyzer class smarter, by exposing an API to request Analyzers with specific demands (given as parameter for instance).

For example, if DogInjector want to register to only specific analyzers it should receive a way to get them from the analyzer and invoke only them; Either by a rule/table-map/bitset etc. that signals what sort of analyzers it want to receive.

Examples:

  • Rule can be "analyzers" with class name that starts with 'Dog'.
  • Table-Map and Bitset to signal specific analyzers (but it makes your injector dependant on the representation of the list, so it may be inappropriate).
  • The analyzers themeseleves can contain information/parameter that you register based on it, and you can provide a lambda function to the MasterAnalyzer that he'll invoke on each analyzer on list and add accordingly.

The most simple-minded solution is to ask for specific analyzers, and get a copied list with references for the analyzers that the MasterAnalyzer adds them in the appropriate order.

*Depending on your requirements and the dynamics of your system you can return a proxy object to the analyzers list, that will 'pass' analyzers that you didn't register to and you won't need to copy the list.

0

There are a few things that aren't clear about this design for me. I'll update this answer as needed based on your clarifications.

First of all, I'm not really sure why you need an Analyzer class per class that you wish to identify. To test if a class is a subclass of another class you can use Class.isAssignableFrom(). For example:

Animal.class.isAssignableFrom(Dog.class); // returns true is Dog is subclass of Animal

You don't show the implementation of doesClassMatch, but there does seem to be a reason that: "to identify a Dog which extends Animal, the latter has to be found first." You just use the method above. The order in which you evaluate these tests doesn't matter. You will get the same results regardless of that.

I think this means that the entire set of Analyzer classes need not exist. You can probably just build the logic of testing a class into the injector routine. It's hard to tell exactly what that looks like since you don't really explain your goal. With more detail on that, more guidance can be provided.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.