1
0
Fork 0
forked from fun/fun

parser: fix variable scoping by defaulting to local scope in functions. (0.41.0)

This change addresses a critical issue where common variable names in
library functions (like 'i', 'n', 'src') were being treated as globals
by default, leading to state collisions and unexpected side effects
across different files.

Changes:
- Modified `src/parser.c` to automatically treat new variables assigned
  within a function body as locals unless they were already declared as
  globals.
- Introduced `sym_find` in the parser to distinguish between finding
  an existing global and creating a new one.
- Reverted `lib/net/cgi.fun` to use standard, short variable names,
  confirming that they no longer interfere with the caller's scope.
- Verified that explicit global updates still work if the variable was
  already defined at the top-level scope.
This commit is contained in:
Johannes Findeisen 2026-04-27 02:06:20 +02:00
commit 43ce9f83af
3 changed files with 48 additions and 33 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(fun VERSION 0.40.8 LANGUAGES C) project(fun VERSION 0.41.0 LANGUAGES C)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)

View file

@ -256,39 +256,38 @@ class CGI()
return resp return resp
fun url_decode(this, s) fun url_decode(this, s)
source_str = to_string(s) src = to_string(s)
decoded_result = "" out = []
number current_pos = 0 i = 0
number source_len = len(source_str) n = len(src)
while (current_pos < source_len) while (i < n)
current_char = substr(source_str, current_pos, 1) ch = substr(src, i, 1)
if (current_char == "+") if (ch == "+")
decoded_result = decoded_result + " " push(out, " ")
current_pos = current_pos + 1 i = i + 1
else if (current_char == "%" && current_pos + 2 < source_len) else if (ch == "%" && i + 2 < n)
hex_digit1 = substr(source_str, current_pos + 1, 1) h1 = substr(src, i + 1, 1)
hex_digit2 = substr(source_str, current_pos + 2, 1) h2 = substr(src, i + 2, 1)
hex_table = "0123456789ABCDEF" hexdigits = "0123456789ABCDEF"
val1 = find(hex_table, str_to_upper(hex_digit1)) v1 = find(hexdigits, str_to_upper(h1))
val2 = find(hex_table, str_to_upper(hex_digit2)) v2 = find(hexdigits, str_to_upper(h2))
if (val1 >= 0 && val2 >= 0) if (v1 >= 0 && v2 >= 0)
number char_code = val1 * 16 + val2 code = v1 * 16 + v2
// Using a more robust table with explicit characters ascii = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
ascii_table = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" if (code >= 32 && code <= 126)
if (char_code >= 32 && char_code <= 126) push(out, substr(ascii, code - 32, 1))
decoded_result = decoded_result + substr(ascii_table, char_code - 32, 1)
else else
decoded_result = decoded_result + "%" push(out, "%")
decoded_result = decoded_result + hex_digit1 push(out, h1)
decoded_result = decoded_result + hex_digit2 push(out, h2)
current_pos = current_pos + 3 i = i + 3
else else
decoded_result = decoded_result + current_char push(out, ch)
current_pos = current_pos + 1 i = i + 1
else else
decoded_result = decoded_result + current_char push(out, ch)
current_pos = current_pos + 1 i = i + 1
return decoded_result return join(out, "")
fun _merge_params(this, pairs) fun _merge_params(this, pairs)
// pairs: array of [key, value] entries // pairs: array of [key, value] entries

View file

@ -201,10 +201,16 @@ static struct {
int count; int count;
} G = {{0}, {0}, {0}, 0}; } G = {{0}, {0}, {0}, 0};
static int sym_index(const char *name) { static int sym_find(const char *name) {
for (int i = 0; i < G.count; ++i) { for (int i = 0; i < G.count; ++i) {
if (strcmp(G.names[i], name) == 0) return i; if (strcmp(G.names[i], name) == 0) return i;
} }
return -1;
}
static int sym_index(const char *name) {
int existing = sym_find(name);
if (existing >= 0) return existing;
if (G.count >= MAX_GLOBALS) { if (G.count >= MAX_GLOBALS) {
parser_fail(0, "Too many globals (max %d)", MAX_GLOBALS); parser_fail(0, "Too many globals (max %d)", MAX_GLOBALS);
return 0; return 0;
@ -5580,7 +5586,17 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
/* assignment or simple call */ /* assignment or simple call */
int lidx = local_find(name); int lidx = local_find(name);
int gi = (lidx < 0) ? sym_index(name) : -1; int gi = (lidx < 0) ? sym_find(name) : -1;
if (lidx < 0 && gi < 0 && g_locals) {
/* Auto-declare as local if we're in a function and it's not known as local or global */
lidx = local_add(name);
}
if (lidx < 0 && gi < 0) {
/* Not local, not existing global -> it's a new global (or a new local if we were in a function but lidx still < 0?)
Actually if g_locals was non-null we already added it to lidx.
If g_locals is NULL, we create it as global. */
gi = sym_index(name);
}
skip_spaces(src, len, &local_pos); skip_spaces(src, len, &local_pos);
/* object field assignment: supports /* object field assignment: supports