diff --git a/ChaosBot/ChaosBot.csproj b/ChaosBot/ChaosBot.csproj index c1612dd..82c074e 100644 --- a/ChaosBot/ChaosBot.csproj +++ b/ChaosBot/ChaosBot.csproj @@ -16,9 +16,9 @@ - + diff --git a/ChaosBot/Discord/Services/CommandHandler.cs b/ChaosBot/Discord/Services/CommandHandler.cs index 4ed20bd..1c370ea 100644 --- a/ChaosBot/Discord/Services/CommandHandler.cs +++ b/ChaosBot/Discord/Services/CommandHandler.cs @@ -192,7 +192,7 @@ namespace ChaosBot.Discord.Services { if (!command.IsSpecified) { - LoggingFacade.Error($"Command execution failed [{context.User.Username}#{context.User.Discriminator} -> {command.Value.Name}]"); + LoggingFacade.Error($"Command execution failed [{context.User.Username}#{context.User.Discriminator} -> {context.Message.Content.Split(" ").First()}]"); return; } diff --git a/ChaosBot/Discord/Services/CustomCommandHandler.cs b/ChaosBot/Discord/Services/CustomCommandHandler.cs index b1e3bd3..da64597 100644 --- a/ChaosBot/Discord/Services/CustomCommandHandler.cs +++ b/ChaosBot/Discord/Services/CustomCommandHandler.cs @@ -6,7 +6,6 @@ using ChaosBot.Services; using ChaosBot.Services.ProgrammingLanguageInterpreter; using Discord; using Discord.Commands; -using Neo.IronLua; namespace ChaosBot.Discord.Services { diff --git a/ChaosBot/Services/ProgrammingLanguageInterpreter/IProgrammingLanguageInterpreter.cs b/ChaosBot/Services/ProgrammingLanguageInterpreter/IProgrammingLanguageInterpreter.cs index a9828fd..ac8f6ff 100644 --- a/ChaosBot/Services/ProgrammingLanguageInterpreter/IProgrammingLanguageInterpreter.cs +++ b/ChaosBot/Services/ProgrammingLanguageInterpreter/IProgrammingLanguageInterpreter.cs @@ -1,7 +1,10 @@ +using System.Threading; +using Discord.Commands; + namespace ChaosBot.Services.ProgrammingLanguageInterpreter { public interface IProgrammingLanguageInterpreter { - string Interpret(string content, string command); + string Interpret(CancellationToken ct, SocketCommandContext context, string content, string command); } } diff --git a/ChaosBot/Services/ProgrammingLanguageInterpreter/LuaProgrammingLanguageInterpreter.cs b/ChaosBot/Services/ProgrammingLanguageInterpreter/LuaProgrammingLanguageInterpreter.cs index 8da885d..bd55f54 100644 --- a/ChaosBot/Services/ProgrammingLanguageInterpreter/LuaProgrammingLanguageInterpreter.cs +++ b/ChaosBot/Services/ProgrammingLanguageInterpreter/LuaProgrammingLanguageInterpreter.cs @@ -1,46 +1,64 @@ -using System; +using System.Linq; +using System.Reflection; using System.Text; -using System.Text.RegularExpressions; -using Neo.IronLua; -using NLog; +using System.Threading; +using Discord.Commands; +using NLua; namespace ChaosBot.Services.ProgrammingLanguageInterpreter { internal class LuaProgrammingLanguageInterpreter : IProgrammingLanguageInterpreter { - private static readonly ILogger Logger = Program.GetLogger(); private readonly StringBuilder _outputBuilder = new StringBuilder(); - - public string Interpret(string content, string command) + private CancellationToken _ct; + private Lua _state; + + public string Interpret(CancellationToken ct, SocketCommandContext context, string content, string command) { - using (Lua lua = new Lua()) + LoggingFacade.Debug($"Interpreting code for {command} using Lua"); + LoggingFacade.Trace($"Using CancellationToken: {ct}"); + _ct = ct; + LoggingFacade.Trace("Starting Lua interpreter"); + _state = new Lua(); + LoggingFacade.Trace("Setting Lua debug hook"); + _state.SetDebugHook(KeraLua.LuaHookMask.Line, 0); + _state.DebugHook += LuaDebugHook; + + MethodInfo printMethod = GetType().GetMethod("Print", BindingFlags.NonPublic | BindingFlags.Instance); + LoggingFacade.Trace($"Overwriting print function with {printMethod}"); + _state["print"] = _state.RegisterFunction("print", this, printMethod); + LoggingFacade.Trace("Adding command context to environment"); + _state["context"] = context; + + LoggingFacade.Trace("Disabling Lua `import` function"); + _state.DoString ("\nimport = function () end\n"); + LoggingFacade.Trace("Disabling Lua `metatable` functionality"); + _state.DoString ("\ngetmetatable = function () end\n"); + _state.DoString ("\nsetmetatable = function () end\n"); + + LoggingFacade.Trace("Running user Lua code"); + _state.DoString(content, command); + + LoggingFacade.Trace("Returning Lua output"); + return _outputBuilder.ToString().Trim(); + } + + private void LuaDebugHook(object sender, NLua.Event.DebugHookEventArgs e) + { + if (_ct.IsCancellationRequested) { - // This needs to be dynamic if we want to call - // functions from within the lua environment - // This is a runtime type check - dynamic env = lua.CreateEnvironment(); - - ParamsDelegate printDel = Print; - env.print = printDel; - - env.dochunk(content, $"{command}.lua"); - - return _outputBuilder.ToString(); + Lua l = (Lua) sender; + l.State.Error("Timeout kill"); } } - private delegate void ParamsDelegate(params object[] parameters); - + // This is being used in the `Interpret` function to register the print function + // ReSharper disable once UnusedMember.Local private void Print(params object[] parameters) { - StringBuilder sb = new StringBuilder(); + string str = string.Join("\t", from param in parameters select param == null ? string.Empty : param.ToString()); - foreach (object parameter in parameters) - { - sb.Append(parameter); - } - - _outputBuilder.Append(sb); + _outputBuilder.AppendLine(str); } } } diff --git a/ChaosBot/Services/ProgrammingLanguageInterpreter/ProgrammingLanguageInterpreterFacade.cs b/ChaosBot/Services/ProgrammingLanguageInterpreter/ProgrammingLanguageInterpreterFacade.cs index e48d28a..59f85ce 100644 --- a/ChaosBot/Services/ProgrammingLanguageInterpreter/ProgrammingLanguageInterpreterFacade.cs +++ b/ChaosBot/Services/ProgrammingLanguageInterpreter/ProgrammingLanguageInterpreterFacade.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using ChaosBot.Models; using Discord.Commands; @@ -19,7 +21,23 @@ namespace ChaosBot.Services.ProgrammingLanguageInterpreter try { - string output = interpreter.Interpret(customCommand.Content, customCommand.Command); + CancellationTokenSource tokenSource = new CancellationTokenSource(); + Task task = Task.Run(() => + { + Thread.CurrentThread.Priority = ThreadPriority.Lowest; + return interpreter.Interpret(tokenSource.Token, context, customCommand.Content, customCommand.Command); + }, tokenSource.Token); + + const int timeout = 250; + bool isTaskCompleted = task.Wait(TimeSpan.FromMilliseconds(timeout)); + + if (!isTaskCompleted) + { + tokenSource.Cancel(); + throw new TimeoutException($"Command '{customCommand.Command}' took more than {timeout}ms to run. It has been killed."); + } + + string output = task.Result; if (output.Length > 0) context.Channel.SendMessageAsync(output); @@ -29,6 +47,7 @@ namespace ChaosBot.Services.ProgrammingLanguageInterpreter } catch (Exception ex) { + LoggingFacade.Exception(ex); errorReason = $"There was an error with your code ({ex.GetType().Name}): {ex.Message}"; return false; }