fix: guard excessive function call depth

This commit is contained in:
Hermes Agent
2026-09-22 06:17:43 +00:00
parent d9a226bd2d
commit c4d894315f
4 changed files with 19 additions and 4 deletions
+6 -3
View File
@@ -13,18 +13,21 @@ This file is the beginner-safe status map for the current package. It separates
The v1.18.26 review identified missing-argument reads in standard-library built-ins. The current runtime now validates required argument counts centrally before any builtin indexes `args[]`. Covered calls include `math.abs`, `math.clamp`, `random.seed`, `random.int`, text helpers, CSV helpers, path helpers, and collection helpers. Missing arguments produce a beginner-facing `needs N arguments` runtime error. The v1.18.26 review identified missing-argument reads in standard-library built-ins. The current runtime now validates required argument counts centrally before any builtin indexes `args[]`. Covered calls include `math.abs`, `math.clamp`, `random.seed`, `random.int`, text helpers, CSV helpers, path helpers, and collection helpers. Missing arguments produce a beginner-facing `needs N arguments` runtime error.
Focused regression coverage: `tests/38_builtin_arity.claro` and `tests/39_random_inverted_range.claro`. Focused regression coverage: `tests/38_builtin_arity.claro`, `tests/39_random_inverted_range.claro`, and `tests/40_call_depth_guard.claro`.
The runtime now rejects inverted `random.int` ranges before modulo arithmetic with: `random.int needs the lower bound to be less than or equal to the upper bound.` This prevents invalid ranges from producing incorrect values or a divide-by-zero signal. The runtime now rejects inverted `random.int` ranges before modulo arithmetic with: `random.int needs the lower bound to be less than or equal to the upper bound.` This prevents invalid ranges from producing incorrect values or a divide-by-zero signal.
User-defined function and object-method calls now have a documented maximum active call depth of 256. Exceeding it produces: `Claro function call depth exceeded the safe limit of 256. Simplify the recursion or add a stopping condition.` Ordinary non-recursive beginner programs are unaffected.
Verified in this checkout on 2026-09-22: Verified in this checkout on 2026-09-22:
- `gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o /tmp/claro-h2-asan -lm` plus `ASAN_OPTIONS=detect_leaks=0 /tmp/claro-h2-asan tests/39_random_inverted_range.claro`: expected diagnostic; no AddressSanitizer or UndefinedBehaviorSanitizer report. - `gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o /tmp/claro-call-depth-asan -lm` plus `ASAN_OPTIONS=detect_leaks=0 /tmp/claro-call-depth-asan tests/40_call_depth_guard.claro`: expected diagnostic; no AddressSanitizer or UndefinedBehaviorSanitizer report.
- `make -s all`: rebuilt both `claro` and `claro.exe` from current source.
- `./claro test`: `PASS: 0 failure(s)`. - `./claro test`: `PASS: 0 failure(s)`.
- `./claro doctor`: all checks `OK`. - `./claro doctor`: all checks `OK`.
- `./claro validate`: validation passed. - `./claro validate`: validation passed.
This slice does not yet fix recursion depth, memory cleanup, `LASTEXIT`, HTTP buffering/status handling, or other review findings. Claro remains a trusted-script interpreter, not a sandbox. This slice does not yet fix memory cleanup, `LASTEXIT`, HTTP buffering/status handling, or other review findings. Claro remains a trusted-script interpreter, not a sandbox.
## Feature matrix ## Feature matrix
+3 -1
View File
@@ -43,6 +43,7 @@ int pclose(FILE *stream);
#endif #endif
#define CLARO_VERSION "Claro v1.18.26" #define CLARO_VERSION "Claro v1.18.26"
#define CLARO_MAX_CALL_DEPTH 256
static void *xmalloc(size_t n){ void *p=malloc(n?n:1); if(!p){ fprintf(stderr,"Out of memory\n"); exit(1);} return p; } static void *xmalloc(size_t n){ void *p=malloc(n?n:1); if(!p){ fprintf(stderr,"Out of memory\n"); exit(1);} return p; }
static void *xrealloc(void *p,size_t n){ void *q=realloc(p,n?n:1); if(!q){ fprintf(stderr,"Out of memory\n"); exit(1);} return q; } static void *xrealloc(void *p,size_t n){ void *q=realloc(p,n?n:1); if(!q){ fprintf(stderr,"Out of memory\n"); exit(1);} return q; }
@@ -109,6 +110,7 @@ typedef struct Runtime {
Str captured; int capture; int trace; Str captured; int capture; int trace;
int error; char *err_msg; char *err_file; int err_line; int error; char *err_msg; char *err_file; int err_line;
int returning; Value ret; int returning; Value ret;
int call_depth;
Program **programs; int pcount, pcap; Program **programs; int pcount, pcap;
char **import_stack; int import_depth; char **import_stack; int import_depth;
int script_argc; char **script_argv; int script_argc; char **script_argv;
@@ -360,7 +362,7 @@ static int call_object_method(Runtime *rt,const char *name,Value *args,int argc)
if(rt->returning){ rt_set(rt,"RESULT",rt->ret); rt->returning=0; } if(rt->returning){ rt_set(rt,"RESULT",rt->ret); rt->returning=0; }
rt->locals=old; rt_set(rt,objname,obj); if(c){ for(fd=c->fields;fd;fd=fd->next){ Value fv=map_get(obj.map,fd->name); snprintf(fieldname,sizeof(fieldname),"%s.%s",objname,fd->name); rt_set_checked(rt,"",0,fieldname,fd->type,fv); } } rt->locals=old; rt_set(rt,objname,obj); if(c){ for(fd=c->fields;fd;fd=fd->next){ Value fv=map_get(obj.map,fd->name); snprintf(fieldname,sizeof(fieldname),"%s.%s",objname,fd->name); rt_set_checked(rt,"",0,fieldname,fd->type,fv); } }
return 1; } return 1; }
static void call_function(Runtime *rt,const char *name,Value *args,int argc){ int handled=0,i; Value br=builtin_call(rt,name,args,argc,&handled); if(handled){ rt_set(rt,"RESULT",br); return; } if(call_object_method(rt,name,args,argc)) return; Function *f=find_function(rt,name); if(!f){ rt_error(rt,"",0,"Unknown function: %s",name); return;} Var *old=rt->locals; rt->locals=NULL; for(i=0;i<f->pcnt;i++) rt_set(rt,f->params[i], i<argc?args[i]:v_none()); rt->returning=0; exec_range(rt,f->prog,f->start,f->end); if(rt->returning){ rt_set(rt,"RESULT",rt->ret); rt->returning=0; } rt->locals=old; } static void call_function(Runtime *rt,const char *name,Value *args,int argc){ int handled=0,i; Value br=builtin_call(rt,name,args,argc,&handled); if(handled){ rt_set(rt,"RESULT",br); return; } if(rt->call_depth>=CLARO_MAX_CALL_DEPTH){ rt_error(rt,"",0,"Claro function call depth exceeded the safe limit of %d. Simplify the recursion or add a stopping condition.",CLARO_MAX_CALL_DEPTH); return; } rt->call_depth++; if(call_object_method(rt,name,args,argc)){ rt->call_depth--; return; } { Function *f=find_function(rt,name); if(!f){ rt_error(rt,"",0,"Unknown function: %s",name); rt->call_depth--; return;} { Var *old=rt->locals; rt->locals=NULL; for(i=0;i<f->pcnt;i++) rt_set(rt,f->params[i], i<argc?args[i]:v_none()); rt->returning=0; exec_range(rt,f->prog,f->start,f->end); if(rt->returning){ rt_set(rt,"RESULT",rt->ret); rt->returning=0; } rt->locals=old; } } rt->call_depth--; }
static int stack_has(Runtime *rt,const char *path){ int i; for(i=0;i<rt->import_depth;i++) if(strcmp(rt->import_stack[i],path)==0) return 1; return 0; } static int stack_has(Runtime *rt,const char *path){ int i; for(i=0;i<rt->import_depth;i++) if(strcmp(rt->import_stack[i],path)==0) return 1; return 0; }
static int export_allows(char **exports,int ec,const char *name){ int i; if(ec==0) return 1; for(i=0;i<ec;i++) if(strcmp(exports[i],name)==0) return 1; return 0; } static int export_allows(char **exports,int ec,const char *name){ int i; if(ec==0) return 1; for(i=0;i<ec;i++) if(strcmp(exports[i],name)==0) return 1; return 0; }
static void do_import(Runtime *rt,const char *path,const char *ns,const char *curfile,int line){ Program *p; char *prefix=NULL; char **exports=NULL; int ec=0,i; if(stack_has(rt,path)){ rt_error(rt,curfile,line,"Import cycle detected at: %s",path); return;} if(strstr(path,"lib/text.claro")&&ns){ add_module(rt,ns,"text"); return;} if(strstr(path,"lib/math.claro")&&ns){ add_module(rt,ns,"math"); return;} if(strstr(path,"lib/random.claro")&&ns){ add_module(rt,ns,"random"); return;} if(strstr(path,"lib/csv.claro")&&ns){ add_module(rt,ns,"csv"); return;} if(strstr(path,"lib/path.claro")&&ns){ add_module(rt,ns,"path"); return;} if(strstr(path,"lib/collections.claro")&&ns){ add_module(rt,ns,"collections"); return;} if(strstr(path,"lib/time.claro")&&ns){ add_module(rt,ns,"time"); return;} static void do_import(Runtime *rt,const char *path,const char *ns,const char *curfile,int line){ Program *p; char *prefix=NULL; char **exports=NULL; int ec=0,i; if(stack_has(rt,path)){ rt_error(rt,curfile,line,"Import cycle detected at: %s",path); return;} if(strstr(path,"lib/text.claro")&&ns){ add_module(rt,ns,"text"); return;} if(strstr(path,"lib/math.claro")&&ns){ add_module(rt,ns,"math"); return;} if(strstr(path,"lib/random.claro")&&ns){ add_module(rt,ns,"random"); return;} if(strstr(path,"lib/csv.claro")&&ns){ add_module(rt,ns,"csv"); return;} if(strstr(path,"lib/path.claro")&&ns){ add_module(rt,ns,"path"); return;} if(strstr(path,"lib/collections.claro")&&ns){ add_module(rt,ns,"collections"); return;} if(strstr(path,"lib/time.claro")&&ns){ add_module(rt,ns,"time"); return;}
+9
View File
@@ -0,0 +1,9 @@
TEACH recurse
DO recurse
LEARNED
TRY
DO recurse
CATCH
SAY LASTERROR
ENDTRY
+1
View File
@@ -0,0 +1 @@
Claro function call depth exceeded the safe limit of 256. Simplify the recursion or add a stopping condition.