Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions src/qcommon/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -2077,15 +2077,10 @@ intptr_t QDECL VM_Call( vm_t *vm, int nargs, int callnum, ... )
// if we have a dll loaded, call it directly
if ( vm->entryPoint )
{
/* Pass three arg slots: native vmMain only receives nargs from varargs; zero the rest
* so e.g. UI_GETAPIVERSION (nargs=0) does not read stack garbage. */
int32_t args[MAX_VMMAIN_CALL_ARGS-1];
Com_Memset( args, 0, sizeof( args ) );
va_list ap;
va_start( ap, callnum );
for ( i = 0; i < nargs; i++ ) {
args[i] = va_arg( ap, int32_t );
}
VM_BuildNativeModuleCallArgs( nargs, args, (int)ARRAY_LEN( args ), ap );
va_end( ap );

// add more arguments if you're changed MAX_VMMAIN_CALL_ARGS:
Expand Down
21 changes: 21 additions & 0 deletions src/qcommon/vm_native_module.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "vm_native_module.h"
#include <stdio.h>
#include <string.h>

int VM_BuildNativeModuleCandidates( const char *moduleName, char out[][MAX_QPATH], int maxCandidates ) {
int count = 0;
Expand All @@ -24,3 +25,23 @@ int VM_BuildNativeModuleCandidates( const char *moduleName, char out[][MAX_QPATH

return count;
}

void VM_BuildNativeModuleCallArgs( int nargs, int32_t *out, int maxArgs, va_list ap ) {
int i;
int copyCount;

if ( !out || maxArgs <= 0 ) {
return;
}

memset( out, 0, (size_t)maxArgs * sizeof( out[0] ) );

copyCount = nargs;
if ( copyCount > maxArgs ) {
copyCount = maxArgs;
}

for ( i = 0; i < copyCount; i++ ) {
out[i] = va_arg( ap, int32_t );
}
}
10 changes: 10 additions & 0 deletions src/qcommon/vm_native_module.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#ifndef VM_NATIVE_MODULE_H
#define VM_NATIVE_MODULE_H

#include <stdarg.h>
#include <stdint.h>

#include "q_shared.h"

/*
Expand All @@ -11,4 +14,11 @@
*/
int VM_BuildNativeModuleCandidates( const char *moduleName, char out[][MAX_QPATH], int maxCandidates );

/*
* Packs native vmMain argument slots from VM_Call varargs. Native vmMain takes
* exactly three integer argument slots after the command; missing slots must be
* zero so low-arity calls such as UI_GETAPIVERSION cannot observe stack junk.
*/
void VM_BuildNativeModuleCallArgs( int nargs, int32_t *out, int maxArgs, va_list ap );

#endif
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Tests

- **Unit** (`BUILD_UNIT_TESTS=ON`): `unit_macros`, `unit_qmath`, `unit_surfaceflags`, `unit_qhelpers`, `unit_crc`, `unit_pathutil`, `unit_msg`, `unit_info`, `unit_cm_bounds`, `unit_parse`, `unit_endian` (CRC, COM path, `Info_*`, `COM_Parse*`, and endian tests use the same minimal `stub_qcommon_min.c` + `q_shared.c` + `q_math.c` link as `unit_qhelpers`; `unit_msg` links `msg.c` + `huffman_static.c` with `stub_qcommon_min.c`, `stub_msg_cvar.c`, and `-DDEDICATED`; `unit_cm_bounds` links `cm_bounds.c` + `q_math.c` only) — run `ctest -R unit_` or `./unit_*` from the build directory.
- **Unit** (`BUILD_UNIT_TESTS=ON`): `unit_macros`, `unit_qmath`, `unit_surfaceflags`, `unit_qhelpers`, `unit_crc`, `unit_pathutil`, `unit_msg`, `unit_info`, `unit_cm_bounds`, `unit_parse`, `unit_endian`, `unit_vm_native_module` (CRC, COM path, `Info_*`, `COM_Parse*`, endian, and native VM module tests use the same minimal `stub_qcommon_min.c` + `q_shared.c` + `q_math.c` link as `unit_qhelpers` where needed; `unit_msg` links `msg.c` + `huffman_static.c` with `stub_qcommon_min.c`, `stub_msg_cvar.c`, and `-DDEDICATED`; `unit_cm_bounds` links `cm_bounds.c` + `q_math.c` only; `unit_vm_native_module` links `vm_native_module.c` only) — run `ctest -R unit_` or `./unit_*` from the build directory.
- **Script regression tests**: `test_botlib_bounded_strings` (botlib string invariants). Run with `ctest -R test_botlib_bounded_strings` from the build directory.
- **Validation**: `smoke_test`, `renderer_regression_check`, `check_artifacts`, `test_run_vulkan_script`, `test_compile_engine_lto`, `test_demo_game_pk3`, `test_vk_vegetation_dispatch_order`, `test_vulkan_mesh_shader_opt_in`, `test_vulkan_runtime_regressions`, `test_botlib_chat_message_bounds`, `test_vulkan_renderer_guards`, `test_vulkan_regression_source_guards`, `test_gltf_opengl_regressions`, `test_botlib_bounded_strings` — see `scripts/`, `tests/scripts/`, and `docs/RENDERER_CONFIDENCE.md`.
65 changes: 65 additions & 0 deletions tests/unit/test_vm_native_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* Unit test: VM native module candidate naming
* Run: ctest -R unit_vm_native_module
*/
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

Expand Down Expand Up @@ -64,6 +66,60 @@ static int test_candidate_limit(void) {
return 0;
}

static void build_call_args(int nargs, int32_t out[3], ...) {
va_list ap;

va_start(ap, out);
VM_BuildNativeModuleCallArgs(nargs, out, 3, ap);
va_end(ap);
}

static int test_native_call_args_zero_fill(void) {
int32_t out[3] = { 0x11111111, 0x22222222, 0x33333333 };

build_call_args(0, out);
ASSERT(out[0] == 0, "zero-arg call should clear arg0");
ASSERT(out[1] == 0, "zero-arg call should clear arg1");
ASSERT(out[2] == 0, "zero-arg call should clear arg2");

build_call_args(1, out, 1234);
ASSERT(out[0] == 1234, "one-arg call should copy arg0");
ASSERT(out[1] == 0, "one-arg call should clear arg1");
ASSERT(out[2] == 0, "one-arg call should clear arg2");

return 0;
}

static int test_native_call_args_copy_all_slots(void) {
int32_t out[3] = { 0 };

build_call_args(3, out, -1, 0x12345678, 42);
ASSERT(out[0] == -1, "three-arg call should copy arg0");
ASSERT(out[1] == 0x12345678, "three-arg call should copy arg1");
ASSERT(out[2] == 42, "three-arg call should copy arg2");

return 0;
}

static void build_call_args_limited(int nargs, int32_t out[3], int maxArgs, ...) {
va_list ap;

va_start(ap, maxArgs);
VM_BuildNativeModuleCallArgs(nargs, out, maxArgs, ap);
va_end(ap);
}

static int test_native_call_args_respect_limit(void) {
int32_t out[3] = { 77, 88, 99 };

build_call_args_limited(3, out, 2, 10, 20, 30);
ASSERT(out[0] == 10, "limited native args should copy arg0");
ASSERT(out[1] == 20, "limited native args should copy arg1");
ASSERT(out[2] == 99, "limited native args should not write past maxArgs");

return 0;
}

int main(void) {
if (test_empty_inputs() != 0) {
return 1;
Expand All @@ -74,6 +130,15 @@ int main(void) {
if (test_candidate_limit() != 0) {
return 1;
}
if (test_native_call_args_zero_fill() != 0) {
return 1;
}
if (test_native_call_args_copy_all_slots() != 0) {
return 1;
}
if (test_native_call_args_respect_limit() != 0) {
return 1;
}

printf("PASS: unit_vm_native_module\n");
return 0;
Expand Down
Loading