Code red - A guide to runtime errors
Compiler errors can be annoying, but at least they're extremely easy to discover and usually fairly simple to fix. There is a more sinister breed of error message, one that lurks beyond the friendly visage of Dream Maker's interface, one that only dares to rear its ugly, red, monospaced head in the wild lands of Dream Seeker (insert ominous crash of thunder here). Runtime errors are the bane of programmers the world over, so it's lucky you've got me to tell you how to deal with them!
Important note: This article/tutorial assumes that you already have beginning-to-intermediate knowledge of DM.
General Debugging Tips
When debugging runtime errors, there are a number of points to keep in mind.
- Activate Dream Maker's debug mode. From within Dream Maker, pull down the Build menu and select "Preferences for [your game's name here]". Tick the "Debug mode" box and press OK. Runtime errors will now show the DM file and line number where they occurred, making them much easier to track down and fix.
- Read the entire runtime error. All that information is not just padding! Simply thinking about what the error message could be trying to tell you can often lead to a solution.
- There are many types of runtime error, and the methods for dealing with them vary greatly. (I will be providing a list for reference at the end of this article.)
- Deadron has written a useful <a href="http://www.byondscape.com/ascape.dmb/Deadron.2002-0118/" target="_blank">BYONDscape article about debugging in general.
Now proceed to our resident lingual specialist (me) for a session of Dream-Seeker-orientated technobabble training.
Speak Like a Geek
This is a typical run-time error message:
runtime error: Division by zero
proc name: test (/mob/verb/test)
source file: test.dm,3
usr: Crispy (/mob)
src: Crispy (/mob)
Crispy (/mob): test()
The first order of business is to determine what type of error it is; in this case, division by zero. That helps a lot, but we need more information than that to fix the error. To the second line, then; and here we have the name of the proc in which the error occurred. It's the test() verb, which belongs to all mobs (hence /mob/verb/test). That helps, but the more specific we can get about the exact location of the error, the better. That's why the third line is included. It states the file (test.dm) and the line number (3) on which the error occurred, which is very useful. (If you don't have the "source file" line in your runtime error messages, see the first dot point in the previous section.)
Now we get into the latter part of the error message. Note that the values of src and usr are displayed; in this case they're the same, but that of course won't always be the case. Then we have the call stack. Here it only contains one line, which represents the "call" that the player made when they used the verb. If test() had been called from another proc, there would be more lines.
Now we know where the error message is, we can fix it. In this case, it's a simple matter of going to line 3 of test.dm and finding out why a division by zero is happening. Usually it will be because you're dividing by a variable which has a value of zero; so you have to find out why it's zero, and prevent it from being zero in the future.
My call stack is bigger than yours!
The division by zero example isn't particularly useful when looking at the call stack, so here's a different one:
runtime error: Cannot read null.x
proc name: dd get step (/proc/dd_get_step)
source file: Geography.dm,88
usr: Crispy (/mob)
dd get step(null, 1)
Crispy (/mob): test()
The error message shows that Dream Seeker tried to read a var called x from a var that references an object, but couldn't because that var was set to null. For example, it might have tried to read O.x but then found that O was null. Note that the proc name is now dd_get_step(), and it's a global proc (as it starts with /proc; see the second line of the error message). We are also told that the error occurred in Geography.dm at line 88. With a bit of logic, some guesswork, and some fact checking, we can work out that Geography.dm is part of Deadron's Geography library. Does that mean that Deadron's Geography library is buggy? Well, maybe, but it's worth looking a little further before falsely reporting a bug. This is where the call stack comes into play.
The first line of the call stack is, as you might expect, the crashed proc - dd_get_step(). The second line refers to test() again, indicating that test() called dd_get_step(). Aha! A clue. (Incidentally, the "Crispy (/mob)" part shows what object the test() proc belongs to, with its name and type. The "dd get step" line does not have a part like this, because it's a global proc.).
Now look at the arguments that were passed into dd_get_step(); null, and 1. It would help to know what arguments dd_get_step() expects; we could look at the proc's definition, but why figure out for yourself when there's a manual? Deadron has documented the Geography library - which contains the dd_get_step() proc - so let's check the relevant documentation.
- dd_get_step(object, direction)
- Returns the turf in the specified direction (NORTH, SOUTHWEST, etc) from the object. Accepts any object with x, y, z variables defining its location. Provided because the BYOND get_step() function only takes obj or mob as an argument.
You can see that the first argument is "object". Obviously we don't want this to be null, as it's rather critical to the operation of dd_get_step() that it has a proper value!
So we need to avoid "null" being passed in as the first argument. Let's see how test() is calling dd_get_step().
src << dd_get_step(src.MyObject,NORTH)
If "null" is being passed as the first argument, and "MyObject" is being supplied as that argument, then it follows that the MyObject var must be null. And there's the problem. If this was a real piece of code, you'd now have to make sure that MyObject wasn't null, and do something about it when it was (perhaps display an error message, or just fail silently). But that's another matter.
So, now you've had a basic run-down of how to, well, run-down runtime errors. So I'll round off this article by giving you a nice reference list of runtime errors.
Runtime Error Messages
This isn't a completely comprehensive list, but it lists many of the more common runtime errors (and some of the less common ones). Note
: Errors like "Maximum hop count exceeded" aren't really runtime errors - they look a bit like them, but they have no call stack and are caused for different reasons - so this list does not include them.
Can't find your runtime error in this list? Do a <a href="http://developer.byond.com/forum/index.cgi?action=forum_search_compose" target="_blank">forum search on it!
|Cannot read/modify null.something
||This is (arguably) the most common runtime error. You tried to access or modify a var or proc belonging to an object, but the object reference was null. For example, you tried to access O.somevar but the O var was null.
Note that if you're trying to access src.somevar or src.someproc() from within a Del() proc, make sure that your parent call (the ..() line) is at the end rather than at the beginning, or src will be deleted before try to you access src.somevar or call src.someproc(). Also note that src.client is sometimes null in mob/Logout(); try using client/Del() instead of mob/Logout() if you need to access anything belonging to the client.
This often happens when you set up a proc to receive an argument, but when you call it forget to pass that argument in. DeathCheck() procs commonly experience this problem.
Several common variations of this error are listed below.
||You tried to perform a mathematic operation that doesn't make sense. For example, addition involving a number and a text string, or any other two var types that don't mix easily. (Hint: Use num2text() and text2num() to convert between numbers and text.)
Another possible cause of a type mismatch error is that you tried to add something to a list, but the list wasn't actually created. See "Cannot read null.Find()" below for ways to properly define a list.
|Cannot read null.Enter()
This could be happening for the same reasons as above; or you might have called Move() with no arguments or with a non-atom argument. For example, it will occur if you're passing coordinates into Move(); in which case you should use locate() to get the turf at those coordinates and pass that in to Move() instead.
|<a name="error_null_list">Cannot read/modify null.Find(), null.Add(), null.Copy(), null.Cut(), null.Remove(), null.len
You probably tried to perform a Find(), Add(), Copy(), Cut(), or Remove() operation on an uninitialised list, or tried to read the "len" var of an uninitialised list. To avoid this, create lists in one of the following ways (this may not be advisable if the var belongs to an object; see below):
var/list/MyList = new()
var/list/MyList = list()
(The zero is important in that last one.) Any of those methods will create the list as well as defining it. The crucial thing to remember about lists is that there's a difference between an empty list (one with nothing in it) and a null list (one which does not exist). Think of it as the difference between an empty bag and a non-existent bag; you can put things into an empty bag, but the bag has to actually exist first!
Note that if you have many lists that you're not using (for example, all mobs have a list but only a few of them actually use it), consider creating the list only when you need it. The more unused blank lists that DM has to keep track of, the slower your game will run.
Important note: If you have a number of objects and want them to have a list (the contents of which varies from instance to instance of that object; for example, an NPC's most hated players list, in which each NPC records who they have grudges against), create the lists in New() (at run-time) instead of at compile-time. Otherwise, all of those objects will share the same list!
If you get this error when no lists are involved, then refer to "Cannot read null.something", above.
You used the : (colon) operator instead of the . (period, or dot) operator to access a variable, and the variable hasn't been defined; or you used the dot operator on a variable, but the variable does not hold an object of the type that the compiler expects it to be. You should always use the dot operator, and if the type of an object is in doubt be sure to use istype() to check its type. If using the dot operator generates a compiler error, then "type cast" the variable to the correct type, like this:
set src in usr
// Set a /mob/player var to usr; this is called type casting usr to /mob/player
// Make sure the usr is a /mob/player
// Note the use of the single-argument form of istype()
// The usr isn't a player, so don't continue
usr << "You can't use this verb, you're not a /mob/player!"
// This bit only happens if the usr is a /mob/player
P << P.infotext
|Undefined proc or verb
||Same as above, except that you're trying to call a proc or verb rather than access a variable. The same rules about the : (colon) and . (period, or dot) operators apply.
|Bad icon operation
||You tried to do something weird with the /icon type. This error message can be particularly annoying; because of a quirk in Dream Seeker, it often doesn't give any source file or line number information. You can still use the call stack to find out which proc caused the error, though.
|List index out of bounds
||You tried to access something from a list, but the list index you used doesn't exist. List indexes must be larger than or equal to 1, and less than or equal to the length of the list. This error can also occur if you use the pick() proc on a list that doesn't have anything in it.
||You tried to access something from a list, but the value you gave for the list index can't be a list index (for example; a non-integer number, some text, or an object). Alternatively, you tried to use a list index with something that isn't a list; if M is a mob, and you try to access M, you'll get this error.
||You might receive this error message if, for example, you displayed an alert() or input() dialog to a mob that didn't have a client. Remember that alert() and input() are directed to usr by default, so if you've left it at the default make sure that usr is really what you want. (For more information about usr, see Lummox JR's BYONDscape article <a href="http://www.byondscape.com/ascape.dmb/LummoxJR.2002-1104/" target="_blank">usr Unfriendly.)
||This probably means that you tried to delete something that can't be deleted, such as a number or a text string or a type path. If you're trying to delete a type path, you probably want to use some form of locate(), or a for() loop, in order to find objects of that type.
||You tried to call a non-existent proc. For example, calling ...() (with three periods rather than two) causes this.
|<a name="error_cannotremovefromlist">Cannot remove from list
||You tried to remove something from a list, but can't. Attempting to remove a type path from a "contents" list (either using
contents.Remove(/mob/sometype)) would trigger this; in that case, you probably want to use something like
src.contents-=locate(/mob/sometype) in src instead.
|Bad text or out of bounds
This often happens when you try and use copytext() on a string that's too short; for example,
copytext("hello",8,1) will generate this error because there's only five characters in "hello" and you're trying to get the eighth one.
||You tried to do a mathematical operation on something that isn't a number. For example, maybe you multiplied an object by another object, or tried to calculate the value of src divided by a number, or subtracted a text string from usr, or divided something by null.
|Maximum recursion level reached
||You have an infinite loop, or a proc is calling itself over and over again. Firstly, don't do as the error message suggests and set world.loop_checks=0. That's a very bad idea even if you know what you're doing, because the infinite loop would still be going, and would most likely slow down your computer something nasty. Instead, look at why your code is looping infinitely, and stop it from doing so. In some cases, you might actually want a proc to call itself over and over again, every X seconds; in that case, use spawn() to avoid the error and prevent the proc slowing down your computer too much.
If this error is happening inside a Stat() proc, you've probably defined a stat using Stat() instead of stat(). Remember that DM is case-sensitive!
|Cannot create objects of type /type/path
||You tried to create a new object using a var for its type (e.g.
new MyTypePath(src)), but the var you used (in that example, MyTypePath) is not a type path but an object, or a string containing a non-existent type path, or a number, or anything else that isn't a valid type path.
|Division by zero
||You tried to divide by zero. Often this means that you tried to divide something by a var, and the value of that var was zero.