Skip to content

Commit 1d727a0

Browse files
authored
Add regex checks to E2 functions + change trimming method for E2 processor and limit max chip size to 5mb (#3620)
* Add more regex checks to strings in E2 * Make whitespace trimming use another mechanism * Change trailing whitespace trimming way + limit max line lenght to 1m * Change trimming mechanism more * Useless check * Fix trim nil error * Fix e2 tests regression * Try optimizing FindComments * Limit max chip size to 5mb * Don't define table * Make the error clearer * Don't use hash table for this * Readd nil check
1 parent 35ff8ad commit 1d727a0

2 files changed

Lines changed: 145 additions & 61 deletions

File tree

lua/entities/gmod_wire_expression2/base/preprocessor.lua

Lines changed: 120 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ local type_map = {
6464
}
6565

6666
function PreProcessor:GetType(tp, trace)
67-
tp = tp:Trim():lower()
67+
tp = self:Trim(tp):lower()
6868
local up = tp:upper()
6969

7070
if tp == "normal" then
@@ -83,47 +83,110 @@ function PreProcessor:HandlePPCommand(comment, col)
8383
end
8484
end
8585

86+
function PreProcessor:Trim(line)
87+
local length = #line
88+
local first
89+
90+
for i = 1, length do
91+
local b = string.byte(line, i)
92+
93+
if b ~= 32 and (b < 9 or b > 13) then
94+
first = i
95+
break
96+
end
97+
end
98+
99+
if not first then
100+
return ""
101+
end
102+
103+
local last
104+
105+
for i = length, 1, -1 do
106+
local b = string.byte(line, i)
107+
108+
if b ~= 32 and (b < 9 or b > 13) then
109+
last = i
110+
break
111+
end
112+
end
113+
114+
return string.sub(line, first, last)
115+
end
116+
117+
function PreProcessor:TrimRight(line)
118+
for i = #line, 1, -1 do
119+
local b = string.byte(line, i)
120+
121+
if b ~= 32 and (b < 9 or b > 13) then
122+
return string.sub(line, 1, i)
123+
end
124+
end
125+
126+
-- The line consists only of spaces
127+
return ""
128+
end
129+
86130
function PreProcessor:FindComments(line)
87131
local isinput = not self.blockcomment and not self.multilinestring and line:match("^@inputs") ~= nil
88132
local isoutput = not self.blockcomment and not self.multilinestring and line:match("^@outputs") ~= nil
89133

90-
local ret, count, pos, found = {}, 0, 1
91-
repeat
92-
found = line:find((isinput or isoutput) and '[#"\\A-Z]' or '[#"\\]', pos)
93-
if found then -- We found something
94-
local char = line:sub(found, found)
95-
if (isinput or isoutput) and char:match("[A-Z]") ~= nil then -- we found the start of an input/output variable definition
96-
local varname, endpos = line:match("^([A-Z][A-Za-z0-9_]*)()",found)
97-
count = count + 1
98-
ret[count] = {type = isinput and "inputs" or "outputs", name=varname, pos=found, blockcomment = {}}
99-
pos = endpos
100-
elseif char == "#" then -- We found a comment
101-
local before = line:sub(found - 1, found - 1)
102-
if before == "]" then -- We found an ending
103-
count = count + 1
104-
ret[count] = { type = "end", pos = found - 1 }
105-
pos = found + 1
134+
local ret, count = {}, 0
135+
local len = #line
136+
local i = 1
137+
138+
while i <= len do
139+
local byte = string.byte(line, i)
140+
141+
-- We found the start of an input/output variable definition
142+
if (isinput or isoutput) and byte >= 65 and byte <= 90 then
143+
local start = i
144+
i = i + 1
145+
146+
while i <= len do
147+
local b = string.byte(line, i)
148+
149+
if (b >= 65 and b <= 90) or (b >= 97 and b <= 122) or (b >= 48 and b <= 57) or b == 95 then
150+
i = i + 1
106151
else
107-
local after = line:sub(found + 1, found + 1)
108-
if after == "[" then -- We found a start
109-
count = count + 1
110-
ret[count] = { type = "start", pos = found }
111-
pos = found + 2
112-
else -- We found a normal comment
113-
count = count + 1
114-
ret[count] = { type = "normal", pos = found }
115-
pos = found + 1
116-
end
152+
break
117153
end
118-
elseif char == '"' then -- We found a string
154+
end
155+
156+
local varname = string.sub(line, start, i - 1)
157+
count = count + 1
158+
ret[count] = { type = isinput and "inputs" or "outputs", name = varname, pos = start, blockcomment = {} }
159+
elseif byte == 92 then -- We found an escape character
160+
i = i + 2 -- Skip the escape character and the character following it
161+
elseif byte == 34 then -- We found a string
162+
count = count + 1
163+
ret[count] = { type = "string", pos = i }
164+
i = i + 1
165+
elseif byte == 35 then -- We found a comment
166+
local before = i > 1 and string.byte(line, i - 1)
167+
168+
if before == 93 then -- We found an ending
119169
count = count + 1
120-
ret[count] = { type = "string", pos = found }
121-
pos = found + 1
122-
elseif char == '\\' then -- We found an escape character
123-
pos = found + 2 -- Skip the escape character and the character following it
170+
ret[count] = { type = "end", pos = i - 1 }
171+
i = i + 1
172+
else
173+
local after = i < len and string.byte(line, i + 1)
174+
175+
if after == 91 then -- We found a start
176+
count = count + 1
177+
ret[count] = { type = "start", pos = i }
178+
i = i + 2
179+
else -- We found a normal comment
180+
count = count + 1
181+
ret[count] = { type = "normal", pos = i }
182+
i = i + 1
183+
end
124184
end
185+
else
186+
i = i + 1
125187
end
126-
until (not found)
188+
end
189+
127190
return ret, count
128191
end
129192

@@ -265,7 +328,7 @@ local directive_handlers = {
265328
["persist"] = handleIO("persist"),
266329

267330
["trigger"] = function(self, value, trace)
268-
local trimmed = string.Trim(value)
331+
local trimmed = PreProcessor.Trim(nil, value)
269332
if trimmed == "all" then
270333
if self.directives.trigger[1] ~= nil then
271334
self:Error("Directive (@trigger) conflicts with previous directives", trace)
@@ -311,7 +374,7 @@ local directive_handlers = {
311374
end
312375

313376
if CLIENT then
314-
if #string.Trim(arg) > 0 then
377+
if #PreProcessor.Trim(nil, arg) > 0 then
315378
trace.start_col = trace.end_col + 1
316379
trace.end_line = trace.start_line + 1
317380
trace.end_col = 1
@@ -348,7 +411,7 @@ function PreProcessor:ParseDirectives(line)
348411
-- not a directive?
349412
if not directive then
350413
-- flag as "in code", if that is the case
351-
if string.Trim(line) ~= "" then
414+
if self:Trim(line) ~= "" then
352415
self.incode = true
353416
end
354417
-- don't handle as a directive.
@@ -387,8 +450,6 @@ function PreProcessor:Process(buffer, directives, ent)
387450
self.ifdefStack = {}
388451
self.warnings, self.errors = {}, {}
389452

390-
local lines = string.Explode("\n", buffer)
391-
392453
if not directives then
393454
self.directives = {
394455
name = nil,
@@ -403,25 +464,28 @@ function PreProcessor:Process(buffer, directives, ent)
403464
self.ignorestuff = true
404465
end
405466

406-
-- to avoid big hangs, 2 regex changed from 500 to 10000 to avoid false positives
407-
local regex_limits = {[0] = 50000000, 15000, 10000, 150, 70, 40}
408-
local timeout = SysTime() + 0.5
467+
local lines
409468

410-
for i, line in ipairs(lines) do
411-
self.readline = i
469+
if #buffer > 5000000 then -- 5mb
470+
self:Error("Code is too big (5mb max)")
471+
lines = {}
472+
else
473+
-- to avoid big hangs
474+
local timeout = SysTime() + 0.5
475+
lines = string.Explode("\n", buffer)
412476

413-
local ok = pcall(function() WireLib.CheckRegex(line, "^(.-)%s*$", regex_limits) end)
414-
if not ok then self:Error("Line strip regex is too complex!") goto cont end
477+
for i, line in ipairs(lines) do
478+
self.readline = i
415479

416-
line = string.TrimRight(line)
417-
line = self:RemoveComments(line)
418-
line = self:ParseDirectives(line)
419-
lines[i] = line
420-
::cont::
480+
line = self:TrimRight(line)
481+
line = self:RemoveComments(line)
482+
line = self:ParseDirectives(line)
483+
lines[i] = line
421484

422-
if SysTime() > timeout then
423-
self:Error("Preprocessing took too long!")
424-
break
485+
if SysTime() > timeout then
486+
self:Error("Preprocessing took too long")
487+
break
488+
end
425489
end
426490
end
427491

@@ -470,7 +534,7 @@ function PreProcessor:ParsePorts(ports, startoffset)
470534
column2 = column + column2
471535
local tr = Trace.new(self.readline, column2, self.readline, column2 + #var)
472536

473-
var = string.Trim(var)
537+
var = self:Trim(var)
474538
-- skip empty entries
475539
if var ~= "" then
476540
-- error on malformed variable names
@@ -615,7 +679,7 @@ function PreProcessor:PP_else(args, trace)
615679
local state = table.remove(self.ifdefStack)
616680
if state == nil then self:Error("Found #else outside #ifdef/#ifndef block", trace) end
617681

618-
if args:Trim() ~= "" then self:Error("Must not pass an argument to #else", trace) end
682+
if self:Trim(args) ~= "" then self:Error("Must not pass an argument to #else", trace) end
619683

620684
if self:Disabled() then
621685
table.insert(self.ifdefStack, false)
@@ -628,7 +692,7 @@ function PreProcessor:PP_endif(args, trace)
628692
local state = table.remove(self.ifdefStack)
629693
if state == nil then self:Error("Found #endif outside #ifdef/#ifndef block", trace) end
630694

631-
if args:Trim() ~= "" then self:Error("Must not pass an argument to #endif", trace) end
695+
if self:Trim(args) ~= "" then self:Error("Must not pass an argument to #endif", trace) end
632696
end
633697

634698
function PreProcessor:PP_error(args, trace)

lua/entities/gmod_wire_expression2/core/string.lua

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,15 +232,33 @@ end
232232
__e2setcost(2)
233233

234234
e2function string string:trim()
235-
return this:Trim()
235+
local ok, ret = pcall(function() WireLib.CheckRegex(this, "^%s*(.-)%s*$") return string.Trim(this) end)
236+
237+
if not ok then
238+
return self:throw(ret)
239+
else
240+
return ret
241+
end
236242
end
237243

238244
e2function string string:trimLeft()
239-
return this:TrimLeft()
245+
local ok, ret = pcall(function() WireLib.CheckRegex(this, "^%s*(.+)$") return string.TrimLeft(this) end)
246+
247+
if not ok then
248+
return self:throw(ret)
249+
else
250+
return ret
251+
end
240252
end
241253

242254
e2function string string:trimRight()
243-
return this:TrimRight()
255+
local ok, ret = pcall(function() WireLib.CheckRegex(this, "^(.-)%s*$") return string.TrimRight(this) end)
256+
257+
if not ok then
258+
return self:throw(ret)
259+
else
260+
return ret
261+
end
244262
end
245263

246264
--[[******************************************************************************]]--
@@ -250,8 +268,9 @@ __e2setcost(10)
250268
--- Returns the 1st occurrence of the string <pattern>, returns 0 if not found. Prints malformed string errors to the chat area.
251269
e2function number string:findRE(string pattern)
252270
local ok, ret = pcall(function() WireLib.CheckRegex(this, pattern) return string_find(this, pattern) end)
271+
253272
if not ok then
254-
return self:throw(ret, 0)
273+
return self:throw(ret)
255274
else
256275
return ret or 0
257276
end
@@ -260,8 +279,9 @@ end
260279
--- Returns the 1st occurrence of the string <pattern> starting at <start> and going to the end of the string, returns 0 if not found. Prints malformed string errors to the chat area.
261280
e2function number string:findRE(string pattern, start)
262281
local ok, ret = pcall(function() WireLib.CheckRegex(this, pattern) return string_find(this, pattern, start) end)
282+
263283
if not ok then
264-
return self:throw(ret, 0)
284+
return self:throw(ret)
265285
else
266286
return ret or 0
267287
end

0 commit comments

Comments
 (0)