Feature Implementation In Large Unfamiliar Codebases

I feel like every contribution I make to the SourceMod project leaves me on a wild goose-chase following class hierarchies and I always end up with about 10-20 tabs open of different files in Notepad++ before I have a clue about what I need to do. Luckily this time, I decided to write down some of this goose-chase. Here goes.

In SourceMod errors are loud and halt execution. This is by design, as any error that may occur during plugin execution is nearly always the programmer’s fault (even if it really isn’t, the author should be doing proper checking). So if you wanted a stack trace and to halt a function’s execution you’d use the native SetFailState with some useful error message.

However, in SourceMod (pre this pull request) there’s no way of grabbing a stack trace from a point in time without halting function execution. This would be useful for reporting errors or debugging, but you still want the rest of your function call to be completed.

In an effort to stay organized I drafted what I’d like to see from an input -> output point of view and here’s what I came up with:

The following code would output two stack traces. One will be from the native I intend to make RequestStackTrace() & the already existing SetFailState().

#include <sourcemod>

public void OnPluginStart()
{
    SomeFunction();
}

void SomeFunction()
{
    SomeFunction2();
}

void SomeFunction2()
{
    RequestStackTrace("RequestStackTrace %i %.2f", 4, 10.0);
    SetFailState("SetFailState %i %.2f", 4, 10.0);
}

Which would output the following into the error logs

L 09/17/2017 - 15:07:55: [SM] Stack trace requested: RequestStackTrace 4 10.00
L 09/17/2017 - 15:07:55: [SM] Call stack trace:
L 09/17/2017 - 15:07:55: [SM] [0] RequestStackTrace
L 09/17/2017 - 15:07:55: [SM] [1] Line 15, C:\Users\Micha\Desktop\sourcemod\sourcemod\build\package\addons\sourcemod\Test.sp::SomeFunction2
L 09/17/2017 - 15:07:55: [SM] [2] Line 10, C:\Users\Micha\Desktop\sourcemod\sourcemod\build\package\addons\sourcemod\Test.sp::SomeFunction
L 09/17/2017 - 15:07:55: [SM] [3] Line 5, C:\Users\Micha\Desktop\sourcemod\sourcemod\build\package\addons\sourcemod\Test.sp::OnPluginStart
L 09/17/2017 - 15:07:55: [SM] Exception reported: SetFailState 4 10.00
L 09/17/2017 - 15:07:55: [SM] Blaming: test.smx
L 09/17/2017 - 15:07:55: [SM] Call stack trace:
L 09/17/2017 - 15:07:55: [SM] [0] SetFailState
L 09/17/2017 - 15:07:55: [SM] [1] Line 16, C:\Users\Micha\Desktop\sourcemod\sourcemod\build\package\addons\sourcemod\Test.sp::SomeFunction2
L 09/17/2017 - 15:07:55: [SM] [2] Line 10, C:\Users\Micha\Desktop\sourcemod\sourcemod\build\package\addons\sourcemod\Test.sp::SomeFunction
L 09/17/2017 - 15:07:55: [SM] [3] Line 5, C:\Users\Micha\Desktop\sourcemod\sourcemod\build\package\addons\sourcemod\Test.sp::OnPluginStart

It’s beautiful isn’t it? Well, now that we have expected usage and output, we can actually implement this. First, do you remember earlier I said that I decided to write down my goose-chase? Well, here is my step by step mental process, before I hit a huge roadblock.

Trying to solve https://bugs.alliedmods.net/show_bug.cgi?id=4317 (Dump call stack)

Found DebugReporter.h in core/logic/ and found IFrameIterator ; https://github.com/alliedmodders/sourcemod/blob/master/core/logic/DebugReporter.h#L45

Looked at the definition for DebugReport::ReportError ; https://github.com/alliedmodders/sourcemod/blob/master/core/logic/DebugReporter.cpp#L158

Looking to either create 'DebugReport::GetTrace(IFrameIterator)', but the problem is that the trace is logged step by step, and not cached at all.
This means that DebugReport::GetTrace would have to return a 2d char array. One column per line of the stack trace. Then I'd have to change DebugReport::ReportError
and have it just loop that array and throw it's contents to logs.

This adds an extra step for DebugReport::ReportError, but also allows us to make stack traces more dynamically. In fact, the presence of a bool 'isIntentional' could
allow us to determine if "Stack trace requested: %s" should be used instead of "Exception reported: %s"

 

This is all fine and dandy, most of what I said ended up being what I used in my actual implementation. The problem, however, is that in order for the stack trace to be built we need a class from the interface sp::IFrameIterator, but as you can see if you have a keen eye IFrameIterator is a part of sourcepawn, and there is no actual implementation IFrameIterator anywhere inside of SourceMod, nor are there any interfaces to GET a plugin’s FrameIterator at runtime. Fuck.

I had a discussion with VoiDeD, a software developer who is quite keen on the SourceMod project. If you’d like to read the entire conversation, I’m not going to stop you, but here’s the tldr: I originally wanted to copy the definition of FrameIterator to SourceMod core, but was told that the better option is to expose/define CreateFrameIterator & DestoryFrameIterator methods inside of sourcepawn, and call those to grab the FrameIterator I need inside of SourceMod.

So, I did and the rest was just a matter of programming it. If you’d like to see the pull requests for sourcemod and sourcepawn they’re here and here, respectively. Otherwise that’s about it. I’m positive there will be more wild goose-chases in my future, but I just hope to god I end up gaining some familiarity. Even if it’s one pr at a time…

Leave a Reply

Your email address will not be published. Required fields are marked *