6.170 / Spring 2001 / Lab 2: Debugging Java Programs

Handout B2

Contents:


Purpose

This lab should familiarize you with some of the tools and techniques available to you to help you debug problems that you may have with your programs.

Setup

First you should copy the source files from /mit/6.170/www/labs/lab2/ to your ~/6.170/lab2 directory as follows:
mkdir ~/6.170/lab2
cp /mit/6.170/www/labs/lab2/*.java ~/6.170/lab2
Once you have copied the files try to compile them. They should compile without any errors.

Exceptions

The exception handling mechanism in Java is a very useful system which greatly simplifies the reporting of and handling of errors or "exceptional" circumstances. Often exceptions are used to propagate information about some sort of error condition from deep within a computation to higher up in the program. To do this code can, when encountering an error, "throw" an exception. This causes the Java runtime to look for a "catch" statement for that type of exception traveling up the call stack (to the method that called the current method and then to the method that called that method). When an appropriate catch statement is found the code in the catch statement is executed. If, however, an exception is not caught by the program, the Java runtime system halts the execution of the program and prints a message about the exception and where it was thrown from.

Common Exceptions

The Java API contains many built-in exceptions each with a special purpose. Here are some of the ones you are most likely to encounter due to bugs in your program.

Stack Traces

When the Java runtime catches an exception and terminates the program it not only prints out a message saying what sort of an exception was caught but also prints out a stack trace showing the method call stack (a list of all of the methods that have been entered but not yet existed) at the time the exception was thrown. For instance when you first run the sample code for this lab you will see the following error:
athena% java lab2.HuffmanMain
Exception in thread "main" java.lang.NullPointerException
        at lab2.HuffmanCodec.encode(HuffmanCodec.java:137)
        at lab2.HuffmanMain.main(HuffmanMain.java:14)
The first line of output tells you that an exception has been thrown and tells you the type of the exception. In this case it is a NullPointerException. The following lines show you a snap shot of what methods were calling what other methods at the time the exception was thrown. In this case (starting from the bottom) we see that the program started out executing lab2.HuffmanMain.main. The main method then, on line 14, called HuffmanMain.encode which threw the NullPointerException on line 137 of the source file.

If, when debugging, you want to find out about which methods are calling certain code you can have the Java runtime print out a stack trace on demand without halting the program by throwing an exception. To do this call Thread.dumpStack().

toString()

All Java objects have a toString method which returns a String representation of the object. This provides a convenient way for you to dump information about the state of your program helping you understand what is going on.

As you may have noticed all java.lang.Object provides an implementation of toString() which will return the name of the class as well as an implementation-specific hash value. In most cases, therefore, you'll find it in your best interest to override the toString method with one of your own which returns a more useful string, perhaps identifying the class. information.

In fact because the toString() is available for all objects, the Java language provides special support to automatically call it in certain circumstances. For instance the string concatenation operation, +, can automatically call the toString method on one of it's arguments. In fact it can also be used to convert Java primitives to strings. For instance you could write the following line in Java:

System.out.println("My vector has " + v.size() + " elements which are " + v);
When compiling the above line the Java compiler will automatically insert the appropriate code to convert the result of size, which is an int to a String as well as automatically calling toString() on v to produce a String. This will cause the program to output something akin to the following:
My vector has 5 elements which are [A, B, C, D, E]

In fact the compiler is even slightly trickier than that in that when converting an object to a String it will only call toString() if the reference to the object is non-null, else it will produce the string "null".

Useful Emacs Tools

Working with your Java code under Emacs provides you with access to many useful tools to help you debug and work with your code.

In order for these features to work you must make sure you have the following line in your .emacs file:

  (load "/mit/6.170/etc/emacs/6170.el")
If you do not, add it and restart Emacs.

mouse-goto-error

Make sure you have already copied the source files for this lab to your lab2 directory and compiled them. Now make sure you don't currently have any of the Java files open in an Emacs buffer.

Start a shell from within Emacs: M-x shell RET

Within the shell buffer, type

java lab2.Thrownull
which will result in a NullPointerException:
  
  athena% java lab2.Thrownull
  Exception in thread "main" java.lang.NullPointerException
	  at lab2.Thrownull.printHashCode(Thrownull.java:12)
	  at lab2.Thrownull.main(Thrownull.java:8)
Click, with your middle mouse button, anywhere on the line containing "Thrownull.java:8". Notice that Emacs reads lab2/Thrownull.java into a buffer (if the file had already been read into a buffer, it would have skipped that step) and puts the cursor on line 8, which is the line that threw the exception. Now middle-click anywhere on the line containing "Thrownull.java:12" It can be very convenient to simply click up and down a stack trace, seeing which code was in the middle of executing, much like a debugger would permit you to do, or to go directly to the code of interest.

jdk-lookup

Edit a Java file. Make sure you have a copy of Netscape running. Type C-h f ; a JDK index is loaded (this might take a few seconds). To the question "JDK doc for: ", type the name of a Java method, such as addElement. (You can use completion -- press TAB -- to finish a partly-typed entry or to show you all the completions for a file.) If there are multiple methods with that name, specify the full signature (including package and argument types); with completion, you shouldn't have to do much typing. The documentation for the method you specified appears in the Netscape window.

Place the cursor on a method name, and type C-h f again. Notice that the method name that the cursor is in is the default, but you can still type any method name you like. Note that this works for methods defined in the problem sets (such as "heading") as well as for those defined in the JDK and those from JUnit, and that it works for field names as well as for method names.

An Example: Huffman Encoding

Huffman encoding is a simple form of data encoding which attempts to represent each input symbol with a sequence of bits such that the most frequently used symbols are represent with the fewest bits. Huffman encoding is described the the 6.001 text (section 2.3.4) as well as explained in detail combined with a lovely Java animation here. The sample code for this lab consists of a simple Huffman encoding/decoding scheme. The class HuffmanSymbol is used to associate a symbol in the alphabet that is to be encoded with a value representing how frequently it occurs since Huffman encoding attempts to use the fewest bits for the most frequently occurring symbols. HuffmanTree is a simple data structure which can be used to build a tree which is used to decode a bit stream. The process of decoding a bit stream involves looking at each bit in series and choosing the left subtree if the bit is 0 and the right subtree if the bit is a 1. When a leaf of the tree is reached the symbol corresponding to that leaf can be output. The file HuffmanCodec is used to both build the encoding table and decoding tree as well as to perform the actual encoding and decoding. A separate instance of HuffmanCodec is created for a given set of symbols and frequencies after which it can be used for any input so long as the input only uses symbols that were used when constructing the codec. Finally HuffmanMain contains a short driver to encode/decode two simple strings as well as some useful methods for converting data types.

Debugging the Code

If you now run lab2.HuffmanMain you'll find the program dies by throwing a NullPointerException. Alas the Huffman encoder/decoder is broken and now you get to try to fix it. In some sense this might be a bit more difficult than debugging your own code since you are not familiar with how it works, but on the other hand you don't already have a notion of what you think is going on stuck in your head preventing you from seeing what really is going on.

A very important tip before you start trying to debug the code: read the code and think about it. Thinking turns out to be a far more efficient way of debugging than littering code with System.out.println's, using a debugger, or anything else. These other tools are often useful as you try to get a grasp as to what is going on, or to verify what you think is happening, but they won't substitute for some good reasoning about the code.

It may also at some point in the debugging process also become useful to modify the main method slightly so that you can try out other test cases.

Some things you may want to look at is verifying that methods preconditions are met. You might also find it useful to add toString methods to classes to allow you to print out intermediate values. You should also consider adding code to verify that rep invariants are being maintained.

Here's a hint: The correct encoding for "set" is "1001111110". You should first try to get the encoding and decoding functions to decode to the same strings you input. Once that is done you will have a functioning encoder/decode but it won't quite be Huffman encoding since it won't be using the fewest bits possible for the least used symbols, so you should then look into fixing that using the above encoded string to help.

Other Tools

JDB

The JDK comes with a debugger called jdb which works in a manner somewhat similar to gdb. If you are familiar with gdb you may find it useful to try using jdb. In a future lab we will teach the use of JDB.
Back to the 6.170 labs index.
Back to the 6.170 home page.
For problems or questions regarding this page, contact: 6.170-webmaster@mit.edu.
$Id: lab2.html,v 1.20 2001/02/16 14:56:05 jeffshel Exp $