As a developer, you will occasionally encounter problems in your code, including incorrect logic, improper usage of an API, or any number of other issues. This guide will teach you how to debug your code, detect some common problems, and find their solutions.
Oops! Something is badly wrong. My program crashed, or printed nonsense, or seems to be running forever. Now what?
Beginners have a tendency to blame the compiler, the library, or anything other than their own code. Experienced programmers would love to do the same, but they know that, realistically, most problems are their own fault.
Fortunately, most bugs are simple and can be found with simple techniques. Examine the evidence in the erroneous output and try to infer how it could have been produced. Look at any debugging output before the crash; if possible get a stack trace from a debugger. Now you know something of what happened, and where. Pause to reflect. How could that happen? Reason back from the state of the crashed program to determine what could have caused this.
Debugging involves backwards reasoning, like solving murder mysteries. Something impossible occurred, and the only solid information is that it really did occur. So we must think backwards from the result to discover the reasons. Once we have a full explanation, we'll know what to fix and, along the way, likely discover a few other things we hadn't expected.
This excerpt from The Practice of Programming emphasizes the importance of gathering sufficient data and facts before undertaking the task of debugging. Sometimes, this means eliminating possible causes in order to find the real problem. Other times, it's an indirect way to resolve the issue.
Solar2D Editor is an
Once you have Sublime Text and Solar2D Editor installed, it can help you debug your code. For example, you can stop the Simulator at certain points and examine the state of your code, or you can step through the code and inspect variable values along the way. In addition, you can follow the stack trace of the app for more comprehensive debugging purposes.
One key concept in using an interactive debugger is the ability to stop your program at certain points so you can observe the current state. This includes looking at the value of variables in scope, stepping through the code one line or one function at a time, and checking the stack trace. This is known as using break points and it all revolves around stopping your program at the points you want to inspect.
You can enable or disable (toggle) break points in one of two ways:
Click on the line of code and select Solar2D Editor → Debugger → Toggle Breakpoint from the main menu.
Right-click the line of code and choose Toggle Breakpoint from the popup context menu.
When you set a break point, a small logo will appear on the line. Then, when you run your app from Sublime Text via Solar2D Editor → Debugger → Run, it will stop when it reaches this line of code and return to Sublime Text. At this point, you have several options:
Run — Run the code until the next break point or until completion.
Step Over — This will step forward one line of code, and if it's a function, it will complete the function and stop on the next line of code.
Step Into — This will step forward one line of code, and if it's a function, it will enter the function and stop on the first line of that function.
Toggle Breakpoint — Turn off the breakpoint or set a new one.
Inspect Variable — See Inspecting Variables below.
Stop — Stop the program.
From the main menu, select Debugger → Inspect Variable. A search bar will open at the bottom of Sublime Text and you can enter any valid variable. You will then see the details of that variable, and if it's a table, this can provide a better visual representation compared to what is typically available.
Sometimes it's helpful to know where an error originated from. When you call a function and it misbehaves, a common reason is that the calling function passed invalid data. By using the stack trace, you can quickly identify which line of code called the function that you are currently executing. More specifically, you can set a break point in that function, continue running your app, and when the function exits and hits the next break point, you can examine the variables that were used to call the function.
For more information on the stack trace, see Viewing Runtime Errors below.
If you have Show Runtime Errors enabled in the Simulator preferences, runtime errors (those that crash your app) will abort the program and reveal a message dialog box with more information about the error, including:
main.lua
.Line 218
Attempt to perform arithmetic on a nil value.
stack traceback: main.lua:218: in function 'start' main.lua:222: in main chunk
This collective information usually pinpoints the exact spot in your code where the problem lies. In this example, you can open the main.lua
file, locate line 218
, and look for a math operation where one of the values is nil
— specifically, a place where you're attempting to perform math on an undefined variable. Furthermore, the stack trace reveals that the error resides within the function named start
, and that function was called from line 222
.
Sometimes, the stack trace is less specific and must be followed carefully. For instance:
?: in function <?:221> main.lua:218: in function 'start' main.lua:222: in main chunk
From this, you know that the error resides on line 221
, but you must trace backwards for more details. In this case, the problem occurred on line 221
, and that line was called from a function named start
at line 218
of main.lua
. Thus, even if the first line of the stack trace doesn't pinpoint exactly where the problem occurred, it can usually expose the root of the error.
When building for iOS you have the choice of a Development build or a Distribution build. When building for Android, you either build with a Debug keystore or a Release keystore. Typically, your choice impacts the information available in the stack trace as follows:
If you build with a development profile or a debug keystore, Solar2D will retain debug symbols within the Lua code. This results in a minimal performance impact and a larger application bundle.
If you build with a distribution profile or a release keystore, Solar2D will strip debug symbols out of the Lua code.
If you wish to retain these symbols regardless of the build type, simply override the default behavior as illustrated here. This will make it easier to trace the code, but if the error occurs inside of core Solar2D code, debug symbols will still not be available. In these cases, you'll need to trace backwards and determine why the information you're passing to a Solar2D library function is causing an error.
Diagnostic output can also be viewed within the Simulator Console. Here, useful debugging messages will be shown, along with the value of print()
statements within your code. While this practice may be considered outdated to some developers, tracking the output of print()
commands can often reveal the cause of an error or an issue with conditional logic.
The above methods can help diagnose most coding issues, but the Xcode Simulator for macOS may be necessary to preview how the app will function on actual iOS devices. This helps simulate some device features that the Simulator cannot, but it's still not as accurate as a real device.
To test an app in the Xcode Simulator:
Select File → Build → Build for iOS... from within the Solar2D Simulator.
In the dialog window, under the Build for:
Proceed with the build process and the application will open in a simulated device window.
From this application, select Debug →
Some errors only manifest themselves once you install the app on a device. Fortunately, each device has its own console log which you can access, and for Apple, you can see this console log within Xcode as follows:
Open Xcode if it's not already running.
Open the Devices & Simulators window (Window → Devices & Simulators).
Connect the device to your computer and wait until it appears in the left column of the window. When it does, select it.
Click on the Open Console button to open the device's console log.
Debugging on Android is a bit more "command line oriented," but once you figure out the commands, it's reasonably quick to install an app, view the log as the app runs, and look for error messages.
First, you must install some free tools from Google called Android Debug Bridge. Simply follow these steps:
Visit the download page. Instead of clicking the large download button, click on the Download Options link. Then, in the
Install the tools. On Windows, run the installer; on macOS, unzip the file and move the folder to a sensible location.
Follow the instructions for Adding SDK Packages. Install at least the Android SDK Tools and
Once the tools are installed, you can use the command line tools. With your Android device connected, simply type the following command in the Terminal (macOS) or Command Prompt (Windows) and watch the messages appear. This command will filter out all messages except those generated by Solar2D.
adb logcat Corona:v *:s
Sometimes errors are generated by things besides Solar2D, for example messages from Google Play related to Google
adb logcat
This will prevent filtering for print()
statements within your code near the area which you suspect is problematic. For example:
print( "MYAPP - Purchase: *********************************************" ) store.purchase( "com.coronalabs.NewExampleInAppPurchase.NonConsumableTier1" )
If you prefer a more visual tool, adb also offers a GUI version of adb logcat
If you're working on a Win32 desktop app, some details on debugging can be located here.
If you're working on a macOS desktop app, some details on debugging can be located here.
There are some common issues which are usually simple to resolve once you're aware of them.
The most important practice for code readability is proper indentation (adding tabs or spaces before lines to distinguish blocks of code). You should use a text editor or Integrated Development Environment (IDE) which handles indentions for you. Consider this code:
local function doSomething( params ) if params.someValue == 10 then if params.someOtherValue == "test" then params.yetAnotherValue = params.someValue end end
Without indenting the lines to show the various blocks of code, it's very difficult to read. In fact, there's an error which would be obvious if the code was indented correctly. Compare the difference:
local function doSomething( params ) if params.someValue == 10 then if params.someOtherValue == "test" then params.yetAnotherValue = params.someValue end end
As you can see, there is a missing end
keyword for the doSomething
function, and this is clearly revealed when the code is properly indented.
Many code editors can help you by automatically indenting code and, when you close a block, it will back out one level and put the end
in the correct place. Each operating system has a selection of code editors, and better editors feature Lua syntax highlighting. For a list of recommended editors, please see our installation guides for macOS or Windows.
Another important aspect is naming variables and functions so that you and others know what they mean. Consider these two blocks of code:
local function a( b ) local c = b.target if c.x < 100 then c.alpha = 0 end return true end
local function touchEventHandler( event ) local target = event.target if target.x < 100 then target.alpha = 0 end return true end
These functions are identical in behavior, but by using clearer variable and function names, most developers can easily figure out what you're trying to accomplish.
Some developers are dismayed to find that their project works in the Simulator but not on their actual device. Usually, one of the following factors is at fault:
File names are not titleimage.png
and TitleImage.PNG
are the same in the Simulator, but they are considered different on a device.
There is an error in the build.settings
file, and often this is because the various blocks and tables are not properly nested. Please see the Project Build Settings guide for instructions on how to properly construct the build.settings
file and each section within it.
There's a problem with a plugin. Each plugin must be included exactly as specified, and several plugins require device permissions in order to function properly. Please see the appropriate plugin documentation for details.
In some cases, an event listener function may still be running when you attempt to remove the listener, or a Solar2D library will be completing an internal process when you attempt to call a certain action. For example, you may attempt to remove a GPS listener while the GPS event is still being processed, or you may try to deactivate a physics body at the exact moment it collides with another object. Both of these are liable to cause an error/warning.
The solution to these type of issues is to perform the necessary action after a short timer delay:
local function handleGPS( event ) timer.performWithDelay( 10, function() Runtime:removeEventListener( "location", handleGPS ); end ) end
If you have exhausted all of the above debugging methods, proceed to the Forums. There you can search for topics which describe the same issue you're struggling with. Often, another developer will have reported the same issue and, in many cases, the thread will contain the solution — as such, please do not post a new topic without first searching for the answer in existing threads.
Note that new contributors will have their first post moderated — these will be entered into the system, but they will not be visible to others until approved by a moderator.
If you cannot locate the solution in any previous forum threads, please post a new request and follow these guidelines:
Do not post the same topic multiple times. Also, choose the most appropriate
Include enough information so that the community can help. You should provide the following:
When including code within a forum post, surround it with [lua] [/lua]
tags for clarity and readability:
[lua] ... [/lua]