Aspects And Instrumentation

Swe

Introduction

Programs can be analyzed offline by inspecting the source code. Alternatively, programs can be analyzed online by inserting code in the application to perform the analysis while the program is running. The program is said to be instrumented. An example is a classic debugger which inserts special code at breakpoints to pause the program. The goal of this assignment is to explore and implement several instrumentations.

Instrumentation of arbitrary native programs (say written in C or C++) requires a lot of knowledge about a specific platform and instruction set. We abstract from this by using aspect-oriented programming to weave in code, and use Java as it is slightly easier to instrument.

The assignment consist of 8 small exercises. The grading is determined as follows. You earn an 8.0 for a properly documented, properly structured, and reasonably correct submission. You get higher grades by doing more work: more extensive discussions of design choices, more features, doing some of the optional exercises, etc. On the other hand, omitting an exercise leads to a loss of 1.0 point.

Work on this assignment in pairs and work on each individual exercise together.

Preliminaries

Several tools are needed in order to do this assignment: a recent Java virtual machine and AspectJ, the aspect-oriented Java compiler. Furthermore, you'll need to learn the basics of the AspectJ language and some of the Java reflection API's.

Get Java

A recent Java virtual machine is already installed on all Windows and Linux student machines. If you do this exercise at home, make sure you use version 1.6 or later (or Java 1.5 in combination with the Eclipse AspectJ? plugin on the lab machines). Familiarize yourself with using Java from the commandline.

Get AspectJ

For this exercise we use the aspect-oriented programming extensions of AspectJ and the AspectJ compiler (ajc) to compile the Java source files. Download AspectJ yourself from:

http://www.eclipse.org/aspectj/

Download the appropriate .jar file and run it with java -jar jarfile. A graphical installer will appear. Use the default options, which installs AspectJ in your home directory (~/aspectj1.5). Add the appropriate paths to your PATH and CLASSPATH environment variables, for example:

export PATH=~/aspectj1.5/bin:$PATH
export CLASSPATH=~/aspectj1.5/lib:$CLASSPATH

You should then be able to run it:

> ajc -version
AspectJ Compiler 1.5.3 built on Wednesday Nov 22, 2006 at 11:18:15 GMT

You can use Eclipse to help you develop AspectJ? programs. If you use Eclipse, be sure to verify that your code also works when using AspectJ? from the commandline.

Get to know AspectJ

Make sure you understand the basics of AspectJ before proceeding with the exercise. Read the Programming Guide and scan through the Quick Reference on the documentation page: http://www.eclipse.org/aspectj/docs.php. There is some additional material available there as well.

Experiment a bit with pointcuts and advice until you think that you have a basic understanding of what these things are and how you can use them.

Exercises

Important: read this section thoroughly!

We are going to implement several instrumentations. For each instrumentation you'll have to:

  • Write some aspects that perform the analysis and produces a report of the analysis when the application closes.
  • Write example program(s) to test your aspects.
  • Write how to compile the programs and how to run them.
  • Appropriate documentation. Discuss design decisions and possible problems that can arise when you use your aspects on real-world programs.

Some remarks:

  • You are allowed to implement each assignment on its own, disregarding the other assignments. If that requires special build or run instructions, make sure to document it!
  • Some of the exercises require a written answer. Give brief but meaningful answers.
  • Watch out with multi-threading! Use the appropriate protections. If needed, you may assume that all threads are created as an instance of Runnable.
  • The outcome of an instrumentation needs to be some kind of report. Determine for yourself if you want to report to the console (using System.out.println), or to a html/xml-file, or LaTeX-file, or some other reasonable way. If you choose to report to the console, make sure that the output of the instrumentation is not interleaved with the output of the original program.
  • Note that the static main-method may return before the last thread has stopped executing. So, printing the instrumentation report as a last statement of the main-method is not a good idea, because it is then possible that the report is printed too early. There are a number of solutions to this problem, for example, using AspectJ? to catch the termination of the last thread.
  • Take a look at the Java reflection APIs, and information that can be obtained from the ClassLoader.
  • Consider searching the internet for example aspects and example introspections of Java code. Do not blindly copy&paste source code though (and at least give a reference to the original source).
  • Java classes of interest: Runtime and ClassLoader.

1: Implementation of hashCode() matches equals()

Java programmers often override the equals() method of Object, but do not provide a proper implementation of hashCode() (See "Implementing equals"). Your task is to write an instrumentation of a Java program that generates a report containing the names of the Java classes that do not have a correct equals()/hashCode() combination. See the class documentation for the properties that equals() and hashCode() need to satisfy.

For example, given the class:

class WrongHashcode
{
    public boolean equals(Object o)
    {
        return true;
    }
}

public class Main
{
    public static void main(String[] args) throws Exception
    {
        WrongHashcode w = new WrongHashcode();
        Object other = new Object();
        w.equals(other);
        System.out.println("Hello, World!");
    }
}

The output is for example:

> java Main
Hello, World!
====================================================================
Invalid implementations of hashCode():
--------------------------------------------------------------------
class WrongHashcode
--------------------------------------------------------------------

2: All sockets closed

Java is a garbage-collected language. This means that unused resources allocated by a program are eventually released automatically. However, while this works fine for memory, this approach is not as suitable for resources such as sockets, files and database connections, because it can take a while before the garbage collector decides to clean them up, among other reasons. Therefore, programmers explicitly release these kind of resources when they are no longer needed.

The task is to instrument a Java program that uses Sockets, such that those sockets are reported that are opened but not closed. Include some information about the socket (such as to where it is connected to) and the line number and filename where the socket is created.

Consider the following points:

  • What to do if the program terminates early (for example due to some unhandled exceptions)
  • Can the garbage collector influence your instrumentation?

Answer the following question:

  • Can you generalize your approach to files and database connections? You do not have to implement this, but give a sketch and discuss potential caveats.

3: Notify for each wait

Java offers a wait/notify construction for synchronization in a distributed system. Problems arise when there are more waits than notifies. This problem appears to be similar to the previous exercise. Yet, if you try to implement it, you'll notice some problems:

  • Describe these problems (i.e. why instrumentation does not help you in order to detect the above situation)
  • Describe instrumentations that would help you with debugging when you notice that the waits and notifies are not properly balanced.
  • (Optional) discuss how you can use instrumentation to detect situations where there is not enough synchronization (i.e. conflicting field accesses)

4: Direct recursion

Create an instrumentation that lists methods that are directly recursive. A method is said to be directly recursive if the first method call is a call to itself. Can you think of an example where this is hard to analyze statically?

5: Method coverage

Code coverage measurement is important in combination with automated tests. Instead of lines of code we are taking a slightly easier approach and are going to instrument a program such that in the end we know which methods are not executed during the execution of the program. The output could be (for example):

Uncovered methods:
--------------------------------------------------------------------
Main.unusedMethod
Administration.decrement

Remarks:

  • Use thisJoinPoint to get information about the context of a join point.
  • Query the system class loader for class/method information.
  • Can you filter out unused methods from system libraries?
  • It is not trivial to list all the classes in Java. You have to find (or build) a class/function that does this for you. You can use something from the web, just give a reference.

6: Profiling methods

We are now going to write a simple profiler. The task is to determine for each of your classes how much time and memory the methods take (in total).

Remarks:

  • Think about the influence of the garbage collector.
  • Use the System and Runtime classes to obtain information about the current time and the memory in use by the process.

Answer the following questions:

  • Which concessions did you have to make? How accurate are the numbers produced by your instrumentation?
  • (Optional) Determine a call-tree of the program. Give time and memory usage on each of the nodes of the call-tree.
  • (Optional) Can you also compute the time spent in a method without considering the time spend in sub methods?

7: Watches

A typical debugger has breakpoints and watches. A breakpoint pauses the program when a certain line of code is executed. A watch pauses the program when a variable is assigned a certain value. Now the task is to create an instrumentation that records the source locations where a numeric variable of your choosing is assigned a value in the range 10..3000. Then, generalize this approach such that the user can configure the object, variable and range.

8: Discuss combining instrumentations

You were allowed to implement each of the instrumentations individually. Now, suppose that you want to use all the instrumentations together. Discuss:

  • How this could be done
  • How to resolve interference between instrumentations

Keep the text limited to at most one A4 paper (excluding examples).

What to hand in

  • Source code (both aspects and sample programs)
  • Compilation and running instructions. To simplify matters for us:
    • Create a separate directory for each exercise
    • Put all the command lines that are needed to compile and run each example in a single file (run.txt). For example:
                  cd ex1
                  ajc Main.java
                  java Main
                  cd ..
                  cd ex2
                  ajc Main.java
                  java Main
                  cd ..
                  cd ex3
                  ... etc ...
                
      Add additional parameters to ajc and java if required. If you used AspectJ? plugin for Eclipse during development, you still have to produce this file and make sure that it can be compiled with ajc and run with java. If you really want to, you can also create an ant file that does the compiling and running.
  • Documentation

You do not need to hand in the compiled class files. However, if you use additional libraries for some reason, make sure to include them or to provide a download link.

-- ArieMiddelkoop - 25 Sep 2007