Error if a stack allocated variable is too big (configurable with --max-stack-object-size).

This commit is contained in:
Christoffer Lerno
2025-08-21 00:33:56 +02:00
parent b7c9a4e2e9
commit a2ef63f5b6
12 changed files with 54 additions and 26 deletions

View File

@@ -17,6 +17,7 @@
- Fix max module name to 31 chars and the entire module path to 63 characters.
- Improve error message for missing `$endif`.
- `foo[x][y] = b` now interpreted as `(*&foo[x])[y] = b` which allows overloads to do chained [] accesses.
- Error if a stack allocated variable is too big (configurable with `--max-stack-object-size`).
### Fixes
- List.remove_at would incorrectly trigger ASAN.

View File

@@ -585,6 +585,7 @@ typedef struct BuildOptions_
MemoryEnvironment memory_environment;
SanitizeMode sanitize_mode;
uint32_t max_vector_size;
uint32_t max_stack_object_size;
bool print_keywords;
bool print_attributes;
bool print_builtins;
@@ -725,6 +726,7 @@ typedef struct
LinkerType linker_type;
uint32_t symtab_size;
uint32_t max_vector_size;
uint32_t max_stack_object_size;
uint32_t switchrange_max_size;
uint32_t switchjump_max_size;
const char **args;

View File

@@ -177,6 +177,7 @@ static void usage(bool full)
print_opt("--win64-simd=<option>", "Win64 SIMD ABI: array, full.");
print_opt("--win-debug=<option>", "Select debug output on Windows: codeview or dwarf (default: codeview).");
print_opt("--max-vector-size <number>", "Set the maximum vector bit size to allow (default: 4096).");
print_opt("--max-stack-object-size <number>", "Set the maximum size of a stack object in KB (default: 128).");
PRINTF("");
print_opt("--print-linking", "Print linker arguments.");
PRINTF("");
@@ -943,6 +944,14 @@ static void parse_option(BuildOptions *options)
options->riscv_float_capability = parse_opt_select(RiscvFloatCapability, argopt, riscv_capability);
return;
}
if (match_longopt("max-stack-object-size"))
{
int size = (at_end() || next_is_opt()) ? 0 : atoi(next_arg());
if (size < 1) error_exit("Expected a valid positive integer >= 1 for --max-stack-object-size.");
if (size > MAX_STACK_OBJECT_SIZE) error_exit("Expected a valid positive integer <= %u for --max-vector-size.", (unsigned)MAX_STACK_OBJECT_SIZE);
options->max_stack_object_size = size;
return;
}
if (match_longopt("max-vector-size"))
{
int size = (at_end() || next_is_opt()) ? 0 : atoi(next_arg());
@@ -952,6 +961,7 @@ static void parse_option(BuildOptions *options)
{
error_exit("The --max-vector-size value must be a power of 2, try using %u instead.", next_highest_power_of_2(size));
}
options->max_vector_size = size;
return;
}
if ((argopt = match_argopt("memory-env")))

View File

@@ -431,6 +431,7 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions *
OVERRIDE_IF_SET(benchfn);
OVERRIDE_IF_SET(symtab_size);
OVERRIDE_IF_SET(max_vector_size);
OVERRIDE_IF_SET(max_stack_object_size);
OVERRIDE_IF_SET(win.def);
OVERRIDE_IF_SET(no_entry);
OVERRIDE_IF_SET(echo_prefix);
@@ -446,6 +447,7 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions *
OVERRIDE_IF_SET(android.api_version);
if (!target->max_vector_size) target->max_vector_size = DEFAULT_VECTOR_WIDTH;
if (!target->max_stack_object_size) target->max_stack_object_size = DEFAULT_STACK_OBJECT_SIZE;
if (target->quiet && !options->verbosity_level) options->verbosity_level = -1;

View File

@@ -407,7 +407,7 @@ static bool parse_param_path(ParseContext *c, DesignatorElement ***path)
}
}
static Expr *parse_lambda(ParseContext *c, Expr *left, SourceSpan lhs_span)
static Expr *parse_lambda(ParseContext *c, Expr *left, SourceSpan lhs_span UNUSED)
{
ASSERT(!left && "Unexpected left hand side");
Expr *expr = EXPR_NEW_TOKEN(EXPR_LAMBDA);
@@ -622,9 +622,10 @@ Expr *parse_ct_expression_list(ParseContext *c, bool allow_decl)
*
* @param c the context
* @param left must be null.
* @param lhs_start unused
* @return Expr *
*/
static Expr *parse_type_identifier(ParseContext *c, Expr *left, SourceSpan lhs_start)
static Expr *parse_type_identifier(ParseContext *c, Expr *left, SourceSpan lhs_start UNUSED)
{
ASSERT(!left && "Unexpected left hand side");
return parse_type_expression_with_path(c, NULL);
@@ -635,9 +636,10 @@ static Expr *parse_type_identifier(ParseContext *c, Expr *left, SourceSpan lhs_s
*
* @param c the context
* @param left must be null.
* @param lhs_start unused
* @return Expr *
*/
static Expr *parse_splat(ParseContext *c, Expr *left, SourceSpan lhs_start)
static Expr *parse_splat(ParseContext *c, Expr *left, SourceSpan lhs_start UNUSED)
{
ASSERT(!left && "Unexpected left hand side");
Expr *expr = expr_new(EXPR_SPLAT, c->span);
@@ -671,9 +673,10 @@ static Expr *parse_type_expr(ParseContext *c, Expr *left, SourceSpan lhs_start U
*
* @param c the context
* @param left must be null
* @param lhs_start unused
* @return Expr *
*/
static Expr *parse_ct_stringify(ParseContext *c, Expr *left, SourceSpan lhs_start)
static Expr *parse_ct_stringify(ParseContext *c, Expr *left, SourceSpan lhs_start UNUSED)
{
ASSERT(!left && "Unexpected left hand side");
SourceSpan start_span = c->span;

View File

@@ -764,7 +764,8 @@ INLINE bool parse_rethrow_bracket(ParseContext *c, SourceSpan start)
/**
* optional_type ::= type '!'?
* @param c
* @return
* @param allow_generic should generic be allowed
* @return The resulting type
*/
static inline TypeInfo *parse_optional_type_maybe_generic(ParseContext *c, bool allow_generic)
{

View File

@@ -545,7 +545,7 @@ RETRY:
}
default:
// Check type sizes
goto CHECK_SIZE;
break;
}
CHECK_SIZE:
if (type_size(expr->type) > type_size(type)) return expr;
@@ -721,13 +721,9 @@ static bool report_cast_error(CastContext *cc, bool may_cast_explicit)
type_quoted_error_string(to),
type_to_error_string(type_no_optional(to)));
}
else
{
RETURN_CAST_ERROR(expr,
"It is not possible to cast %s to the inner type %s.",
type_quoted_error_string(type_no_optional(expr->type)), type_quoted_error_string(to));
}
RETURN_CAST_ERROR(expr,
"It is not possible to cast %s to the inner type %s.",
type_quoted_error_string(type_no_optional(expr->type)), type_quoted_error_string(to));
}
if (may_cast_explicit)
{
@@ -837,7 +833,7 @@ static bool rule_ptr_to_ptr(CastContext *cc, bool is_explicit, bool is_silent)
}
static bool rule_all_ok(CastContext *cc, bool is_explicit, bool silent)
static bool rule_all_ok(CastContext *cc UNUSED, bool is_explicit UNUSED, bool silent UNUSED)
{
return true;
}

View File

@@ -4789,6 +4789,16 @@ bool sema_analyse_var_decl(SemaContext *context, Decl *decl, bool local)
{
if (!sema_set_alloca_alignment(context, decl->type, &decl->alignment)) return false;
}
if (decl->var.kind == VARDECL_LOCAL && type_size(decl->type) > compiler.build.max_stack_object_size * 1024)
{
size_t size = type_size(decl->type);
RETURN_SEMA_ERROR(
decl, "The size of this local variable (%s%d Kb) exceeds the maximum allowed stack object size (%d Kb), "
"you can increase this limit with --max-stack-object-size, but be aware that too large objects "
"allocated on the stack may lead to the stack running out of memory.", size % 1024 == 0 ? "" : "over ",
size / 1024,
compiler.build.max_stack_object_size);
}
return success;
}

View File

@@ -98,7 +98,7 @@ bool sema_analyse_expr_value(SemaContext *context, Expr *expr);
Expr *expr_access_inline_member(Expr *parent, Decl *parent_decl);
bool sema_analyse_ct_expr(SemaContext *context, Expr *expr);
Decl *sema_find_typed_operator(SemaContext *context, OperatorOverload operator_overload, SourceSpan span, Expr *lhs, Expr *rhs, bool *reverse);
OverloadMatch sema_find_typed_operator_type(SemaContext *context, OperatorOverload operator_overload, OverloadType overloat_type, Type *lhs_type, Type *rhs_type, Expr *rhs, Decl **candidate_ref, OverloadMatch last_match, Decl **ambiguous_ref);
OverloadMatch sema_find_typed_operator_type(SemaContext *context, OperatorOverload operator_overload, OverloadType overload_type, Type *lhs_type, Type *rhs_type, Expr *rhs, Decl **candidate_ref, OverloadMatch last_match, Decl **ambiguous_ref);
BoolErr sema_type_has_equality_overload(SemaContext *context, Type *type);
Decl *sema_find_untyped_operator(Type *type, OperatorOverload operator_overload, Decl *skipped);
bool sema_insert_method_call(SemaContext *context, Expr *method_call, Decl *method_decl, Expr *parent, Expr **arguments, bool reverse_overload);

View File

@@ -49,7 +49,7 @@ static inline bool sema_check_value_case(SemaContext *context, Type *switch_type
static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, SourceSpan expr_span, CanonicalType *switch_type, Ast **cases);
static inline bool sema_analyse_statement_inner(SemaContext *context, Ast *statement);
static bool sema_analyse_require(SemaContext *context, Ast *directive, AstId **asserts, SourceSpan source);
static bool sema_analyse_require(SemaContext *context, Ast *directive, AstId **asserts, SourceSpan span);
static bool sema_analyse_ensure(SemaContext *context, Ast *directive);
static bool sema_analyse_optional_returns(SemaContext *context, Ast *directive);
@@ -880,11 +880,9 @@ static inline bool sema_analyse_try_unwrap(SemaContext *context, Expr *expr)
{
if (decl->var.kind == VARDECL_UNWRAPPED)
{
SEMA_ERROR(ident, "This variable is already unwrapped, so you cannot use 'try' on it again, please remove the 'try'.");
return false;
RETURN_SEMA_ERROR(ident, "This variable is already unwrapped, so you cannot use 'try' on it again, please remove the 'try'.");
}
SEMA_ERROR(ident, "Expected this variable to be an optional, otherwise it can't be used for unwrap, maybe you didn't intend to use 'try'?");
return false;
RETURN_SEMA_ERROR(ident, "Expected this variable to be an optional, otherwise it can't be used for unwrap, maybe you didn't intend to use 'try'?");
}
expr->expr_kind = EXPR_TRY;
expr->try_expr = (ExprTry) { .decl = decl };
@@ -929,7 +927,6 @@ static inline bool sema_analyse_try_unwrap(SemaContext *context, Expr *expr)
if (!IS_OPTIONAL(optional))
{
RETURN_SEMA_ERROR(optional, "Expected an optional expression to 'try' here. If it isn't an optional, remove 'try'.");
return false;
}
if (var_type)
@@ -959,9 +956,8 @@ static inline bool sema_analyse_try_unwrap(SemaContext *context, Expr *expr)
static inline bool sema_analyse_try_unwrap_chain(SemaContext *context, Expr *expr, CondType cond_type, CondResult *result)
{
ASSERT(cond_type == COND_TYPE_UNWRAP_BOOL);
ASSERT(expr->expr_kind == EXPR_TRY_UNWRAP_CHAIN);
ASSERT_SPAN(expr, cond_type == COND_TYPE_UNWRAP_BOOL);
ASSERT_SPAN(expr, expr->expr_kind == EXPR_TRY_UNWRAP_CHAIN);
FOREACH(Expr *, chain_element, expr->try_unwrap_chain_expr)
{
@@ -3239,8 +3235,7 @@ static bool sema_analyse_ensure(SemaContext *context, Ast *directive)
{
if (expr->expr_kind == EXPR_DECL)
{
SEMA_ERROR(expr, "Only expressions are allowed.");
return false;
RETURN_SEMA_ERROR(expr, "Only expressions are allowed.");
}
}
return true;

View File

@@ -22,7 +22,9 @@
#define NO_ARENA 0
#define MAX_VECTOR_WIDTH 65536
#define MAX_STACK_OBJECT_SIZE (256 * 1024)
#define DEFAULT_VECTOR_WIDTH 4096
#define DEFAULT_STACK_OBJECT_SIZE 64
#define MAX_ARRAY_SIZE INT64_MAX
#define MAX_SOURCE_LOCATION_LEN 255
#define PROJECT_JSON "project.json"

View File

@@ -0,0 +1,6 @@
import std;
fn int main(String[] args)
{
int[1024 * 1024] x; // #error: The size of this local variable (4096 Kb) exceeds the maximum allowed stack object size (128 Kb)
return 0;
}