├── .gitignore ├── azure-pipelines.yml ├── license.txt ├── premake5.lua ├── readme.md └── source ├── common ├── common.cpp └── common.hpp ├── server ├── server.cpp └── server.hpp ├── shared ├── main.cpp ├── shared.cpp └── shared.hpp └── testing └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | /projects/ 2 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | MODULE_NAME: luaerror 3 | DEPENDENCIES: $(System.DefaultWorkingDirectory)/dependencies 4 | GARRYSMOD_COMMON: $(System.DefaultWorkingDirectory)/dependencies/garrysmod_common 5 | GARRYSMOD_COMMON_BRANCH: master 6 | GARRYSMOD_COMMON_REPOSITORY: https://github.com/danielga/garrysmod_common.git 7 | PROJECT_GENERATOR_VERSION: 2 8 | REPOSITORY_DIR: $(System.DefaultWorkingDirectory) 9 | trigger: 10 | batch: true 11 | branches: 12 | include: 13 | - '*' 14 | tags: 15 | include: 16 | - '*' 17 | paths: 18 | include: 19 | - azure-pipelines.yml 20 | - premake5.lua 21 | - source 22 | jobs: 23 | - job: windows 24 | displayName: Windows 25 | pool: 26 | name: Azure Pipelines 27 | vmImage: windows-2022 28 | timeoutInMinutes: 10 29 | variables: 30 | BOOTSTRAP_URL: https://raw.githubusercontent.com/danielga/garrysmod_common/master/build/bootstrap.ps1 31 | BUILD_SCRIPT: $(System.DefaultWorkingDirectory)/dependencies/garrysmod_common/build/build.ps1 32 | COMPILER_PLATFORM: vs2022 33 | PROJECT_OS: windows 34 | PREMAKE5: $(System.DefaultWorkingDirectory)/dependencies/windows/premake-core/premake5.exe 35 | PREMAKE5_URL: https://github.com/danielga/garrysmod_common/releases/download/premake-build%2F5.0.0-beta2/premake-5.0.0-beta2-windows.zip 36 | steps: 37 | - checkout: self 38 | clean: true 39 | fetchDepth: 1 40 | submodules: recursive 41 | - powershell: 'Invoke-Expression ((New-Object System.Net.WebClient).DownloadString("$env:BOOTSTRAP_URL"))' 42 | displayName: Bootstrap 43 | - powershell: '& "$env:BUILD_SCRIPT"' 44 | displayName: Build 45 | - task: CopyFiles@2 46 | displayName: 'Copy files to $(Build.ArtifactStagingDirectory)' 47 | inputs: 48 | SourceFolder: '$(System.DefaultWorkingDirectory)/projects/windows/vs2022' 49 | Contents: '*/Release/*.dll' 50 | TargetFolder: '$(Build.ArtifactStagingDirectory)' 51 | CleanTargetFolder: true 52 | flattenFolders: true 53 | preserveTimestamp: true 54 | - publish: '$(Build.ArtifactStagingDirectory)' 55 | displayName: 'Publish Windows binaries' 56 | artifact: windows 57 | - job: linux 58 | displayName: Linux 59 | pool: 60 | name: Azure Pipelines 61 | vmImage: ubuntu-22.04 62 | container: 63 | image: danielga/steamrt-scout:latest 64 | options: -v /home 65 | timeoutInMinutes: 10 66 | variables: 67 | BOOTSTRAP_URL: https://raw.githubusercontent.com/danielga/garrysmod_common/master/build/bootstrap.sh 68 | BUILD_SCRIPT: $(System.DefaultWorkingDirectory)/dependencies/garrysmod_common/build/build.sh 69 | COMPILER_PLATFORM: gmake 70 | PREMAKE5: $(System.DefaultWorkingDirectory)/dependencies/linux/premake-core/premake5 71 | PROJECT_OS: linux 72 | PREMAKE5_URL: https://github.com/danielga/garrysmod_common/releases/download/premake-build%2F5.0.0-beta2/premake-5.0.0-beta2-linux.tar.gz 73 | CC: gcc-9 74 | CXX: g++-9 75 | AR: gcc-ar-9 76 | NM: gcc-nm-9 77 | RANLIB: gcc-ranlib-9 78 | steps: 79 | - checkout: self 80 | clean: true 81 | fetchDepth: 1 82 | submodules: recursive 83 | - bash: 'curl -s -L "$BOOTSTRAP_URL" | bash' 84 | displayName: Bootstrap 85 | - bash: '$BUILD_SCRIPT' 86 | displayName: Build 87 | - task: CopyFiles@2 88 | displayName: 'Copy files to $(Build.ArtifactStagingDirectory)' 89 | inputs: 90 | SourceFolder: '$(System.DefaultWorkingDirectory)/projects/linux/gmake' 91 | Contents: '*/Release/*.dll' 92 | TargetFolder: '$(Build.ArtifactStagingDirectory)' 93 | CleanTargetFolder: true 94 | flattenFolders: true 95 | preserveTimestamp: true 96 | - publish: '$(Build.ArtifactStagingDirectory)' 97 | displayName: 'Publish Linux binaries' 98 | artifact: linux 99 | - job: macosx 100 | displayName: macOS 101 | pool: 102 | name: Azure Pipelines 103 | vmImage: macOS-11 104 | timeoutInMinutes: 10 105 | variables: 106 | BOOTSTRAP_URL: https://raw.githubusercontent.com/danielga/garrysmod_common/master/build/bootstrap.sh 107 | BUILD_SCRIPT: $(System.DefaultWorkingDirectory)/dependencies/garrysmod_common/build/build.sh 108 | COMPILER_PLATFORM: gmake 109 | PREMAKE5: $(System.DefaultWorkingDirectory)/dependencies/macosx/premake-core/premake5 110 | PROJECT_OS: macosx 111 | PREMAKE5_URL: https://github.com/danielga/garrysmod_common/releases/download/premake-build%2F5.0.0-beta2/premake-5.0.0-beta2-macosx.tar.gz 112 | MACOSX_SDK_URL: https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.7.sdk.tar.xz 113 | MACOSX_SDK_DIRECTORY: $(System.DefaultWorkingDirectory)/dependencies/macosx/MacOSX10.7.sdk 114 | SDKROOT: $(System.DefaultWorkingDirectory)/dependencies/macosx/MacOSX10.7.sdk 115 | AR: ar 116 | steps: 117 | - checkout: self 118 | clean: true 119 | fetchDepth: 1 120 | submodules: recursive 121 | - bash: 'curl -s -L "$BOOTSTRAP_URL" | bash' 122 | displayName: Bootstrap 123 | - bash: | 124 | sudo xcode-select -s "/Applications/Xcode_11.7.app/Contents/Developer" 125 | $BUILD_SCRIPT 126 | displayName: Build 127 | - task: CopyFiles@2 128 | displayName: 'Copy files to $(Build.ArtifactStagingDirectory)' 129 | inputs: 130 | SourceFolder: '$(System.DefaultWorkingDirectory)/projects/macosx/gmake' 131 | Contents: '*/Release/*.dll' 132 | TargetFolder: '$(Build.ArtifactStagingDirectory)' 133 | CleanTargetFolder: true 134 | flattenFolders: true 135 | preserveTimestamp: true 136 | - publish: '$(Build.ArtifactStagingDirectory)' 137 | displayName: 'Publish macOS binaries' 138 | artifact: macosx 139 | - job: publish 140 | displayName: Publish to GitHub Releases 141 | pool: 142 | name: Azure Pipelines 143 | vmImage: ubuntu-22.04 144 | timeoutInMinutes: 5 145 | dependsOn: 146 | - windows 147 | - linux 148 | - macosx 149 | condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/')) 150 | steps: 151 | - download: current 152 | patterns: '**/*.dll' 153 | - task: GitHubRelease@1 154 | displayName: 'Publish GitHub release $(build.sourceBranchName)' 155 | inputs: 156 | gitHubConnection: 'GitHub danielga' 157 | releaseNotesSource: inline 158 | assets: '$(Pipeline.Workspace)/**/*.dll' 159 | addChangeLog: false 160 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | gm_luaerror 2 | A module for Garry's Mod that adds hooks for obtaining errors that 3 | happen on the client and server (if activated on server, it also 4 | pushes errors from clients). 5 | ----------------------------------------------------------------------- 6 | Copyright (c) 2015-2022, Daniel Almeida 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions 11 | are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright 14 | notice, this list of conditions and the following disclaimer. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright 17 | notice, this list of conditions and the following disclaimer in the 18 | documentation and/or other materials provided with the distribution. 19 | 20 | 3. Neither the name of the copyright holder nor the names of its 21 | contributors may be used to endorse or promote products derived from 22 | this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 28 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 29 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 30 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | -------------------------------------------------------------------------------- /premake5.lua: -------------------------------------------------------------------------------- 1 | PROJECT_GENERATOR_VERSION = 2 2 | 3 | newoption({ 4 | trigger = "gmcommon", 5 | description = "Sets the path to the garrysmod_common (https://github.com/danielga/garrysmod_common) directory", 6 | value = "path to garrysmod_common directory" 7 | }) 8 | 9 | include(assert(_OPTIONS.gmcommon or os.getenv("GARRYSMOD_COMMON"), 10 | "you didn't provide a path to your garrysmod_common (https://github.com/danielga/garrysmod_common) directory")) 11 | 12 | CreateWorkspace({name = "luaerror", abi_compatible = true}) 13 | CreateProject({serverside = true, manual_files = true}) 14 | IncludeLuaShared() 15 | IncludeHelpersExtended() 16 | IncludeSDKCommon() 17 | IncludeSDKTier0() 18 | IncludeSDKTier1() 19 | IncludeScanning() 20 | IncludeDetouring() 21 | 22 | files({ 23 | "source/shared/main.cpp", 24 | "source/server/server.cpp", 25 | "source/server/server.hpp", 26 | "source/shared/shared.cpp", 27 | "source/shared/shared.hpp", 28 | "source/common/common.cpp", 29 | "source/common/common.hpp" 30 | }) 31 | 32 | CreateProject({serverside = false, manual_files = true}) 33 | IncludeLuaShared() 34 | IncludeHelpersExtended() 35 | IncludeSDKCommon() 36 | IncludeSDKTier0() 37 | IncludeSDKTier1() 38 | IncludeScanning() 39 | IncludeDetouring() 40 | 41 | files({ 42 | "source/shared/main.cpp", 43 | "source/shared/shared.cpp", 44 | "source/shared/shared.hpp", 45 | "source/common/common.cpp", 46 | "source/common/common.hpp" 47 | }) 48 | 49 | project("testing") 50 | kind("ConsoleApp") 51 | includedirs("source/common") 52 | files({ 53 | "source/common/common.hpp", 54 | "source/common/common.cpp", 55 | "source/testing/main.cpp" 56 | }) 57 | vpaths({ 58 | ["Header files/*"] = "source/**.hpp", 59 | ["Source files/*"] = "source/**.cpp" 60 | }) 61 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # gm\_luaerror 2 | 3 | [![Build Status](https://metamann.visualstudio.com/GitHub%20danielga/_apis/build/status/danielga.gm_luaerror?branchName=master)](https://metamann.visualstudio.com/GitHub%20danielga/_build/latest?definitionId=9&branchName=master) 4 | 5 | A module for Garry's Mod that adds hooks for obtaining errors that happen on the client and server (if activated on server, it also pushes errors from clients). 6 | 7 | ## API reference 8 | 9 | luaerror.Version -- holds the luaerror module version in a string form 10 | luaerror.VersionNum -- holds the luaerror module version in a numeric form, LuaJIT style 11 | 12 | luaerror.EnableRuntimeDetour(boolean) -- enable/disable Lua runtime errors 13 | luaerror.EnableCompiletimeDetour(boolean) -- enable/disable Lua compiletime errors 14 | 15 | luaerror.EnableClientDetour(boolean) -- enable/disable Lua errors from clients (serverside only) 16 | -- returns nil followed by an error string in case of failure to detour 17 | 18 | Hooks: 19 | LuaError(isruntime, fullerror, sourcefile, sourceline, errorstr, stack) 20 | -- isruntime is a boolean saying whether this is a runtime error or not 21 | -- fullerror is a string which is the full error 22 | -- sourcefile is a string which is the source file of the error 23 | -- sourceline is a number which is the source line of the error 24 | -- errorstr is a string which is the error itself 25 | -- stack is a table containing the Lua stack at the time of the error 26 | 27 | ClientLuaError(player, fullerror, sourcefile, sourceline, errorstr, stack) 28 | -- player is a Player object which indicates who errored 29 | -- fullerror is a string which is the full error (trimmed and cleaned up) 30 | -- sourcefile is a string which is the source file of the error (may be nil) 31 | -- sourceline is a number which is the source line of the error (may be nil) 32 | -- errorstr is a string which is the error itself (may be nil) 33 | -- stack is a table containing the Lua stack at the time of the error 34 | -- sourcefile, sourceline and errorstr may be nil because of ErrorNoHalt and friends 35 | 36 | ## Compiling 37 | 38 | The only supported compilation platform for this project on Windows is **Visual Studio 2017** on **release** mode. However, it's possible it'll work with *Visual Studio 2015* and *Visual Studio 2019* because of the unified runtime. 39 | 40 | On Linux, everything should work fine as is, on **release** mode. 41 | 42 | For macOS, any **Xcode (using the GCC compiler)** version *MIGHT* work as long as the **Mac OSX 10.7 SDK** is used, on **release** mode. 43 | 44 | These restrictions are not random; they exist because of ABI compatibility reasons. 45 | 46 | If stuff starts erroring or fails to work, be sure to check the correct line endings (`\n` and such) are present in the files for each OS. 47 | 48 | ## Requirements 49 | 50 | This project requires [garrysmod\_common][1], a framework to facilitate the creation of compilations files (Visual Studio, make, XCode, etc). Simply set the environment variable `GARRYSMOD_COMMON` or the premake option `--gmcommon=path` to the path of your local copy of [garrysmod\_common][1]. 51 | 52 | We also use [SourceSDK2013][2]. The links to [SourceSDK2013][2] point to my own fork of VALVe's repo and for good reason: Garry's Mod has lots of backwards incompatible changes to interfaces and it's much smaller, being perfect for automated build systems like Azure Pipelines (which is used for this project). 53 | 54 | [1]: https://github.com/danielga/garrysmod_common 55 | [2]: https://github.com/danielga/sourcesdk-minimal 56 | -------------------------------------------------------------------------------- /source/common/common.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace common 10 | { 11 | 12 | inline int32_t StringToInteger( const std::string &strint ) 13 | { 14 | try 15 | { 16 | return std::stoi( strint ); 17 | } 18 | catch( const std::exception & ) 19 | { 20 | return 0; 21 | } 22 | } 23 | 24 | inline std::string Trim( const std::string &s ) 25 | { 26 | std::string c = s; 27 | auto not_isspace = std::not_fn( isspace ); 28 | // remote trailing "spaces" 29 | c.erase( std::find_if( c.rbegin( ), c.rend( ), not_isspace ).base( ), c.end( ) ); 30 | // remote initial "spaces" 31 | c.erase( c.begin( ), std::find_if( c.begin( ), c.end( ), not_isspace ) ); 32 | return c; 33 | } 34 | 35 | bool ParseError( const std::string &error, ParsedError &parsed_error ) 36 | { 37 | static const std::regex error_parts_regex( 38 | "^(.+):(\\d+): (.+)$", 39 | std::regex::ECMAScript | std::regex::optimize 40 | ); 41 | 42 | std::smatch matches; 43 | if( !std::regex_search( error, matches, error_parts_regex ) ) 44 | return false; 45 | 46 | parsed_error.source_file = matches[1]; 47 | parsed_error.source_line = StringToInteger( matches[2] ); 48 | parsed_error.error_string = matches[3]; 49 | return true; 50 | } 51 | 52 | bool ParseErrorWithStackTrace( const std::string &error, ParsedErrorWithStackTrace &parsed_error ) 53 | { 54 | std::istringstream error_stream( Trim( error ) ); 55 | 56 | std::string error_first_line; 57 | if( !std::getline( error_stream, error_first_line ) ) 58 | return false; 59 | 60 | ParsedErrorWithStackTrace temp_parsed_error; 61 | 62 | { 63 | static const std::regex client_error_addon_matcher( 64 | "^\\[(.+)\\] ", 65 | std::regex::ECMAScript | std::regex::optimize 66 | ); 67 | 68 | std::smatch matches; 69 | if( std::regex_search( error_first_line, matches, client_error_addon_matcher ) ) 70 | { 71 | temp_parsed_error.addon_name = matches[1]; 72 | error_first_line.erase( 0, 1 + temp_parsed_error.addon_name.size( ) + 1 + 1 ); // [addon]:space: 73 | } 74 | } 75 | 76 | if( !ParseError( error_first_line, temp_parsed_error ) ) 77 | temp_parsed_error.error_string = error_first_line; 78 | 79 | while( error_stream ) 80 | { 81 | static const std::regex frame_parts_regex( 82 | "^\\s+(\\d+)\\. (.+) \\- (.+):(\\-?\\d+)$", 83 | std::regex::ECMAScript | std::regex::optimize 84 | ); 85 | 86 | std::string frame_line; 87 | if( !std::getline( error_stream, frame_line ) ) 88 | break; 89 | 90 | std::smatch matches; 91 | if( !std::regex_search( frame_line, matches, frame_parts_regex ) ) 92 | return false; 93 | 94 | temp_parsed_error.stack_trace.emplace_back( ParsedErrorWithStackTrace::StackFrame { 95 | StringToInteger( matches[1] ), 96 | matches[2], 97 | matches[3], 98 | StringToInteger( matches[4] ) 99 | } ); 100 | } 101 | 102 | parsed_error = std::move( temp_parsed_error ); 103 | return true; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /source/common/common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace common 8 | { 9 | 10 | struct ParsedError 11 | { 12 | std::string source_file; 13 | int32_t source_line = -1; 14 | std::string error_string; 15 | 16 | inline bool operator==( const ParsedError &rhs ) const 17 | { 18 | return source_file == rhs.source_file && 19 | source_line == rhs.source_line && 20 | error_string == rhs.error_string; 21 | } 22 | }; 23 | 24 | struct ParsedErrorWithStackTrace : public ParsedError 25 | { 26 | struct StackFrame 27 | { 28 | int32_t level = 0; 29 | std::string name; 30 | std::string source; 31 | int32_t currentline = -1; 32 | 33 | inline bool operator==( const StackFrame &rhs ) const 34 | { 35 | return level == rhs.level && 36 | name == rhs.name && 37 | source == rhs.source && 38 | currentline == rhs.currentline; 39 | } 40 | }; 41 | 42 | std::string addon_name; 43 | std::vector stack_trace; 44 | 45 | inline bool operator==( const ParsedErrorWithStackTrace &rhs ) const 46 | { 47 | return source_file == rhs.source_file && 48 | source_line == rhs.source_line && 49 | error_string == rhs.error_string && 50 | addon_name == rhs.addon_name && 51 | stack_trace == rhs.stack_trace; 52 | } 53 | }; 54 | 55 | bool ParseError( const std::string &error, ParsedError &parsed_error ); 56 | bool ParseErrorWithStackTrace( const std::string &error, ParsedErrorWithStackTrace &parsed_error ); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /source/server/server.cpp: -------------------------------------------------------------------------------- 1 | #include "server.hpp" 2 | #include "common/common.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #undef isspace 23 | 24 | IVEngineServer *engine = nullptr; 25 | 26 | namespace server 27 | { 28 | 29 | static GarrysMod::Lua::ILuaInterface *lua = nullptr; 30 | 31 | typedef void ( *HandleClientLuaError_t )( CBasePlayer *player, const char *error ); 32 | 33 | static Detouring::Hook HandleClientLuaError_detour; 34 | 35 | static void HandleClientLuaError_d( CBasePlayer *player, const char *error ) 36 | { 37 | common::ParsedErrorWithStackTrace parsed_error; 38 | if( !common::ParseErrorWithStackTrace( error, parsed_error ) ) 39 | return HandleClientLuaError_detour.GetTrampoline( )( player, error ); 40 | 41 | const int32_t funcs = LuaHelpers::PushHookRun( lua, "ClientLuaError" ); 42 | if( funcs == 0 ) 43 | return HandleClientLuaError_detour.GetTrampoline( )( player, error ); 44 | 45 | lua->GetField( GarrysMod::Lua::INDEX_GLOBAL, "Entity" ); 46 | if( !lua->IsType( -1, GarrysMod::Lua::Type::FUNCTION ) ) 47 | { 48 | lua->Pop( funcs + 1 ); 49 | lua->ErrorNoHalt( "[ClientLuaError] Global Entity is not a function!\n" ); 50 | return HandleClientLuaError_detour.GetTrampoline( )( player, error ); 51 | } 52 | lua->PushNumber( player->entindex( ) ); 53 | lua->Call( 1, 1 ); 54 | 55 | lua->PushString( error ); 56 | 57 | lua->PushString( parsed_error.source_file.c_str( ) ); 58 | lua->PushNumber( parsed_error.source_line ); 59 | lua->PushString( parsed_error.error_string.c_str( ) ); 60 | 61 | lua->CreateTable( ); 62 | for( const auto &stack_frame : parsed_error.stack_trace ) 63 | { 64 | lua->PushNumber( stack_frame.level ); 65 | lua->CreateTable( ); 66 | 67 | lua->PushString( stack_frame.name.c_str( ) ); 68 | lua->SetField( -2, "name" ); 69 | 70 | lua->PushNumber( stack_frame.currentline ); 71 | lua->SetField( -2, "currentline" ); 72 | 73 | lua->PushString( stack_frame.source.c_str( ) ); 74 | lua->SetField( -2, "source" ); 75 | 76 | lua->SetTable( -3 ); 77 | } 78 | 79 | if( parsed_error.addon_name.empty( ) ) 80 | lua->PushNil( ); 81 | else 82 | lua->PushString( parsed_error.addon_name.c_str( ) ); 83 | 84 | if( !LuaHelpers::CallHookRun( lua, 7, 1 ) ) 85 | return HandleClientLuaError_detour.GetTrampoline( )( player, error ); 86 | 87 | const bool proceed = !lua->IsType( -1, GarrysMod::Lua::Type::BOOL ) || !lua->GetBool( -1 ); 88 | lua->Pop( 1 ); 89 | if( proceed ) 90 | return HandleClientLuaError_detour.GetTrampoline( )( player, error ); 91 | } 92 | 93 | LUA_FUNCTION_STATIC( EnableClientDetour ) 94 | { 95 | LUA->CheckType( 1, GarrysMod::Lua::Type::BOOL ); 96 | LUA->PushBool( LUA->GetBool( 1 ) ? 97 | HandleClientLuaError_detour.Enable( ) : 98 | HandleClientLuaError_detour.Disable( ) ); 99 | return 1; 100 | } 101 | 102 | void Initialize( GarrysMod::Lua::ILuaBase *LUA ) 103 | { 104 | lua = static_cast( LUA ); 105 | 106 | engine = InterfacePointers::VEngineServer( ); 107 | if( engine == nullptr ) 108 | LUA->ThrowError( "failed to retrieve server engine interface" ); 109 | 110 | const auto HandleClientLuaError = FunctionPointers::CBasePlayer_HandleClientLuaError( ); 111 | if( HandleClientLuaError == nullptr ) 112 | LUA->ThrowError( "unable to sigscan function HandleClientLuaError" ); 113 | 114 | if( !HandleClientLuaError_detour.Create( 115 | Detouring::Hook::Target( reinterpret_cast( HandleClientLuaError ) ), 116 | reinterpret_cast( &HandleClientLuaError_d ) 117 | ) ) 118 | LUA->ThrowError( "unable to create a hook for HandleClientLuaError" ); 119 | 120 | LUA->PushCFunction( EnableClientDetour ); 121 | LUA->SetField( -2, "EnableClientDetour" ); 122 | } 123 | 124 | void Deinitialize( GarrysMod::Lua::ILuaBase * ) 125 | { 126 | HandleClientLuaError_detour.Destroy( ); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /source/server/server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace GarrysMod 4 | { 5 | namespace Lua 6 | { 7 | class ILuaBase; 8 | } 9 | } 10 | 11 | namespace server 12 | { 13 | 14 | void Initialize( GarrysMod::Lua::ILuaBase *LUA ); 15 | void Deinitialize( GarrysMod::Lua::ILuaBase *LUA ); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /source/shared/main.cpp: -------------------------------------------------------------------------------- 1 | #include "shared/shared.hpp" 2 | 3 | #include 4 | 5 | #if defined LUAERROR_SERVER 6 | 7 | #include "server/server.hpp" 8 | 9 | #endif 10 | 11 | GMOD_MODULE_OPEN( ) 12 | { 13 | LUA->CreateTable( ); 14 | 15 | LUA->PushString( "luaerror 1.5.9" ); 16 | LUA->SetField( -2, "Version" ); 17 | 18 | // version num follows LuaJIT style, xxyyzz 19 | LUA->PushNumber( 10509 ); 20 | LUA->SetField( -2, "VersionNum" ); 21 | 22 | #if defined LUAERROR_SERVER 23 | 24 | server::Initialize( LUA ); 25 | 26 | #endif 27 | 28 | shared::Initialize( LUA ); 29 | 30 | LUA->SetField( GarrysMod::Lua::INDEX_GLOBAL, "luaerror" ); 31 | return 0; 32 | } 33 | 34 | GMOD_MODULE_CLOSE( ) 35 | { 36 | shared::Deinitialize( LUA ); 37 | 38 | #if defined LUAERROR_SERVER 39 | 40 | server::Deinitialize( LUA ); 41 | 42 | #endif 43 | 44 | LUA->PushNil( ); 45 | LUA->SetField( GarrysMod::Lua::INDEX_GLOBAL, "luaerror" ); 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /source/shared/shared.cpp: -------------------------------------------------------------------------------- 1 | #include "shared.hpp" 2 | #include "common/common.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | namespace shared 23 | { 24 | 25 | static bool runtime = false; 26 | static std::string runtime_error; 27 | static GarrysMod::Lua::AutoReference runtime_stack; 28 | static CFileSystem_Stdio *filesystem = nullptr; 29 | static bool runtime_detoured = false; 30 | static bool compiletime_detoured = false; 31 | static GarrysMod::Lua::CFunc AdvancedLuaErrorReporter = nullptr; 32 | static Detouring::Hook AdvancedLuaErrorReporter_detour; 33 | 34 | inline bool GetUpvalues( GarrysMod::Lua::ILuaInterface *lua, int32_t funcidx ) 35 | { 36 | if( funcidx < 0 ) 37 | funcidx = lua->Top( ) + funcidx + 1; 38 | 39 | int32_t idx = 1; 40 | const char *name = lua->GetUpvalue( funcidx, idx ); 41 | if( name == nullptr ) 42 | return false; 43 | 44 | // Keep popping until we either reach the end or until we reach a valid upvalue 45 | while( name[0] == '\0' ) 46 | { 47 | lua->Pop( 1 ); 48 | 49 | if( ( name = lua->GetUpvalue( funcidx, ++idx ) ) == nullptr ) 50 | return false; 51 | } 52 | 53 | lua->CreateTable( ); 54 | 55 | // Push the last upvalue to the top 56 | lua->Push( -2 ); 57 | // And remove it from its previous location 58 | lua->Remove( -3 ); 59 | 60 | do 61 | if( name[0] != '\0' ) 62 | lua->SetField( -2, name ); 63 | else 64 | lua->Pop( 1 ); 65 | while( ( name = lua->GetUpvalue( funcidx, ++idx ) ) != nullptr ); 66 | 67 | return true; 68 | } 69 | 70 | inline bool GetLocals( GarrysMod::Lua::ILuaInterface *lua, lua_Debug &dbg ) 71 | { 72 | int32_t idx = 1; 73 | const char *name = lua->GetLocal( &dbg, idx ); 74 | if( name == nullptr ) 75 | return false; 76 | 77 | // Keep popping until we either reach the end or until we reach a valid local 78 | while( name[0] == '(' ) 79 | { 80 | lua->Pop( 1 ); 81 | 82 | if( ( name = lua->GetLocal( &dbg, ++idx ) ) == nullptr ) 83 | return false; 84 | } 85 | 86 | lua->CreateTable( ); 87 | 88 | // Push the last local to the top 89 | lua->Push( -2 ); 90 | // And remove it from its previous location 91 | lua->Remove( -3 ); 92 | 93 | do 94 | if( name[0] != '(' ) 95 | lua->SetField( -2, name ); 96 | else 97 | lua->Pop( 1 ); 98 | while( ( name = lua->GetLocal( &dbg, ++idx ) ) != nullptr ); 99 | 100 | return true; 101 | } 102 | 103 | static void PushStackTable( GarrysMod::Lua::ILuaInterface *lua ) 104 | { 105 | lua->CreateTable( ); 106 | 107 | int32_t lvl = 0; 108 | lua_Debug dbg; 109 | while( lua->GetStack( lvl, &dbg ) == 1 && lua->GetInfo( "SfLlnu", &dbg ) == 1 ) 110 | { 111 | lua->PushNumber( ++lvl ); 112 | lua->CreateTable( ); 113 | 114 | if( GetUpvalues( lua, -4 ) ) 115 | lua->SetField( -2, "upvalues" ); 116 | 117 | if( GetLocals( lua, dbg ) ) 118 | lua->SetField( -2, "locals" ); 119 | 120 | lua->Push( -4 ); 121 | lua->SetField( -2, "func" ); 122 | 123 | lua->Push( -3 ); 124 | lua->SetField( -2, "activelines" ); 125 | 126 | lua->PushNumber( dbg.event ); 127 | lua->SetField( -2, "event" ); 128 | 129 | lua->PushString( dbg.name != nullptr ? dbg.name : "" ); 130 | lua->SetField( -2, "name" ); 131 | 132 | lua->PushString( dbg.namewhat != nullptr ? dbg.namewhat : "" ); 133 | lua->SetField( -2, "namewhat" ); 134 | 135 | lua->PushString( dbg.what != nullptr ? dbg.what : "" ); 136 | lua->SetField( -2, "what" ); 137 | 138 | lua->PushString( dbg.source != nullptr ? dbg.source : "" ); 139 | lua->SetField( -2, "source" ); 140 | 141 | lua->PushNumber( dbg.currentline ); 142 | lua->SetField( -2, "currentline" ); 143 | 144 | lua->PushNumber( dbg.nups ); 145 | lua->SetField( -2, "nups" ); 146 | 147 | lua->PushNumber( dbg.linedefined ); 148 | lua->SetField( -2, "linedefined" ); 149 | 150 | lua->PushNumber( dbg.lastlinedefined ); 151 | lua->SetField( -2, "lastlinedefined" ); 152 | 153 | lua->PushString( dbg.short_src ); 154 | lua->SetField( -2, "short_src" ); 155 | 156 | lua->SetTable( -5 ); 157 | 158 | // Pop activelines and func 159 | lua->Pop( 2 ); 160 | } 161 | } 162 | 163 | inline const IAddonSystem::Information *FindWorkshopAddonFromFile( const std::string &source ) 164 | { 165 | if( source.empty( ) || source == "[C]" ) 166 | return nullptr; 167 | 168 | const auto addons = filesystem->Addons( ); 169 | if( addons == nullptr ) 170 | return nullptr; 171 | 172 | return addons->FindFileOwner( source ); 173 | } 174 | 175 | LUA_FUNCTION_STATIC( AdvancedLuaErrorReporter_d ) 176 | { 177 | const char *errstr = LUA->GetString( 1 ); 178 | 179 | runtime = true; 180 | 181 | if( errstr != nullptr ) 182 | runtime_error = errstr; 183 | else 184 | runtime_error.clear( ); 185 | 186 | PushStackTable( static_cast( LUA ) ); 187 | runtime_stack.Create( ); 188 | 189 | return AdvancedLuaErrorReporter_detour.GetTrampoline( )( LUA->GetState( ) ); 190 | } 191 | 192 | class CLuaGameCallback : public GarrysMod::Lua::ILuaGameCallback 193 | { 194 | public: 195 | CLuaGameCallback( ) : 196 | lua( nullptr ), 197 | callback( nullptr ) 198 | { } 199 | 200 | ~CLuaGameCallback( ) 201 | { 202 | Reset( ); 203 | } 204 | 205 | GarrysMod::Lua::ILuaObject *CreateLuaObject( ) 206 | { 207 | return callback->CreateLuaObject( ); 208 | } 209 | 210 | void DestroyLuaObject( GarrysMod::Lua::ILuaObject *obj ) 211 | { 212 | callback->DestroyLuaObject( obj ); 213 | } 214 | 215 | void ErrorPrint( const char *error, bool print ) 216 | { 217 | callback->ErrorPrint( error, print ); 218 | } 219 | 220 | void Msg( const char *msg, bool useless ) 221 | { 222 | callback->Msg( msg, useless ); 223 | } 224 | 225 | void MsgColour( const char *msg, const Color &color ) 226 | { 227 | callback->MsgColour( msg, color ); 228 | } 229 | 230 | void LuaError( const CLuaError *error ) 231 | { 232 | const std::string &error_str = runtime ? runtime_error : error->message; 233 | 234 | common::ParsedError parsed_error; 235 | if( entered_hook || !common::ParseError( error_str, parsed_error ) ) 236 | return callback->LuaError( error ); 237 | 238 | const int32_t funcs = LuaHelpers::PushHookRun( lua, "LuaError" ); 239 | if( funcs == 0 ) 240 | return callback->LuaError( error ); 241 | 242 | lua->PushBool( runtime ); 243 | lua->PushString( error_str.c_str( ) ); 244 | 245 | lua->PushString( parsed_error.source_file.c_str( ) ); 246 | lua->PushNumber( parsed_error.source_line ); 247 | lua->PushString( parsed_error.error_string.c_str( ) ); 248 | 249 | if( runtime ) 250 | { 251 | runtime_stack.Push( ); 252 | runtime_stack.Free( ); 253 | } 254 | else 255 | PushStackTable( lua ); 256 | 257 | runtime = false; 258 | 259 | const auto source_addon = FindWorkshopAddonFromFile( parsed_error.source_file ); 260 | if( source_addon == nullptr ) 261 | { 262 | lua->PushNil( ); 263 | lua->PushNil( ); 264 | } 265 | else 266 | { 267 | lua->PushString( source_addon->title.c_str( ) ); 268 | lua->PushString( std::to_string( source_addon->wsid ).c_str( ) ); 269 | } 270 | 271 | entered_hook = true; 272 | const bool call_success = LuaHelpers::CallHookRun( lua, 8, 1 ); 273 | entered_hook = false; 274 | if( !call_success ) 275 | return callback->LuaError( error ); 276 | 277 | const bool proceed = !lua->IsType( -1, GarrysMod::Lua::Type::BOOL ) || !lua->GetBool( -1 ); 278 | lua->Pop( 1 ); 279 | if( proceed ) 280 | return callback->LuaError( error ); 281 | } 282 | 283 | void InterfaceCreated( GarrysMod::Lua::ILuaInterface *iface ) 284 | { 285 | callback->InterfaceCreated( iface ); 286 | } 287 | 288 | void SetLua( GarrysMod::Lua::ILuaInterface *iface ) 289 | { 290 | lua = static_cast( iface ); 291 | callback = lua->GetLuaGameCallback( ); 292 | } 293 | 294 | void Detour( ) 295 | { 296 | lua->SetLuaGameCallback( this ); 297 | } 298 | 299 | void Reset( ) 300 | { 301 | lua->SetLuaGameCallback( callback ); 302 | } 303 | 304 | private: 305 | GarrysMod::Lua::CLuaInterface *lua; 306 | GarrysMod::Lua::ILuaGameCallback *callback; 307 | bool entered_hook = false; 308 | }; 309 | 310 | static CLuaGameCallback callback; 311 | 312 | inline void DetourCompiletime( ) 313 | { 314 | if( compiletime_detoured ) 315 | return; 316 | 317 | if( !runtime_detoured ) 318 | callback.Detour( ); 319 | 320 | compiletime_detoured = true; 321 | } 322 | 323 | inline void ResetCompiletime( ) 324 | { 325 | if( !compiletime_detoured ) 326 | return; 327 | 328 | if( !runtime_detoured ) 329 | callback.Reset( ); 330 | 331 | compiletime_detoured = false; 332 | } 333 | 334 | inline void DetourRuntime( ) 335 | { 336 | if( runtime_detoured ) 337 | return; 338 | 339 | if( !compiletime_detoured ) 340 | callback.Detour( ); 341 | 342 | AdvancedLuaErrorReporter_detour.Enable( ); 343 | runtime_detoured = true; 344 | } 345 | 346 | inline void ResetRuntime( ) 347 | { 348 | if( !runtime_detoured ) 349 | return; 350 | 351 | if( !compiletime_detoured ) 352 | callback.Reset( ); 353 | 354 | AdvancedLuaErrorReporter_detour.Disable( ); 355 | runtime_detoured = false; 356 | } 357 | 358 | LUA_FUNCTION_STATIC( EnableRuntimeDetour ) 359 | { 360 | LUA->CheckType( 1, GarrysMod::Lua::Type::BOOL ); 361 | 362 | if( LUA->GetBool( 1 ) ) 363 | DetourRuntime( ); 364 | else 365 | ResetRuntime( ); 366 | 367 | LUA->PushBool( true ); 368 | return 1; 369 | } 370 | 371 | LUA_FUNCTION_STATIC( EnableCompiletimeDetour ) 372 | { 373 | LUA->CheckType( 1, GarrysMod::Lua::Type::BOOL ); 374 | 375 | if( LUA->GetBool( 1 ) ) 376 | DetourCompiletime( ); 377 | else 378 | ResetCompiletime( ); 379 | 380 | LUA->PushBool( true ); 381 | return 1; 382 | } 383 | 384 | LUA_FUNCTION_STATIC( FindWorkshopAddonFileOwnerLua ) 385 | { 386 | const char *path = LUA->CheckString( 1 ); 387 | 388 | const auto owner = FindWorkshopAddonFromFile( path ); 389 | if( owner == nullptr ) 390 | return 0; 391 | 392 | LUA->PushString( owner->title.c_str( ) ); 393 | LUA->PushString( std::to_string( owner->wsid ).c_str( ) ); 394 | return 2; 395 | } 396 | 397 | void Initialize( GarrysMod::Lua::ILuaBase *LUA ) 398 | { 399 | runtime_stack.Setup( LUA ); 400 | 401 | callback.SetLua( static_cast( LUA ) ); 402 | 403 | AdvancedLuaErrorReporter = FunctionPointers::AdvancedLuaErrorReporter( ); 404 | if( AdvancedLuaErrorReporter == nullptr ) 405 | LUA->ThrowError( "unable to obtain AdvancedLuaErrorReporter" ); 406 | 407 | if( !AdvancedLuaErrorReporter_detour.Create( 408 | reinterpret_cast( AdvancedLuaErrorReporter ), 409 | reinterpret_cast( &AdvancedLuaErrorReporter_d ) 410 | ) ) 411 | LUA->ThrowError( "unable to create a hook for AdvancedLuaErrorReporter" ); 412 | 413 | filesystem = static_cast( InterfacePointers::FileSystem( ) ); 414 | if( filesystem == nullptr ) 415 | LUA->ThrowError( "unable to initialize IFileSystem" ); 416 | 417 | LUA->PushCFunction( EnableRuntimeDetour ); 418 | LUA->SetField( -2, "EnableRuntimeDetour" ); 419 | 420 | LUA->PushCFunction( EnableCompiletimeDetour ); 421 | LUA->SetField( -2, "EnableCompiletimeDetour" ); 422 | 423 | LUA->PushCFunction( FindWorkshopAddonFileOwnerLua ); 424 | LUA->SetField( -2, "FindWorkshopAddonFileOwner" ); 425 | } 426 | 427 | void Deinitialize( GarrysMod::Lua::ILuaBase * ) 428 | { 429 | ResetRuntime( ); 430 | ResetCompiletime( ); 431 | AdvancedLuaErrorReporter_detour.Destroy( ); 432 | } 433 | 434 | } 435 | -------------------------------------------------------------------------------- /source/shared/shared.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace GarrysMod 4 | { 5 | namespace Lua 6 | { 7 | class ILuaBase; 8 | class ILuaInterface; 9 | } 10 | } 11 | 12 | namespace shared 13 | { 14 | 15 | void Initialize( GarrysMod::Lua::ILuaBase *LUA ); 16 | void Deinitialize( GarrysMod::Lua::ILuaBase *LUA ); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /source/testing/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | static bool test_parsed_error( const std::string &error, const common::ParsedError &control_parsed_error ) 6 | { 7 | common::ParsedError parsed_error; 8 | if( !common::ParseError( error, parsed_error ) ) 9 | return false; 10 | 11 | return parsed_error == control_parsed_error; 12 | } 13 | 14 | static bool test_parsed_error_with_stacktrace( const std::string &error, const common::ParsedErrorWithStackTrace &control_parsed_error ) 15 | { 16 | common::ParsedErrorWithStackTrace parsed_error; 17 | if( !common::ParseErrorWithStackTrace( error, parsed_error ) ) 18 | return false; 19 | 20 | return parsed_error == control_parsed_error; 21 | } 22 | 23 | int main( const int, const char *[] ) 24 | { 25 | const std::string error1 = "lua_run:1: '=' expected near ''"; 26 | const common::ParsedError control_parsed_error1 = 27 | { 28 | "lua_run", 29 | 1, 30 | "'=' expected near ''" 31 | }; 32 | if( !test_parsed_error( error1, control_parsed_error1 ) ) 33 | { 34 | printf( "Failed on test case 1!\n" ); 35 | return 5; 36 | } 37 | 38 | const std::string error2 = 39 | "\n" 40 | "[gcad] bad argument #3 to 'Add' (function expected, got nil)\n" 41 | " 1. Add - lua/includes/modules/hook.lua:31\n" 42 | " 2. unknown - addons/gcad/lua/gcad/ui/contextmenu/contextmenueventhandler.lua:150\n" 43 | " 3. dtor - addons/glib/lua/glib/oop/oop.lua:292\n" 44 | " 4. unknown - addons/gcad/lua/gcad/ui/contextmenu/contextmenueventhandler.lua:130\n" 45 | " 5. xpcall - [C]:-1\n" 46 | " 6. DispatchEvent - addons/glib/lua/glib/events/eventprovider.lua:86\n" 47 | " 7. UnloadSystem - addons/glib/lua/glib/stage1.lua:380\n" 48 | " 8. RunPackFile - addons/glib/lua/glib/loader/loader.lua:200\n" 49 | " 9. runNextPackFile - addons/glib/lua/glib/loader/loader.lua:494\n" 50 | " 10. callback - addons/glib/lua/glib/loader/loader.lua:497\n" 51 | " 11. RunPackFile - addons/glib/lua/glib/loader/loader.lua:296\n" 52 | " 12. runNextPackFile - addons/glib/lua/glib/loader/loader.lua:494\n" 53 | " 13. callback - addons/glib/lua/glib/loader/loader.lua:497\n" 54 | " 14. RunPackFile - addons/glib/lua/glib/loader/loader.lua:296\n" 55 | " 15. runNextPackFile - addons/glib/lua/glib/loader/loader.lua:494\n" 56 | " 16. callback - addons/glib/lua/glib/loader/loader.lua:497\n" 57 | "\n"; 58 | const common::ParsedErrorWithStackTrace control_parsed_error2 = 59 | { 60 | "", 61 | -1, 62 | "bad argument #3 to 'Add' (function expected, got nil)", 63 | "gcad", 64 | { 65 | { 1, "Add", "lua/includes/modules/hook.lua", 31 }, 66 | { 2, "unknown", "addons/gcad/lua/gcad/ui/contextmenu/contextmenueventhandler.lua", 150 }, 67 | { 3, "dtor", "addons/glib/lua/glib/oop/oop.lua", 292 }, 68 | { 4, "unknown", "addons/gcad/lua/gcad/ui/contextmenu/contextmenueventhandler.lua", 130 }, 69 | { 5, "xpcall", "[C]", -1 }, 70 | { 6, "DispatchEvent", "addons/glib/lua/glib/events/eventprovider.lua", 86 }, 71 | { 7, "UnloadSystem", "addons/glib/lua/glib/stage1.lua", 380 }, 72 | { 8, "RunPackFile", "addons/glib/lua/glib/loader/loader.lua", 200 }, 73 | { 9, "runNextPackFile", "addons/glib/lua/glib/loader/loader.lua", 494 }, 74 | { 10, "callback", "addons/glib/lua/glib/loader/loader.lua", 497 }, 75 | { 11, "RunPackFile", "addons/glib/lua/glib/loader/loader.lua", 296 }, 76 | { 12, "runNextPackFile", "addons/glib/lua/glib/loader/loader.lua", 494 }, 77 | { 13, "callback", "addons/glib/lua/glib/loader/loader.lua", 497 }, 78 | { 14, "RunPackFile", "addons/glib/lua/glib/loader/loader.lua", 296 }, 79 | { 15, "runNextPackFile", "addons/glib/lua/glib/loader/loader.lua", 494 }, 80 | { 16, "callback", "addons/glib/lua/glib/loader/loader.lua", 497 }, 81 | } 82 | }; 83 | if( !test_parsed_error_with_stacktrace( error2, control_parsed_error2 ) ) 84 | { 85 | printf( "Failed on test case 2!\n" ); 86 | return 5; 87 | } 88 | 89 | const std::string error3 = 90 | "\n" 91 | "[ERROR] CompileString:1: '=' expected near ''\n" 92 | " 1. unknown - lua_run:1\n" 93 | "\n"; 94 | const common::ParsedErrorWithStackTrace control_parsed_error3 = 95 | { 96 | "CompileString", 97 | 1, 98 | "'=' expected near ''", 99 | "ERROR", 100 | { 101 | { 1, "unknown", "lua_run", 1 } 102 | } 103 | }; 104 | if( !test_parsed_error_with_stacktrace( error3, control_parsed_error3 ) ) 105 | { 106 | printf( "Failed on test case 3!\n" ); 107 | return 5; 108 | } 109 | 110 | const std::string error4 = 111 | "\n" 112 | "[ERROR] lua_run:1: yes\n" 113 | " 1. error - [C]:-1\n" 114 | " 2. err - lua_run:1\n" 115 | " 3. unknown - lua_run:1\n" 116 | "\n"; 117 | const common::ParsedErrorWithStackTrace control_parsed_error4 = 118 | { 119 | "lua_run", 120 | 1, 121 | "yes", 122 | "ERROR", 123 | { 124 | { 1, "error", "[C]", -1 }, 125 | { 2, "err", "lua_run", 1 }, 126 | { 3, "unknown", "lua_run", 1 } 127 | } 128 | }; 129 | if( !test_parsed_error_with_stacktrace( error4, control_parsed_error4 ) ) 130 | { 131 | printf( "Failed on test case 4!\n" ); 132 | return 5; 133 | } 134 | 135 | const std::string error5 = 136 | "\n" 137 | "[ERROR] lua_run:1: '=' expected near ''\n" 138 | "\n"; 139 | const common::ParsedErrorWithStackTrace control_parsed_error5 = 140 | { 141 | "lua_run", 142 | 1, 143 | "'=' expected near ''", 144 | "ERROR", 145 | { } 146 | }; 147 | if( !test_parsed_error_with_stacktrace( error5, control_parsed_error5 ) ) 148 | { 149 | printf( "Failed on test case 5!\n" ); 150 | return 5; 151 | } 152 | 153 | printf( "Successfully ran all test cases!\n" ); 154 | return 0; 155 | } 156 | --------------------------------------------------------------------------------