Spring 2025 [Bono]
Even if you figure out some of the errors in this lab without the debugger, you should go through the practice of using the debugger to get more information about your program; debuggers are useful tools for every programmer, this is a good opportunity to acquaint yourself with one. For some reason students aren't motivated to learn a new piece of software like this when they have a mysterious bug in a program due in 3 hours :-) so we'll give you points for trying it out now!
Note: if you are already familiar with another full-featured IDE, you can use that instead of IntelliJ IDEA, but the directions for how to do things here are only about IntelliJ IDEA. If you haven't already used the debugger in that IDE, you will need to figure out how to do actions such as setting a breakpoint in that debugger before the lab.
When you open IntelliJ IDEA you can tell it to use the lab6 folder you just created as the project folder. If IntelliJ IDEA resumes with a previous project (for example the "Hello World" project), you can open another project by choosing the menu option File > Open.
This is probably not the first time you have seen the results of a program-crash in Java. Both when using java from the command line, or running in IntelliJ IDEA, the output that you get when a program crashes is a descriptive exception name, which may clue you in to why the program failed. Below the error message the subsequent lines of output show the call stack, that is, a list of the methods that were called, but not yet returned from, when the program failed. If you look from the bottom to the top of this list, at the bottom you'll see your main method, then above that the method that was called by main, and then the method that was called by that one, etc.
The top line of the call stack output shows exactly where the program failed: the class, method, the source code file, and line number. In IntelliJ IDEA, if you select the line number with your mouse, it will highlight that line of code in its editable source code display window. Do that now.
How to start the debugger: You are going to run the program again. Instead of choosing Run on the Run menu (or "Run" from right-clicking the main class file in the Project explorer window (left side of screen)), choose Debug on the Run menu (or "Debug" via a right-click). Enter data for the program as before. Once the program crashes it will show Debugger window.
How to inspect variables in the Debugger window: Once the program is stopped in the debugger you can see the values of the variables at the point where it stopped. All the variables that are currently in scope are shown with their current values on the right side of the Debugger window.
How to look at variables in other methods on the call stack: Sometimes the actual problem is in the method that called this method, or some other methods lower in the call stack. (More about this below.) Once the program has crashed or stopped at a breakpoint, you can look at the values of variables in other methods on the call stack as follows: on the left side of of the Debugger window, select the desired method in the call stack list that appears there.
public void foo(MyClass myObj) { myObj.myObjMethod(); // <-- null pointer exception here }Unless foo is supposed to handle a null value for myObj, the error is somewhere else in the program, likely somewhere in one of the methods that directly or indirectly called foo.
The crash may even happen in Java library code; but that doesn't mean there's a bug in the Java library code. For example, if you go off the end of an ArrayList, the method shown at the top of the call stack is some ArrayList method that's called by get to do range-checking, but the actual error is not in get but that you violated the precondition of get by passing it an out-of-range index.
How to clear a breakpoint: For when you are still debugging but no longer want to stop at a particular point, you can just do "Toggle breakpoint" again at that statement to turn it off.
Once your program has stopped at a breakpoint, you will be able to see current values of the variables at the right side of the Debugger window like we did before. But since our program is still active this time (unlike when it had crashed), we can keep running the program and see how the variables change. Read about single stepping before proceeding.
There are two main kinds of single step commands:
When to use which one? You will step over method calls that you know work, e.g., because you already debugged them, don't suspect a problem there, or because they are Java library methods. You will step into methods that you are still working on, or where you suspect the bug is. If you step into a method by mistake, use Step Out to skip over the rest of it.
In trying to diagnose a bug, running until the next breakpoint, instead of single-stepping, can also be a useful technique:
We're going to work with a version of NamesTester.java and Names.java for the next two exercises.
Now we're going to focus on displaying more complex data structures in the debugger. The version of this class and tester we're using for this lab is a little different than the one used in lecture: this version has code to allow the client to insert, remove, and lookup names that are internally stored in order in an ArrayList. This is a different representation than we used in our lecture version, but we want to use it here so you can see how to display the internals of a Java object.
Like the version we did in class, here we have a test driver NamesTester.java that tests all of the methods on several test cases each. (Note: The NamesTester.java version is also a little different than the one we used in lecture.) In addition to the methods we did in class (lookup, remove), Names has an insert function which inserts names in sorted (i.e., alphabetical) order. You may want to examine NamesTester.java to see how the insert method gets tested.
The Names insert method does not quite work correctly; run NamesTester see how it behaves.
Now, run this program again in the debugger to try to figure out what's wrong with it. Set breakpoints to stop execution at useful locations. Hint: so you don't spend a lot of time single-stepping through test cases that might not give us much information, stop the program at the first call to doOneInsert (call is in NamesTester) that gets incorrect results; then you can step into the call to insert that has the incorrect behavior.
Once you have a breakpoint set, you can view variables as we did before, but this time some of the the variables are object references and arrays. For these more complicated objects, you can view the subparts by clicking on the arrow next to the variable name. You may have to do this for a few levels to see everything you want to see (e.g., this --> namesArr --> namesArr elements).
Once your program is stopped in the insert method, make sure you can see the following key data for this method: namesArr individual elements, namesArr size, newName (the one we are trying to insert), and targetLoc. You can make the Debugger window larger to see them all at the same time.
Once you get the display set up, you can single-step to see how the values change as you do each step in the program. You may want to examine additional variables as you go as well.
You get this lab point by (1) showing the TA the contents of namesArr (including the strings inside it) being displayed while your program is stopped at a breakpoint
For DEN students, take a screenshot of the display showing the contents of namesArray as described above. Call the file ex3Screenshot.png (or ex3Screenshot.bmp depending on file format).
As you can see from this problem, the debugger is just a tool to help us see exactly what our buggy program is doing. It doesn't actually diagnose or fix bugs. For a non-trivial bug like this one, you may need to give it some thought, including doing some pencil and paper work, before figuring out how to correct the problem. As with all programs, you definitely do not want to just start adding random statements until it seems to work.
Upload your ex3Screenshot file and the working versions Factors.java and Names.java into your Lab 6 Vocareum workspace. (Upload button is on the upper left in the Vocareum IDE.)
When you click the Submit button, it will be looking for
and compiling (for source code) the files
Factors.java,
Names.java,
and a file with the prefix ex3screenshot
(since screenshots are stored in different formats on different platforms).