Skip to content

Commit

Permalink
Improved errors parsing code
Browse files Browse the repository at this point in the history
Refactored errors parsing code into a common utility
Added testing code for errors parsing
Bumped revision number
  • Loading branch information
danielga committed Aug 28, 2022
1 parent 4545a4a commit 406a86d
Show file tree
Hide file tree
Showing 9 changed files with 381 additions and 129 deletions.
33 changes: 25 additions & 8 deletions premake5.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ CreateWorkspace({name = "luaerror", abi_compatible = true})
IncludeDetouring()

files({
"source/main.cpp",
"source/server.cpp",
"source/server.hpp",
"source/shared.cpp",
"source/shared.hpp"
"source/shared/main.cpp",
"source/server/server.cpp",
"source/server/server.hpp",
"source/shared/shared.cpp",
"source/shared/shared.hpp",
"source/common/common.cpp",
"source/common/common.hpp"
})

CreateProject({serverside = false, manual_files = true})
Expand All @@ -36,7 +38,22 @@ CreateWorkspace({name = "luaerror", abi_compatible = true})
IncludeScanning()

files({
"source/main.cpp",
"source/shared.cpp",
"source/shared.hpp"
"source/shared/main.cpp",
"source/shared/shared.cpp",
"source/shared/shared.hpp",
"source/common/common.cpp",
"source/common/common.hpp"
})

project("testing")
kind("ConsoleApp")
includedirs("source/common")
files({
"source/common/common.hpp",
"source/common/common.cpp",
"source/testing/main.cpp"
})
vpaths({
["Header files/*"] = "source/**.hpp",
["Source files/*"] = "source/**.cpp"
})
106 changes: 106 additions & 0 deletions source/common/common.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#include "common.hpp"

#include <cctype>
#include <cstdlib>
#include <functional>
#include <regex>
#include <sstream>

namespace common
{

inline int32_t StringToInteger( const std::string &strint )
{
try
{
return std::stoi( strint );
}
catch( const std::exception & )
{
return 0;
}
}

inline std::string Trim( const std::string &s )
{
std::string c = s;
auto not_isspace = std::not_fn( isspace );
// remote trailing "spaces"
c.erase( std::find_if( c.rbegin( ), c.rend( ), not_isspace ).base( ), c.end( ) );
// remote initial "spaces"
c.erase( c.begin( ), std::find_if( c.begin( ), c.end( ), not_isspace ) );
return c;
}

bool ParseError( const std::string &error, ParsedError &parsed_error )
{
static const std::regex error_parts_regex(
"^(.+):(\\d+): (.+)$",
std::regex::ECMAScript | std::regex::optimize
);

std::smatch matches;
if( !std::regex_search( error, matches, error_parts_regex ) )
return false;

parsed_error.source_file = matches[1];
parsed_error.source_line = StringToInteger( matches[2] );
parsed_error.error_string = matches[3];
return true;
}

bool ParseErrorWithStackTrace( const std::string &error, ParsedErrorWithStackTrace &parsed_error )
{
std::istringstream error_stream( Trim( error ) );

std::string error_first_line;
if( !std::getline( error_stream, error_first_line ) )
return false;

ParsedErrorWithStackTrace temp_parsed_error;

{
static const std::regex client_error_addon_matcher(
"^\\[(.+)\\] ",
std::regex::ECMAScript | std::regex::optimize
);

std::smatch matches;
if( std::regex_search( error_first_line, matches, client_error_addon_matcher ) )
{
temp_parsed_error.addon_name = matches[1];
error_first_line.erase( 0, 1 + temp_parsed_error.addon_name.size( ) + 1 + 1 ); // [addon]:space:
}
}

if( !ParseError( error_first_line, temp_parsed_error ) )
temp_parsed_error.error_string = error_first_line;

while( error_stream )
{
static const std::regex frame_parts_regex(
"^\\s+(\\d+)\\. (.+) \\- (.+):(\\-?\\d+)$",
std::regex::ECMAScript | std::regex::optimize
);

std::string frame_line;
if( !std::getline( error_stream, frame_line ) )
break;

std::smatch matches;
if( !std::regex_search( frame_line, matches, frame_parts_regex ) )
return false;

temp_parsed_error.stack_trace.emplace_back( ParsedErrorWithStackTrace::StackFrame {
StringToInteger( matches[1] ),
matches[2],
matches[3],
StringToInteger( matches[4] )
} );
}

parsed_error = std::move( temp_parsed_error );
return true;
}

}
58 changes: 58 additions & 0 deletions source/common/common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#pragma once

#include <cstdint>
#include <string>
#include <vector>

namespace common
{

struct ParsedError
{
std::string source_file;
int32_t source_line = -1;
std::string error_string;

inline bool operator==( const ParsedError &rhs ) const
{
return source_file == rhs.source_file &&
source_line == rhs.source_line &&
error_string == rhs.error_string;
}
};

struct ParsedErrorWithStackTrace : public ParsedError
{
struct StackFrame
{
int32_t level = 0;
std::string name;
std::string source;
int32_t currentline = -1;

inline bool operator==( const StackFrame &rhs ) const
{
return level == rhs.level &&
name == rhs.name &&
source == rhs.source &&
currentline == rhs.currentline;
}
};

std::string addon_name;
std::vector<StackFrame> stack_trace;

inline bool operator==( const ParsedErrorWithStackTrace &rhs ) const
{
return source_file == rhs.source_file &&
source_line == rhs.source_line &&
error_string == rhs.error_string &&
addon_name == rhs.addon_name &&
stack_trace == rhs.stack_trace;
}
};

bool ParseError( const std::string &error, ParsedError &parsed_error );
bool ParseErrorWithStackTrace( const std::string &error, ParsedErrorWithStackTrace &parsed_error );

}
72 changes: 18 additions & 54 deletions source/server.cpp → source/server/server.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "server.hpp"
#include "shared.hpp"
#include "common/common.hpp"

#include <GarrysMod/Lua/Interface.h>
#include <GarrysMod/Lua/LuaInterface.h>
Expand Down Expand Up @@ -28,96 +28,60 @@ namespace server

static GarrysMod::Lua::ILuaInterface *lua = nullptr;

static std::regex client_error_addon_matcher( "^\\[(.+)\\] ", std::regex_constants::optimize );

typedef void ( *HandleClientLuaError_t )( CBasePlayer *player, const char *error );

static Detouring::Hook HandleClientLuaError_detour;

inline std::string Trim( const std::string &s )
{
std::string c = s;
auto not_isspace = std::not_fn( isspace );
// remote trailing "spaces"
c.erase( std::find_if( c.rbegin( ), c.rend( ), not_isspace ).base( ), c.end( ) );
// remote initial "spaces"
c.erase( c.begin( ), std::find_if( c.begin( ), c.end( ), not_isspace ) );
return c;
}

static void HandleClientLuaError_d( CBasePlayer *player, const char *error )
{
common::ParsedErrorWithStackTrace parsed_error;
if( !common::ParseErrorWithStackTrace( error, parsed_error ) )
return HandleClientLuaError_detour.GetTrampoline<HandleClientLuaError_t>( )( player, error );

const int32_t funcs = LuaHelpers::PushHookRun( lua, "ClientLuaError" );
if( funcs == 0 )
return HandleClientLuaError_detour.GetTrampoline<HandleClientLuaError_t>( )( player, error );

lua->GetField( GarrysMod::Lua::INDEX_GLOBAL, "Entity" );
if( !lua->IsType( -1, GarrysMod::Lua::Type::FUNCTION ) )
{
lua->Pop( funcs + 2 );
lua->Pop( funcs + 1 );
lua->ErrorNoHalt( "[ClientLuaError] Global Entity is not a function!\n" );
return HandleClientLuaError_detour.GetTrampoline<HandleClientLuaError_t>( )( player, error );
}
lua->PushNumber( player->entindex( ) );
lua->Call( 1, 1 );

std::string cleanerror = Trim( error );
std::string addon;
std::smatch matches;
if( std::regex_search( cleanerror, matches, client_error_addon_matcher ) )
{
addon = matches[1];
cleanerror.erase( 0, 1 + addon.size( ) + 1 + 1 ); // [addon]:space:
}

lua->PushString( cleanerror.c_str( ) );
lua->PushString( error );

std::istringstream errstream( cleanerror );
const int32_t errorPropsCount = shared::PushErrorProperties( lua, errstream );
lua->PushString( parsed_error.source_file.c_str( ) );
lua->PushNumber( parsed_error.source_line );
lua->PushString( parsed_error.error_string.c_str( ) );

lua->CreateTable( );
while( errstream )
for( const auto &stack_frame : parsed_error.stack_trace )
{
int32_t level = 0;
errstream >> level;

errstream.ignore( 2 ); // ignore ". "

std::string name;
errstream >> name;

errstream.ignore( 3 ); // ignore " - "

std::string source;
std::getline( errstream, source, ':' );

int32_t currentline = -1;
errstream >> currentline;

if( !errstream ) // it shouldn't have reached eof by now
break;

lua->PushNumber( level );
lua->PushNumber( stack_frame.level );
lua->CreateTable( );

lua->PushString( name.c_str( ) );
lua->PushString( stack_frame.name.c_str( ) );
lua->SetField( -2, "name" );

lua->PushNumber( currentline );
lua->PushNumber( stack_frame.currentline );
lua->SetField( -2, "currentline" );

lua->PushString( source.c_str( ) );
lua->PushString( stack_frame.source.c_str( ) );
lua->SetField( -2, "source" );

lua->SetTable( -3 );
}

if( addon.empty( ) )
if( parsed_error.addon_name.empty( ) )
lua->PushNil( );
else
lua->PushString( addon.c_str( ) );
lua->PushString( parsed_error.addon_name.c_str( ) );

if( !LuaHelpers::CallHookRun( lua, 4 + errorPropsCount, 1 ) )
if( !LuaHelpers::CallHookRun( lua, 7, 1 ) )
return HandleClientLuaError_detour.GetTrampoline<HandleClientLuaError_t>( )( player, error );

const bool proceed = !lua->IsType( -1, GarrysMod::Lua::Type::BOOL ) || !lua->GetBool( -1 );
Expand Down
File renamed without changes.
8 changes: 4 additions & 4 deletions source/main.cpp → source/shared/main.cpp
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
#include "shared.hpp"
#include "shared/shared.hpp"

#include <GarrysMod/Lua/Interface.h>

#if defined LUAERROR_SERVER

#include "server.hpp"
#include "server/server.hpp"

#endif

GMOD_MODULE_OPEN( )
{
LUA->CreateTable( );

LUA->PushString( "luaerror 1.5.7" );
LUA->PushString( "luaerror 1.5.8" );
LUA->SetField( -2, "Version" );

// version num follows LuaJIT style, xxyyzz
LUA->PushNumber( 10507 );
LUA->PushNumber( 10508 );
LUA->SetField( -2, "VersionNum" );

#if defined LUAERROR_SERVER
Expand Down
Loading

0 comments on commit 406a86d

Please sign in to comment.