From 44f4efa5aa46d8ed63b13aa382b6318dfdcf827f Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Wed, 30 Jul 2025 01:01:56 +0200 Subject: [PATCH] Do not allow parameters in naked functions. --- releasenotes.md | 2 +- src/compiler/llvm_codegen_function.c | 1 + src/compiler/sema_asm.c | 45 ++++++++++--------- src/compiler/sema_expr.c | 10 ++++- .../asm/naked_call_with_instructions.c3t | 19 +++++--- test/test_suite/asm/naked_rethrow.c3 | 6 +++ 6 files changed, 55 insertions(+), 28 deletions(-) diff --git a/releasenotes.md b/releasenotes.md index 5ff301bc2..75ff6bc73 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -87,7 +87,7 @@ - Lambdas now properly follow its attributes #2346. - Not setting android-ndk resulted in a "set ndk-path" error. - Lambda deduplication would be incorrect when generated at the global scope. -- Allow accessing parameters in a naked function, just disallow `return`, this fixes #1955. +- Disallow accessing parameters in a naked function, as well as `return`, this fixes #1955. ### Stdlib changes - Improve contract for readline. #2280 diff --git a/src/compiler/llvm_codegen_function.c b/src/compiler/llvm_codegen_function.c index 7df8fa776..d20904a75 100644 --- a/src/compiler/llvm_codegen_function.c +++ b/src/compiler/llvm_codegen_function.c @@ -497,6 +497,7 @@ void llvm_emit_body(GenContext *c, LLVMValueRef function, FunctionPrototype *pro // Generate LLVMValueRef's for all parameters, so we can use them as local vars in code FOREACH_IDX(i, Decl *, param, signature->params) { + if (!param->name && is_naked) continue; llvm_emit_func_parameter(c, param, prototype->abi_args[i], &arg, i); } diff --git a/src/compiler/sema_asm.c b/src/compiler/sema_asm.c index 0b0fd24c4..01e83171e 100644 --- a/src/compiler/sema_asm.c +++ b/src/compiler/sema_asm.c @@ -59,6 +59,29 @@ static inline AsmArgGroup sema_ireg_for_type(Type *type) } } */ + +static inline Decl *sema_resolve_external_symbol(SemaContext *context, Expr *expr, const char *name) +{ + Decl *decl = sema_resolve_symbol(context, name, NULL, expr->span); + if (!decl) return NULL; + + if (decl->decl_kind != DECL_VAR) + { + SEMA_ERROR(expr, "Expected a global or local variable."); + return NULL; + } + if (IS_OPTIONAL(decl)) + { + SEMA_ERROR(expr, "Optional variables are not allowed in asm."); + return NULL; + } + if (decl->var.kind == VARDECL_PARAM && context->call_env.is_naked_fn) + { + SEMA_ERROR(expr, "Function parameters in '@naked' functions may not be directly referenced."); + return NULL; + } + return decl; +} static inline bool sema_reg_int_suported_type(AsmArgType arg, Type *type) { ASSERT(type_flatten(type) == type); @@ -347,21 +370,11 @@ static inline bool sema_check_asm_var(SemaContext *context, AsmInlineBlock *bloc { ExprAsmArg *arg = &expr->expr_asm_arg; const char *name = arg->ident.name; - Decl *decl = sema_resolve_symbol(context, name, NULL, expr->span); + Decl *decl = sema_resolve_external_symbol(context, expr, name); if (!decl) return false; ASSERT(arg->kind == ASM_ARG_REGVAR); arg->ident.ident_decl = decl; - if (decl->decl_kind != DECL_VAR) - { - SEMA_ERROR(expr, "Expected a global or local variable."); - return false; - } - if (IS_OPTIONAL(decl)) - { - SEMA_ERROR(expr, "Optional variables are not allowed in asm."); - return false; - } bool is_write = arg_type.is_write; bool is_read = !arg_type.is_write || arg_type.is_readwrite; arg->ident.is_input = !is_write; @@ -441,18 +454,10 @@ static inline bool sema_check_asm_memvar(SemaContext *context, AsmInlineBlock *b { ExprAsmArg *arg = &expr->expr_asm_arg; const char *name = arg->ident.name; - Decl *decl = sema_resolve_symbol(context, name, NULL, expr->span); + Decl *decl = sema_resolve_external_symbol(context, expr, name); if (!decl) return false; ASSERT(arg->kind == ASM_ARG_MEMVAR); arg->ident.ident_decl = decl; - if (decl->decl_kind != DECL_VAR) - { - RETURN_SEMA_ERROR(expr, "Expected a global or local variable."); - } - if (IS_OPTIONAL(decl)) - { - RETURN_SEMA_ERROR(expr, "Optional variables are not allowed in asm."); - } bool is_write = arg_type.is_write; bool is_read = !arg_type.is_write || arg_type.is_readwrite; arg->ident.is_input = !is_write; diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 8bb90ecf5..3edf94457 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -1262,12 +1262,18 @@ static inline bool sema_expr_analyse_identifier(SemaContext *context, Type *to, return true; } break; + case VARDECL_PARAM: + if (context->call_env.is_naked_fn) + { + RETURN_SEMA_ERROR(expr, "Parameters may not be directly accessed in '@naked' functions."); + } + break; case VARDECL_GLOBAL: if (context->call_env.pure) { - SEMA_ERROR(expr, "'@pure' functions may not access globals."); - return false; + RETURN_SEMA_ERROR(expr, "'@pure' functions may not access globals."); } + break; default: break; } diff --git a/test/test_suite/asm/naked_call_with_instructions.c3t b/test/test_suite/asm/naked_call_with_instructions.c3t index 96835c3cb..44f64a3ef 100644 --- a/test/test_suite/asm/naked_call_with_instructions.c3t +++ b/test/test_suite/asm/naked_call_with_instructions.c3t @@ -1,28 +1,37 @@ // #target: macos-x64 module test; import std; + fn void main() { char[8] foo = {255, 0, 0, 0, 0, 0, 0, 0}; - io::printn(to_ulong(&foo)); + ulong a = to_ulong2(&foo); + ulong b = to_ulong(&foo); } -fn ulong to_ulong(char[8]* buf) @naked @noinline +fn ulong to_ulong(char[8]*) @naked @noinline { asm { - movq $rax, [buf]; + movq [$rsp - 8], $rdi; + movq $rax, [$rsp - 8]; + movq $rax, [$rax]; ret; } unreachable(); } +fn ulong to_ulong2(char[8]* buf) +{ + return *(ulong*)buf; +} + + /* #expect: test.ll define i64 @test.to_ulong(ptr %0) #1 { entry: - call void asm sideeffect alignstack "movq ($0), %rax\0Aret \0A", "r,~{rax},~{flags},~{dirflag},~{fspr}"(ptr %0) + call void asm sideeffect alignstack "movq %rdi, -8(%rsp)\0Amovq -8(%rsp), %rax\0Amovq (%rax), %rax\0Aret \0A", "~{rax},~{flags},~{dirflag},~{fspr}"() unreachable } - attributes #1 = { naked noinline nounwind uwtable "no-trapping-math"="true" "stack-protector-buffer-size"="8" } diff --git a/test/test_suite/asm/naked_rethrow.c3 b/test/test_suite/asm/naked_rethrow.c3 index 72da4b570..4b65d6f05 100644 --- a/test/test_suite/asm/naked_rethrow.c3 +++ b/test/test_suite/asm/naked_rethrow.c3 @@ -10,4 +10,10 @@ fn ulong? test() @naked @noinline int? z; z!; // #error: Rethrow is not allowed in a '@naked' function unreachable(); +} + +fn ulong test2(int a) @naked @noinline +{ + int x = a; // #error: Parameters may not be directly accessed in '@naked' functions + unreachable(); } \ No newline at end of file