Skip to content

Guide to Debugging

Intro

Got a bug and you’re unable to find it by just looking at your code? Try debugging! This guide will teach you the basics of debugging, how to read the values and some tips and tricks. It will be written as a chronological story. Where the chapters explain the next part of the debugging process.

Be sure to look at Getting Started if you’re new and need help with setting up your repo. Do also remember that all below here is how I do it. There are many ways but I find that this works for me.

What Is Debugging

“Debugging is the process of detecting and removing of existing and potential errors (also called as “bugs”) in a software code that can cause it to behave unexpectedly or crash.” (source)

As you can see from this quote. It is a very broad term. This guide will use a code debugger to step through your code and look at what is happening.

How To Debug

We will be using #15958 as an example issue.

Finding The Issue

First of all, you need to understand what is happening functionally. This usually gives you hints as to where it goes wrong.

Looking at the GitHub issue we can see that the author (luckily) wrote a clear reproduction. Without one we’d only be guessing as to where it goes wrong exactly. Here a tripwire mine activates when it is in a container such as a closed locker or a bag.

This gives us the hint that the trigger mechanism does not check if the object is directly on a turf. Using this hint we go look for the proc which causes the trigger to happen. Using my previous guide’s advice we quickly find /obj/item/assembly/infra.

image

In the current file of /obj/item/assembly/infra, “infrared.dm”, we can see a lot of different procs. We’re only really interested in the proc which triggers the bomb.

image

We see that the proc toggle_secure starts and stops processing of the object. This gives us a hint as to where the triggering happens. Looking at /obj/item/assembly/infra/process we see that it creates beams when it is active. Those beams are functionally used to trigger the bomb itself.

image

Looking at the /obj/effect/beam/i_beam object we see that this is indeed the case.

image

/obj/effect/beam/i_beam/proc/hit() is defined here which calls trigger_beam on master. hit() in term is called whenever something Bumped or Crossed the beam. I found this by right clicking on the hit proc and choosing Find All References

image

image

So now we know what is causing the triggering of the bomb. We know that beams are sent when the bomb is active. And we functionally know that these beams are also sent when the bomb is hidden in a locker or bag.

Breakpoints

Now we know what is happening we can start debugging. I have a suspicion already of what is the cause of the issue. Namely that the infrared emitter does not check the loc of the actual bomb in the /obj/item/assembly/infra/process() proc.

To confirm this I will place a breakpoint just when the process proc begins. You do this by clicking just left of the line number where you want to put the breakpoint.

image

The red dot there is a breakpoint that is set. Clicking it again removes it.

After doing this we will follow the reproduction steps. Once the game hits your breakpoint it will freeze your game and your VS Code instance should pop up to the front. If not just open VS Code.

When testing this I noticed that the breakpoint got hit multiple times before I could do my reproduction. If that is the case then you have multiple options for combating this annoyance. Either you disable the breakpoint and re-enable it when you are ready to test. This is not a great way of doing it for this case but in some cases, this is enough. Or you move the breakpoint to a better location. I choose this one and I’ve moved it a bit lower.

image

I moved it past the if(!on) check which eliminates all trip wires which are not turned on. (Quick note. This is really bad code. They should not be processing when they are not turned on)

I spawned a premade grenade instead of making my own grenade here. Saves me quite some time and annoyance in trying to look up how to do this again. Remember the search results when looking for the trip mine? Yeah, use one of those.

In Debug Mode

Once I turn on the bomb I notice that VS Code indeed pops up to the front. The game is now paused till you say it can continue.

image

The breakpoint line is now highlighted. The highlight shows where the code is currently. It is about to do the if(!secured) check. Let’s make the code execute that one step by stepping over the line. F10 as a shortcut or you can press the “Step Over” button in your active debugger window.

image

Now the code is executed and the highlight moved to the next line. Step over is handy to quickly move over your code. It will jump over any proc call. Step Into (F11) is for when you want to actually step into the proc call. This will be explained later when it is needed.

If you step over a bunch of times you will see that it will go and create the i_beam.

image

Even though I am currently holding the bomb in my hands (same situation as when it is in a bag/locker code-wise). Why is this the case? In the code, you can see that a beam is created when the bomb is on, secured and first and last are both null. These all have nothing to do with our issue. But the last check checks if T is not null. T is defined earlier in the proc as get_turf(src).

image

In other words. T is the turf below my characters feet. And of course, this one does exist in this case. What is missing here is a check to see if the actual bomb is on a turf. How do we even check this?

Time to go look for a reference to the actual bomb as /obj/item/assembly/infra is just the mechanism used by the bomb. /obj/item/assembly/infra itself is a subtype of /obj/item/assembly which is the base type that is used for bomb mechanisms. So we best look at the definition of /obj/item/assembly. We do this by Ctrl-clicking on the assembly part of /obj/item/assembly/infra.

image

Here we see the definition. We can see that /obj/item/assembly has a variable called holder which is of type /obj/item/assembly_holder. This is most likely the thing we want.

Now to check if this is correct. Go to your debug window and open Arguments and then src. src is of course the object we currently are. Which is the /obj/item/assembly/infra. Once it is open you can see a LOT of variables.

image

We are not interested in most of them and instead, we want to find holder

image

Yep, this is the one we want. Now how do we check if this object is directly on a turf? How do we even check its location? The answer is loc. loc contains the location of the object. Here I found out that the loc of the assembly_holder wasn’t actually my character but instead it was the grenade. Good thing we checked further before we started coding right?

image

Finding/Making The Right Variable To Use

Now it becomes a bit odd. Chemistry bomb code is fairly … bad. But we can make this work. First, we go look a bit more into the code to find the proper variable to use. We can use holder.loc.loc but that says very little about what it actually means. It would cause even more headaches for people working with the code in the future. Instead, we will help our future co-developers a bit and look into improving the existing code. Later on, we also see that this is not the correct way of fixing it fully.

Let’s take a look at the assembly code in assembly.dm. Let’s see how the assembly determines what its grenade is. When looking around the file I found the proc /obj/item/assembly/proc/pulse(radio = FALSE). This one seems promising.

image

Here we can see that either the holder is used or if loc is a grenade it will be primed using prime. As you can see a previous coder even stated that this is a hack. This however does give me an idea of how to handle this and the edge cases that exist. Namely the case where a grenade owns the trip laser as a mechanism.

The idea I had in mind is to create a proc which returns the physical outer object. The payload, grenade or such. Where does this proc go? Well in the assembly file since it will be usable for other code as well. I just put it at the bottom of the file since nowhere else seemed to fit better. I quickly found that I needed some more info for this. What if the holder was not attached yet? Or it is remotely attached to a bomb? For this I made a proc for the /obj/item/assembly_holder object which will return the actual outermost object.

image

I found out that master is the bomb linked by the line at the top of the image. This all allows me to complete my other proc.

image

Now we have a proper method to use to find the outermost object.

Applying The Fix And Testing It

Now we can go and fix the issue at hand. We want to check if the outermost object its loc is a turf. If not it should not fire new lasers and kill the old ones.

We already have the turf that the /obj/item/assembly/infra is located on. Now we just have to check if that turf is the same turf as our outermost object.

image

Now, this should work. But you of course have to test it! Build it and run the game. Run it without a breakpoint set first to see if it works functionally.

And it seems to work! Now let’s trip it and see if it actually keeps working.

Runtimes And Stacktrace Traveling

And the game froze. VS Code began blinking. What is happening? The game ran into a runtime exception

image

Now let’s see if this is actually our fault or not. Since we did not touch any timers. To check this go to the debug window and open the call stack.

image

Here you can see ALL the current “threads” of the game. DM itself is not a multithreaded language but it can have multiple threads. I won’t go into detail on that here for simplicity sake. The top item is the one you are currently on. Just click on it to open it.

image

Here we can see the entire stack trace of our current thread. The stack trace is the entire path the code took thus far. At the top is the last called proc and at the bottom is the origin of the call. Clicking on each stack will jump you to the location in the code. The message said that addtimer was called on a QDELETED item. This means that the item it is made for is already deleted. Lets click on /obj/item/assembly/infra/trigger_beam() in the stack trace to go to that call location.

image

Here we see where it went wrong. It seems that addtimer(CALLBACK(src, .proc/process_cooldown), 10) is called even though the src is already deleted. How can this happen? To save us all some time. pulse is the proc which triggers the bomb. In our case our bomb exploded, destroying itself. Now… is this our fault? No. But we can fix it nonetheless.

image

This code change will ensure the message is sent and that the runtime stops from happening.

Stepping Into VS Stepping Over

Now back to the testing. Once build again start up the game again and try it this time using a raw /obj/item/assembly/infra. As you can see it works only when you hold it. But not when it is on the floor itself. Seems we made a mistake! Place a breakpoint again in the /obj/item/assembly/infra/process() proc since there something goes wrong.

image

Now we want to step into the current line. This means that we will go into get_outer_object. Press either F11 or the “Step Into” button.

image

This will get us to the proc itself. Now step over till you see it return loc. loc here is the turf. Which is not the outermost object. Seems we need to do another check there. This almost certainly also goes for /obj/item/assembly_holder/proc/get_outer_object() as we used mostly the same logic there.

image

Outro

Now let’s test again. And it seems that there are more issues! The assembly_holder still shoots a laser if you hold it or put it in a bag. Same for the infra itself. Now I know the fix already but where is the fun in that.

I’m leaving the last solution (and honour of making the PR that solves the issue) open for you! If you feel like testing yourself then please pick up this issue and fix it the way you think would work. I’d love to see your method!

Please also let me know what you think of this format of doing a guide. This one was more a look into how I do it step by step compared to a more structured guide.