From 28885df94d003bd2570df110a6e2486c76580181 Mon Sep 17 00:00:00 2001 From: Vurv <56230599+Vurv78@users.noreply.github.com> Date: Sun, 31 Jul 2022 09:52:55 -0700 Subject: [PATCH 1/2] Add runString(s) --- .../gmod_wire_expression2/core/selfaware.lua | 46 +++++++++++++++++++ lua/wire/client/e2descriptions.lua | 1 + 2 files changed, 47 insertions(+) diff --git a/lua/entities/gmod_wire_expression2/core/selfaware.lua b/lua/entities/gmod_wire_expression2/core/selfaware.lua index bb3f6e2301..83ba52b3fd 100644 --- a/lua/entities/gmod_wire_expression2/core/selfaware.lua +++ b/lua/entities/gmod_wire_expression2/core/selfaware.lua @@ -243,3 +243,49 @@ end e2function number hash( string str ) return getHash( self, str ) end + +local PreProcessor, Tokenizer, Parser, Optimizer, Compiler = E2Lib.PreProcessor.Execute, E2Lib.Tokenizer.Execute, E2Lib.Parser.Execute, E2Lib.Optimizer.Execute, E2Lib.Compiler.Execute +local raise = E2Lib.raiseException + +--- Runs E2 code from a string. Be sure to use try {} catch() {} if you want to handle the errors +local fmt = string.format + +__e2setcost(500) +e2function void runString(string code) + local chip = self.entity + + -- Have a check just in case tick quota isn't enough (servers have it really high) + self.data.runstring_stack = (self.data.runstring_stack or 0) + 1 + if self.data.runstring_stack >= 50 then + error("runString stack overflow") + end + + + local status, directives, code = PreProcessor(code, nil, self) + if not status then return raise( fmt("Preprocessor Error [%s]", directives), 2, self.trace) end + + local status, tokens = Tokenizer(code) + if not status then return raise( fmt("Tokenizer Error [%s]", tokens), 2, self.trace) end + + local status, tree, dvars = Parser(tokens) + if not status then return raise( fmt("Parser Error [%s]", tree), 2, self.trace) end + + status, tree = Optimizer(tree) + if not status then return raise( fmt("Optimizer Error [%s]", tree), 2, self.trace) end + + local status, script, inst = Compiler(tree, chip.inports[3], chip.outports[3], chip.persists and chip.persists[3] or {}, dvars, chip.includes) + + if not status then return raise( fmt("Compiler Error [%s]", script), 2, self.trace) end + + self:PushScope() + -- pcall so we can pop the scope + local success, why = pcall( script[1], self, script ) + self:PopScope() + + if not success then + -- Failed, throw the error back to the error handler + error(why, 0) + end + + self.data.runstring_stack = self.data.runstring_stack - 1 +end \ No newline at end of file diff --git a/lua/wire/client/e2descriptions.lua b/lua/wire/client/e2descriptions.lua index 81dd0abf02..daa705742b 100644 --- a/lua/wire/client/e2descriptions.lua +++ b/lua/wire/client/e2descriptions.lua @@ -849,6 +849,7 @@ E2Helper.Descriptions["ioOutputEntities(s)"] = "Returns an array of all entities E2Helper.Descriptions["runOnLast(n)"] = "If set to 1, the chip will run once when it is removed, setting the last() flag when it does" E2Helper.Descriptions["selfDestruct()"] = "Removes the expression" E2Helper.Descriptions["selfDestructAll()"] = "Removes the expression and all constrained props" +E2Helper.Descriptions["runString(s)"] = "Runs E2 code from a string, in a local scope. It still has access to all of your functions and directive vars, so do not trust user input with this!" -- Debug E2Helper.Descriptions["playerCanPrint()"] = "Returns whether or not the next print-message will be printed or omitted by antispam" From fc4bd9eda45b41b1e7daf062bf671fc1cc4831c4 Mon Sep 17 00:00:00 2001 From: Vurv <56230599+Vurv78@users.noreply.github.com> Date: Sun, 31 Jul 2022 15:15:14 -0700 Subject: [PATCH 2/2] Make quota much more strict * Changed from 500 to 1000 base ops cost * Add prf based on tree size post parsing / optimziing * Modify Tokenizer/Parser internals to optionally take a hook to call internally, used to add ops for each token/node (and be able to abort mid-parsing/tokenizing). * Add half an op every 2 characters input in runString (running 500 char code will cost an extra 250 ops) * Fix stack not decreasing on error --- .../gmod_wire_expression2/base/parser.lua | 48 ++++++++++++---- .../gmod_wire_expression2/base/tokenizer.lua | 55 ++++++++++++++----- .../gmod_wire_expression2/core/selfaware.lua | 32 ++++++++--- 3 files changed, 102 insertions(+), 33 deletions(-) diff --git a/lua/entities/gmod_wire_expression2/base/parser.lua b/lua/entities/gmod_wire_expression2/base/parser.lua index 41d79cebb9..792ff55f25 100644 --- a/lua/entities/gmod_wire_expression2/base/parser.lua +++ b/lua/entities/gmod_wire_expression2/base/parser.lua @@ -104,7 +104,10 @@ function Parser:Error(message, token) end end -function Parser:Process(tokens, params) +---@alias ParserConfig { each_hook: fun()? } + +---@param config ParserConfig +function Parser:Process(tokens, config) self.tokens = tokens self.index = 0 self.count = #tokens @@ -112,7 +115,7 @@ function Parser:Process(tokens, params) self.includes = {} self:NextToken() - local tree = self:Root() + local tree = self:Root(config and config.each_hook) if parserDebug:GetBool() then print(E2Lib.AST.dump(tree)) end @@ -211,30 +214,51 @@ end local loopdepth -function Parser:Root() +---@param hook fun()? +function Parser:Root(hook) loopdepth = 0 return self:Stmts() end -function Parser:Stmts() +---@param hook fun()? +function Parser:Stmts(hook) local trace = self:GetTokenTrace() local stmts = self:Instruction(trace, "seq") if not self:HasTokens() then return stmts end - while true do - if self:AcceptRoamingToken("com") then - self:Error("Statement separator (,) must not appear multiple times") + if hook then + while true do + if self:AcceptRoamingToken("com") then + self:Error("Statement separator (,) must not appear multiple times") + end + + hook() + stmts[#stmts + 1] = self:Stmt1() + + if not self:HasTokens() then break end + + if not self:AcceptRoamingToken("com") then + if self.readtoken[3] == false then + self:Error("Statements must be separated by comma (,) or whitespace") + end + end end + else + while true do + if self:AcceptRoamingToken("com") then + self:Error("Statement separator (,) must not appear multiple times") + end - stmts[#stmts + 1] = self:Stmt1() + stmts[#stmts + 1] = self:Stmt1() - if not self:HasTokens() then break end + if not self:HasTokens() then break end - if not self:AcceptRoamingToken("com") then - if self.readtoken[3] == false then - self:Error("Statements must be separated by comma (,) or whitespace") + if not self:AcceptRoamingToken("com") then + if self.readtoken[3] == false then + self:Error("Statements must be separated by comma (,) or whitespace") + end end end end diff --git a/lua/entities/gmod_wire_expression2/base/tokenizer.lua b/lua/entities/gmod_wire_expression2/base/tokenizer.lua index dcfda9f34a..d6d07bc5d7 100644 --- a/lua/entities/gmod_wire_expression2/base/tokenizer.lua +++ b/lua/entities/gmod_wire_expression2/base/tokenizer.lua @@ -21,7 +21,10 @@ function Tokenizer:Error(message, offset) error(message .. " at line " .. self.tokenline .. ", char " .. (self.tokenchar + (offset or 0)), 0) end -function Tokenizer:Process(buffer, params) +---@alias TokenizerConfig { each_hook: fun()? } + +---@param config TokenizerConfig +function Tokenizer:Process(buffer, config) self.buffer = buffer self.length = buffer:len() self.position = 0 @@ -32,26 +35,52 @@ function Tokenizer:Process(buffer, params) local tokenname, tokendata, tokenspace self.tokendata = "" - while self.character do - tokenspace = self:NextPattern("%s+") and true or false - - if not self.character then break end + if config and config.each_hook then + local hook = config.each_hook + while self.character do + tokenspace = self:NextPattern("%s+") and true or false - self.tokenline = self.readline - self.tokenchar = self.readchar - self.tokendata = "" + if not self.character then break end - tokenname, tokendata = self:NextSymbol() + self.tokenline = self.readline + self.tokenchar = self.readchar + self.tokendata = "" - if tokenname == nil then - tokenname, tokendata = self:NextOperator() + tokenname, tokendata = self:NextSymbol() if tokenname == nil then - self:Error("Unknown character found (" .. self.character .. ")") + tokenname, tokendata = self:NextOperator() + + if tokenname == nil then + self:Error("Unknown character found (" .. self.character .. ")") + end end + + hook() + tokens[#tokens + 1] = { tokenname, tokendata, tokenspace, self.tokenline, self.tokenchar } end + else + while self.character do + tokenspace = self:NextPattern("%s+") and true or false + + if not self.character then break end + + self.tokenline = self.readline + self.tokenchar = self.readchar + self.tokendata = "" - tokens[#tokens + 1] = { tokenname, tokendata, tokenspace, self.tokenline, self.tokenchar } + tokenname, tokendata = self:NextSymbol() + + if tokenname == nil then + tokenname, tokendata = self:NextOperator() + + if tokenname == nil then + self:Error("Unknown character found (" .. self.character .. ")") + end + end + + tokens[#tokens + 1] = { tokenname, tokendata, tokenspace, self.tokenline, self.tokenchar } + end end return tokens diff --git a/lua/entities/gmod_wire_expression2/core/selfaware.lua b/lua/entities/gmod_wire_expression2/core/selfaware.lua index 83ba52b3fd..cc53e3a70c 100644 --- a/lua/entities/gmod_wire_expression2/core/selfaware.lua +++ b/lua/entities/gmod_wire_expression2/core/selfaware.lua @@ -245,36 +245,52 @@ e2function number hash( string str ) end local PreProcessor, Tokenizer, Parser, Optimizer, Compiler = E2Lib.PreProcessor.Execute, E2Lib.Tokenizer.Execute, E2Lib.Parser.Execute, E2Lib.Optimizer.Execute, E2Lib.Compiler.Execute -local raise = E2Lib.raiseException +local raise_exception = E2Lib.raiseException --- Runs E2 code from a string. Be sure to use try {} catch() {} if you want to handle the errors local fmt = string.format -__e2setcost(500) +__e2setcost(1000) e2function void runString(string code) local chip = self.entity -- Have a check just in case tick quota isn't enough (servers have it really high) self.data.runstring_stack = (self.data.runstring_stack or 0) + 1 - if self.data.runstring_stack >= 50 then + if self.data.runstring_stack >= 30 then error("runString stack overflow") end + local function raise(msg, level, trace) + self.data.runstring_stack = self.data.runstring_stack - 1 + raise_exception(msg, level, trace) + end + + self.data.runstring_config = self.data.runstring_config or { + each_hook = function() + self.prf = self.prf + 50 + end + } + + local default_config = self.data.runstring_config + + self.prf = self.prf + #code / 2 local status, directives, code = PreProcessor(code, nil, self) if not status then return raise( fmt("Preprocessor Error [%s]", directives), 2, self.trace) end - local status, tokens = Tokenizer(code) + local status, tokens = Tokenizer(code, default_config) if not status then return raise( fmt("Tokenizer Error [%s]", tokens), 2, self.trace) end - local status, tree, dvars = Parser(tokens) + local status, tree, dvars = Parser(tokens, default_config) if not status then return raise( fmt("Parser Error [%s]", tree), 2, self.trace) end + self.prf = self.prf + #tree * 20 + status, tree = Optimizer(tree) if not status then return raise( fmt("Optimizer Error [%s]", tree), 2, self.trace) end + self.prf = self.prf + #tree * 10 local status, script, inst = Compiler(tree, chip.inports[3], chip.outports[3], chip.persists and chip.persists[3] or {}, dvars, chip.includes) - if not status then return raise( fmt("Compiler Error [%s]", script), 2, self.trace) end self:PushScope() @@ -282,10 +298,10 @@ e2function void runString(string code) local success, why = pcall( script[1], self, script ) self:PopScope() + self.data.runstring_stack = self.data.runstring_stack - 1 + if not success then -- Failed, throw the error back to the error handler error(why, 0) end - - self.data.runstring_stack = self.data.runstring_stack - 1 end \ No newline at end of file