Because the majority of us write perfect code on our first attempt, you probably won't need this chapter. But, just in case there is a late night where a couple of those pesky bugs slip into your code, this chapter explains some of the features of the Visual J++ Debugger, including controlling the flow of your application line-by-line, inspecting variables in various ways, and covering how the debugger can help when exceptions are thrown and when dealing with multithreaded applications.
One of the most important aspects of debugging a program is getting a successful build. From there you can start setting breakpoints to instruct the debugger where you want to walk through your code. Finally, you have control of where you are walking.
Before you even think about debugging an application, you must first successfully build your application. When a compile-time error is encountered while building your applications, Visual J++ places all the necessary information into the Build tab of the output window. Each error line shows the location in the file where the error occurred, the line and column, and the error found. Figure 8.1 shows a sample compile-time error.
Figure 8.1 : Output window showing a compile error.
The example EX08A shown was created using the AppletWizard with all the bells and whistles turned on. For more information on creating applications and using the Applet Wizard, see Chapter 2, "Creating Your First Applet with Applet Wizard," and Chapter 3 "Using the Developer Studio."
While reviewing the code that was generated by the Applet Wizard,
a character in the file was inadvertently deleted. When the application
was then built, Visual J++ showed the compile-time errors in the
Output window. By highlighting the line in the Output window,
Visual J++ shows the source code line in error in the right pane
of the Project Workspace and places a small blue arrow next to
the line in error. In addition, the full error message is displayed
in the status bar. For this example, it is clear that the name
of the class is EX08AFrame, not EX08AFram.
| NOTE |
If you need more information about the compile error shown in the Output window, simply place the cursor on the error number (J0049 in the previous example) and press F1. An InfoView topic shows more information about the error and how it can be corrected. |
Once you have successfully built your application free of compile-time errors, you are ready to start debugging. The next section covers how to set breakpoints.
The first step in debugging your application is being able to start and stop execution of the source. To stop the execution of the application at a particular location in your program you must set a breakpoint. Breakpoints can be set on a specific source code line, at the entrance to or exit from a method, by examining the call stack, or at a specific memory address.
One of the most useful places to set a breakpoint is at a particular line of source code. To do this, simply place the cursor in the right pane of the Project View on the line of source where you want to stop. Then, use the Edit | Breakpoints command to bring up the Breakpoints dialog of Figure 8.2.
Figure 8.2 : Setting a breakpoint using the Breakpoints dialog.
You can easily set a breakpoint on the source code line by pressing the button to the right of the edit box and selecting the choice displayed. Once you have set the breakpoint at the line, you can conditionally stop execution based on an expression. Pressing the Condition button brings up the dialog shown in Figure 8.3.
Figure 8.3 : Adding conditions to the breakpoint.
The breakpoint will not stop execution until the expression entered into the first edit evaluates to true (if a Boolean expression is entered), or when the expression changes.
Some pesky bugs have a habit of hiding the first time (or several times) around during execution. The last edit on the Breakpoint Condition dialog enables the user to specify the number of times for which the expression evaluates to true before the execution stops. In this way, the user does not have to keep track of the number of times a breakpoint has stopped if he or she knows the error does not surface until the tenth iteration through a loop.
The list at the bottom of the Breakpoints dialog shows all current breakpoints for the project. You will notice that that there is a check mark in a small box next to each breakpoint. This indicates that the breakpoint is enabled. If you want to leave a breakpoint in the project but don't stop on it because you may have debugged that section of code, simply disable the breakpoint. To do this, click the small check mark next to the breakpoint. This disables the breakpoint, while leaving it in the system. The Remove button to the right of the list enables the user to delete the currently selected breakpoint from the list.
As a visual indication of the breakpoint, you will notice that a small red dot is placed next to the line of source in the right pane of the Project Workspace. When the breakpoint is disabled, the dot is changed to a red circle.
Once you have established your breakpoints, you need to actually start debugging. To start the execution of a program in the debugger, use the Build | Debug | Go command. This will start executing the program. Make sure that the application will run in the stand-alone interpreter using the Debug tab of the Build | Settings dialog.
Once a breakpoint is encountered, Visual J++ stops execution of the program and displays Project Workspace with an arrow showing where the current execution point is in the source code. Figure 8.4 shows an example of this.
Figure 8.4 : Current execution point of a stopped application.
| NOTE |
All breakpoints and current line indicators show the line about to be executed. Therefore, you should not expect values changing on the current line to have changed until after you execute the line. |
The Debug toolbar shown in Figure 8.4 contains a number of options
at your disposal. Table 8.1 contains a listing of the commands
available on the Debug toolbar.
| Command | Description |
| Restart | Restart the application, clearing the values of all variables. |
| Stop Debugging | Stop running the application in the debugger. |
| Step Into | Execute the current instruction, stepping into any methods encountered. |
| Step Over | Execute the current instruction, without stepping into any methods encountered. |
| Step Out | Execute instructions until the first instruction is encountered after the call to the current method. |
| Run to Cursor | Execute instructions until the line with the cursor is encountered. |
| QuickWatch | Display the QuickWatch window. |
| Watch | Display the Watch window. |
| Variables | Display the Variables window. |
| Registers | Display the Registers window. |
| Memory | Display the Memory window. |
| Call Stack | Display the Call Stack. |
| Disassembly | Display the current source in the disassembled bytecode instructions. |
Let's stop for a second and go back to how to start the execution of the application. There are three processes involved and they all have shortcut keys associated with F5.
When in the debugger, you can just start the application executing instructions from the current instruction until a breakpoint is encountered using the Debug | Go command or by using F5. This is basically letting the program execute because you either want to see how the program completes execution or you have a breakpoint set somewhere later in the flow of execution.
Alternatively, you might want to restart the execution of the application from the start. Debug | Restart, or Shift+F5 clears all the contents of the variables, resets the current instruction to the start of the application, and executes a Go command. Again, this assumes that breakpoints have been set and enabled to stop the execution of the application at the appropriate locations.
Lastly, if you have seen what you were looking for, Debug | Stop Debugging, or Alt+F5, terminates the application and stops the execution of the debugger.
When you have stopped at a breakpoint, you are likely to want to see or control the flow of execution of instructions in your application. This is commonly referred to as stepping through your application. There are several possibilities when stepping.
Consider the following line of code:
double classicPrice = myClassic.CalculateSalePrice();
This line calls the CalculateSalePrice method of the myClassic instance of the ClassicCar class and stores the result in classicPrice. For this example, the definition of the class is irrelevant, but a possible definition can be found in Appendix A, "A Whirlwind Tour of the Java Language."
Assume this line is contained in the current method of an executing application and is the current instruction. Using the Debug | Step Into command, or F8, the current instruction will be the first line of the CalculateSalePrice method. In other words, you are stepping into the contents of the function being called. This is quite useful when the function being called is yours and you want to see how the function behaves when it is being called from different sources or you want to see which class is being called for an overridden function.
When stepping into functions, remember that flow of control is passed to the next logical method called. Therefore, if there are embedded method calls, the first one executed will be stepped into first. What if the calculated sales price of the car is no longer based on the car but is instead based on the markup of the dealership selling the car? The following line of code might be encountered:
double classicPrice = myClassic.CalculateSalePrice(myDealer.GetMarkup());
If this is the current line and F8 is pressed, the first method stepped into would be the call to GetMarkup. If F8 is pressed on the return of that method, CalculateSalePrice would then be entered.
If you are not interested in stepping into a function, you should use Debug | Step Over or F10. This steps over the call to the function and simply executes the current instruction and moves to the next instruction in the current method. For this command, it makes no difference the number of functions being called in the current instruction. Therefore, in both of the previous examples, the next instruction will always be the next instruction in the current method.
You might find yourself in a method where only the initial instructions of the method are in question and you really want to know how the result of this method affects the calling method. To step out of the current method, use the Debug | Step Out command (or Shift+F7). This command moves the currently executing instruction to the instruction immediately following the call to the current method.
The Debug | Run to Cursor command, or F7, is used to start execution and break when the line with the cursor is encountered. The same effect can be accomplished by setting a breakpoint on the current line, using the Debug | Go command to start execution and removing the breakpoint once the execution has stopped on the current line. Obviously, F7 is much easier to use.
A nice little feature of Visual J++ is the capability to stop
the execution of your application. The Debug | Break command stops
the execution of the application. This is very useful for those
situations where you either forgot to set a breakpoint and started
executing a long series of steps to get to a piece of code intended
to be debugged, or your application slipped into an infinite loop.
| CAUTION |
While the ability to interrupt your application is quite nice, it can also be very dangerous. The interruption occurs at a very low level. Therefore, where it will be interrupted is unpredictable. The best way to use this capability is to do the following: |
So far, you have learned how to set breakpoints and control what is being executed. Although this accomplishes quite a bit, a much more useful tool is the capability to look at the state of the application. This section covers how to inspect the values of variables several different ways and how to take a look at the Call Stack of the application.
One the of the most useful features of a debugger is to look at the values of the variables in applications. The Visual J++ Debugger allows several different ways, including automatically displaying values using DataTips, an immediate inspection of variables using the QuickWatch dialog, placing variables on a Watch window which is automatically updated when values change, and by examining the Variables window which always contains variables at differing levels.
A very quick and handy tool to use when inspecting values of variables is to simply place the mouse over the variable to be inspected. Figure 8.5 shows DataTips in action.
Figure 8.5 : Current execution point of a stopped application.
This is a quick and painless way to quickly glance at a value. Expressions can also be inspected by simply highlighting the expression and waiting for DataTips. Invalid expressions and variables not in the current scope will not bring up DataTips.
A more thorough inspection of the value of a variable is handled with the QuickWatch window. Figure 8.6 shows the QuickWatch window.
Figure 8.6 : Example of the QuickWatch window.
The first edit window lets the user type in the expression that is to be inspected. The bottom window shows the current value. The user can examine the value and modify any values available by simply clicking the value next to the item to be modified.
The value being displayed may have a small plus symbol (+) next to the name in the name column. Visual J++ automatically detects whether the expression being inspected is an object that contains objects in itself. By pressing the plus symbol, additional lines are added to the current value list representing the contained objects. Typical examples of this are when inspecting arrays or class instances. Figure 8.7 shows an example of inspecting the current class instance.
Figure 8.7 : Inspecting the current instance.
You can change the expression and repopulate the current value list by pressing the Recalculate button.
If the expression is a good one, you can add the expression as a Watch by using the Watch button.
The Watch window is used to monitor values. The values of the items being watched are updated when the values change. Therefore, you can always see the current value of the item. Figure 8.8 shows the Watch window.
Figure 8.8 : The Project Workspace showing the Watch window.
Again, Visual J++ detects objects that contain objects and indicates the expandability of the objects with a small plus (+) symbol. You can expand or collapse an item by pressing on the + or - symbols next to the items.
You might have noticed that there are four tabs on the Watch window. Watches can be logically collected together on each of the four tabs. In this way, you can inspect the group of variables that is most applicable to the area being debugged.
You can add a watch to the current table in a variety of ways:
Variables can be displayed using a variety of different formats
when displayed in the Watch window. To specify the format to display
a value, follow the name of the variable with a comma and a format
specification from Table 8.2. (This table can also be found in
the online Visual J++ documentation.)
| Specifier | Type of value displayed |
| d,i | Signed decimal integer |
| u | Unsigned decimal integer |
| o | Unsigned octal integer |
| x,X | Hexadecimal integer (non-numeric digits are displayed in the same case as the specifier) |
| l,h | Long or short prefix, respectively, for integer specifiers |
| f | Signed floating point |
| e | Signed scientific notation |
| g | Shorter version of either f or e |
| c | Single character |
| s | String |
| su | Unicode string |
You can get additional information about a watched item by selecting that item in the Watch window. Use the right mouse button to display the pop-up menu and select the properties command. This will display the dialog of Figure 8.9 showing the name, type, and value of the item.
Figure 8.9 : The properties of a watched item showing the type of the item.
The Variable window is similar to the Watch window as seen in Figure 8.10.
Figure 8.10 : The Project Workspace showing the Auto tab of the Variable window.
The difference between the Variable window and the Watch window
is that the user cannot add variables to this window. Instead,
a variety of items are automatically added to various tabs as
shown in Table 8.3.
| Tab | Contents of the tab |
| Auto | Variables referenced in the current or previously executed instruction |
| Local | All local variables defined in the current method |
| this | Object reference by this |
The values for the items in the Variable window are displayed the same way as the Watch and QuickWatch windows. To modify the values, simply double-click the value to be modified and start typing. To show the type of an item, choose the item from the window and use the pop-up menu to select the Properties command.
The context of the variables being displayed can be selected using the combobox at the top of the Variable window. The combobox contains the list of method calls that has led up to the execution of the current method. The content of context combobox is also contained in the Call Stack window.
The Call Stack window contains a logical mapping of the calls made to get to the currently executing method. Every time a call is made by a method, the calling method name is pushed onto the top of the stack. When the called method returns, the name is popped off the stack. The net result is a listing of methods called to get to the current method. Figure 8.11 shows an example of a Call Stack.
Figure 8.11 : The Project Workspace showing the Call Stack window.
| TIP |
You can move the Call Stack window by simply dragging the title bar of the window-normal window functionality. However, because the window is a dockable window, if you move it to a dockable area, say the bottom of the dialog, it might try to dock, even if that is not your intention. To prevent the docking from taking place, press the Ctrl key before releasing the window. |
Each line in the Call Stack contains the line used to call the method. In addition, the parameters types and value used in making the call to the method are displayed.
Visual J++ supports debugging into exceptions through the use of the Exceptions dialog of Fig-ure 8.12.
Figure 8.12 : Exceptions dialog.
To view the Exceptions dialog, execute the Debug | Exceptions command. This dialog enables the user to specify an exception and the action to be taken if the exception is thrown. Both system- and user-defined exceptions can be handled.
The two actions that can occur when an exception is thrown is to stop immediately or stop if the exception is not handled. One advantage of stopping immediately is that the debugger will stop as soon as the exception has been generated. The advantage of this is that the user has the chance to examine the source code in the state that caused the exception to occur and possibly fix the cause of the exception, if it can be fixed by modifying the value of a variable or a similar modification. Once the remedy is in place, the user can restart the application, at which point the user will be prompted if they would like the exception handlers to be called. If the problem has been remedied, the user should reply no.
The second action is notification that an exception was not handled by the exception-handling routines. With this option, the user will simply be notified that an unhandled exception occurred.
A thread is like a mini-process within an application. A process can be made up of one to many threads with each thread performing an individual task. The Visual J++ Debugger supports being able to choose the thread to be debugged using the dialog shown in Figure 8.13.
This dialog shows that each thread in a process contains an identifying id, a suspend quantity, a priority (which for Java threads will always be normal), and the currently executing location. The location can be displayed either in terms of method currently being executed or in terms of address.
To learn more about multithreaded applications, see Chapter 12, "Moving Up to Multithreading."
Although you might possibly never read this chapter, it is nice to know that the Visual J++ Debugger enables the user extensive control over setting breakpoints to stop the execution of an application, lets the user to step in, and over, method calls, and allows for easy inspection and modification of all the variables being used by the application. In addition, support is provided to stop execution of the application when exceptions are thrown and to specify the thread that is to be debugged for multithreaded applications.