Skip to content

Commit 1a006c9

Browse files
committed
optimize union unpacking for analyzer functions and prevent combinations totalling over 1000 from running
1 parent f63ae64 commit 1a006c9

File tree

3 files changed

+256
-22
lines changed

3 files changed

+256
-22
lines changed

nattlua/analyzer/operators/function_call_analyzer.lua

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ local function should_expand(arg, contract)
3131
return false
3232
end
3333

34-
-- Helper to extract all possible values for an argument
3534
local function get_all_values(val)
3635
if val.Type == "range" then
3736
-- extract min and max as separate values like a union, not as a range
@@ -55,28 +54,43 @@ local function get_all_values(val)
5554
end
5655
end
5756

58-
-- Generate Cartesian product recursively
59-
local function generate_combinations(packed_args, argument_options, arg_index)
60-
if arg_index > packed_args then
61-
return {{}} -- Base case: one empty combination
57+
local function generate_combinations_iterative(argument_options)
58+
local result = {{}}
59+
60+
for arg_index = 1, #argument_options do
61+
local new_result = {}
62+
local new_index = 1
63+
64+
for _, combination in ipairs(result) do
65+
for _, value in ipairs(argument_options[arg_index]) do
66+
local new_combination = {}
67+
for i, v in ipairs(combination) do
68+
new_combination[i] = v
69+
end
70+
new_combination[#combination + 1] = value
71+
72+
new_result[new_index] = new_combination
73+
new_index = new_index + 1
74+
end
75+
end
76+
77+
result = new_result
6278
end
79+
80+
return result
81+
end
6382

64-
local rest_combinations = generate_combinations(packed_args, argument_options, arg_index + 1)
65-
local all_combinations = {}
66-
67-
for _, value in ipairs(argument_options[arg_index]) do
68-
for _, rest in ipairs(rest_combinations) do
69-
local combination = {value}
70-
71-
for _, v in ipairs(rest) do
72-
table.insert(combination, v)
73-
end
83+
local max_combinations = 1000
7484

75-
table.insert(all_combinations, combination)
85+
local function is_above_limit(argument_options)
86+
local total = 1
87+
for i = 1, #argument_options do
88+
total = total * #argument_options[i]
89+
if total > max_combinations then
90+
return total, true
7691
end
7792
end
78-
79-
return all_combinations
93+
return total, false
8094
end
8195

8296
local function unpack_union_tuples(obj, input)
@@ -110,7 +124,12 @@ local function unpack_union_tuples(obj, input)
110124
-- If nothing needs expansion, return original arguments
111125
if not has_expandable_args then return {packed_args} end
112126

113-
return generate_combinations(#packed_args, argument_options, 1)
127+
local total_combinations, is_above_limit = is_above_limit(argument_options)
128+
if is_above_limit then
129+
return nil, "too many argument combinations (" .. total_combinations .. " > " .. max_combinations .. ")"
130+
end
131+
132+
return generate_combinations_iterative(argument_options)
114133
end
115134

116135
local function call_and_collect(analyzer, obj, arguments, ret)
@@ -200,7 +219,14 @@ return function(analyzer, obj, input)
200219

201220
local ret = Tuple()
202221

203-
for _, arguments in ipairs(unpack_union_tuples(obj, input)) do
222+
local combinations, error_msg = unpack_union_tuples(obj, input)
223+
224+
if not combinations then
225+
analyzer:Error({error_msg})
226+
return output_signature:Copy()
227+
end
228+
229+
for _, arguments in ipairs(combinations) do
204230
local t = call_and_collect(analyzer, obj, arguments, ret)
205231

206232
if t then return t end

nattlua/definitions/lua/bit.nlua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ do
126126
analyzer function bit.extract(n: number, field: number, width: nil | number): number
127127
if n:IsLiteral() and field:IsLiteral() then
128128
if width and width:IsLiteral() then
129-
return bit.extract(n:GetData(), field:GetData(), width:GetData())
129+
return bit.band(bit.rshift(n:GetData(), field:GetData()), bit.lshift(1, width:GetData()) - 1)
130130
else
131-
return bit.extract(n:GetData(), field:GetData())
131+
return bit.band(bit.rshift(n:GetData(), field:GetData()), 1)
132132
end
133133
end
134134

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
2+
--[[
3+
4+
base64 -- v1.5.3 public domain Lua base64 encoder/decoder
5+
no warranty implied; use at your own risk
6+
7+
Needs bit32.extract function. If not present it's implemented using BitOp
8+
or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua
9+
implementation inspired by Rici Lake's post:
10+
http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html
11+
12+
author: Ilya Kolbin (iskolbin@gmail.com)
13+
url: github.com/iskolbin/lbase64
14+
15+
COMPATIBILITY
16+
17+
Lua 5.1+, LuaJIT
18+
19+
LICENSE
20+
21+
See end of file for license information.
22+
23+
--]]
24+
25+
26+
local base64 = {}
27+
28+
local extract = _G.bit32 and _G.bit32.extract -- Lua 5.2/Lua 5.3 in compatibility mode
29+
if not extract then
30+
if _G.bit then -- LuaJIT
31+
local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band
32+
extract = function( v, from, width )
33+
return band( shr( v, from ), shl( 1, width ) - 1 )
34+
end
35+
elseif _G._VERSION == "Lua 5.1" then
36+
extract = function( v, from, width )
37+
local w = 0
38+
local flag = 2^from
39+
for i = 0, width-1 do
40+
local flag2 = flag + flag
41+
if v % flag2 >= flag then
42+
w = w + 2^i
43+
end
44+
flag = flag2
45+
end
46+
return w
47+
end
48+
else -- Lua 5.3+
49+
extract = load[[return function( v, from, width )
50+
return ( v >> from ) & ((1 << width) - 1)
51+
end]]()
52+
end
53+
end
54+
55+
56+
function base64.makeencoder( s62, s63, spad )
57+
local encoder = {}
58+
for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J',
59+
'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y',
60+
'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',
61+
'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2',
62+
'3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do
63+
encoder[b64code] = char:byte()
64+
end
65+
return encoder
66+
end
67+
68+
function base64.makedecoder( s62, s63, spad )
69+
local decoder = {}
70+
for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do
71+
decoder[charcode] = b64code
72+
end
73+
return decoder
74+
end
75+
76+
local DEFAULT_ENCODER = base64.makeencoder(nil, nil, nil)
77+
local DEFAULT_DECODER = base64.makedecoder(nil, nil, nil)
78+
79+
local char, concat = string.char, table.concat
80+
81+
function base64.encode( str, encoder, usecaching )
82+
encoder = encoder or DEFAULT_ENCODER
83+
local t, k, n = {}, 1, #str
84+
local lastn = n % 3
85+
local cache = {}
86+
for i = 1, n-lastn, 3 do
87+
local a, b, c = str:byte( i, i+2 )
88+
local v = a*0x10000 + b*0x100 + c
89+
local s
90+
if usecaching then
91+
s = cache[v]
92+
if not s then
93+
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
94+
cache[v] = s
95+
end
96+
else
97+
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
98+
end
99+
t[k] = s
100+
k = k + 1
101+
end
102+
if lastn == 2 then
103+
local a, b = str:byte( n-1, n )
104+
local v = a*0x10000 + b*0x100
105+
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64])
106+
elseif lastn == 1 then
107+
local v = str:byte( n )*0x10000
108+
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64])
109+
end
110+
return concat( t )
111+
end
112+
113+
function base64.decode( b64, decoder, usecaching )
114+
decoder = decoder or DEFAULT_DECODER
115+
local pattern = '[^%w%+%/%=]'
116+
if decoder then
117+
local s62, s63
118+
for charcode, b64code in pairs( decoder ) do
119+
if b64code == 62 then s62 = charcode
120+
elseif b64code == 63 then s63 = charcode
121+
end
122+
end
123+
pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) )
124+
end
125+
b64 = b64:gsub( pattern, '' )
126+
local cache = usecaching and {}
127+
local t, k = {}, 1
128+
local n = #b64
129+
local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0
130+
for i = 1, padding > 0 and n-4 or n, 4 do
131+
local a, b, c, d = b64:byte( i, i+3 )
132+
local s
133+
if usecaching then
134+
local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d
135+
s = cache[v0]
136+
if not s then
137+
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
138+
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
139+
cache[v0] = s
140+
end
141+
else
142+
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
143+
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
144+
end
145+
t[k] = s
146+
k = k + 1
147+
end
148+
if padding == 1 then
149+
local a, b, c = b64:byte( n-3, n-1 )
150+
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40
151+
t[k] = char( extract(v,16,8), extract(v,8,8))
152+
elseif padding == 2 then
153+
local a, b = b64:byte( n-3, n-2 )
154+
local v = decoder[a]*0x40000 + decoder[b]*0x1000
155+
t[k] = char( extract(v,16,8))
156+
end
157+
return concat( t )
158+
end
159+
160+
local encoded = base64.encode("hello", nil, nil)
161+
local decoded = base64.decode(encoded, nil, nil)
162+
163+
attest.equal(encoded, "aGVsbG8=")
164+
attest.equal(decoded, "hello")
165+
166+
return base64
167+
168+
--[[
169+
------------------------------------------------------------------------------
170+
This software is available under 2 licenses -- choose whichever you prefer.
171+
------------------------------------------------------------------------------
172+
ALTERNATIVE A - MIT License
173+
Copyright (c) 2018 Ilya Kolbin
174+
Permission is hereby granted, free of charge, to any person obtaining a copy of
175+
this software and associated documentation files (the "Software"), to deal in
176+
the Software without restriction, including without limitation the rights to
177+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
178+
of the Software, and to permit persons to whom the Software is furnished to do
179+
so, subject to the following conditions:
180+
The above copyright notice and this permission notice shall be included in all
181+
copies or substantial portions of the Software.
182+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
183+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
184+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
185+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
186+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
187+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
188+
SOFTWARE.
189+
------------------------------------------------------------------------------
190+
ALTERNATIVE B - Public Domain (www.unlicense.org)
191+
This is free and unencumbered software released into the public domain.
192+
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
193+
software, either in source code form or as a compiled binary, for any purpose,
194+
commercial or non-commercial, and by any means.
195+
In jurisdictions that recognize copyright laws, the author or authors of this
196+
software dedicate any and all copyright interest in the software to the public
197+
domain. We make this dedication for the benefit of the public at large and to
198+
the detriment of our heirs and successors. We intend this dedication to be an
199+
overt act of relinquishment in perpetuity of all present and future rights to
200+
this software under copyright law.
201+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
202+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
203+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
204+
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
205+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
206+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
207+
------------------------------------------------------------------------------
208+
--]]

0 commit comments

Comments
 (0)