Skip to content
Closed
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
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2073,4 +2073,12 @@ if(BUILD_UNIT_TESTS)
target_include_directories(unit_macros PRIVATE ${CMAKE_SOURCE_DIR}/src)
add_test(NAME unit_macros COMMAND unit_macros)
set_tests_properties(unit_macros PROPERTIES LABELS "unit;validation")

add_executable(unit_vm_native_loader
tests/unit/test_vm_native_loader.c
src/qcommon/vm_native_loader.c
)
target_include_directories(unit_vm_native_loader PRIVATE ${CMAKE_SOURCE_DIR}/src)
add_test(NAME unit_vm_native_loader COMMAND unit_vm_native_loader)
set_tests_properties(unit_vm_native_loader PROPERTIES LABELS "unit;validation")
endif()
28 changes: 12 additions & 16 deletions src/qcommon/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ and one exported function: Perform
*/

#include "vm_local.h"
#include "vm_native_loader.h"

opcode_info_t ops[ OP_MAX ] =
{
Expand Down Expand Up @@ -1720,25 +1721,20 @@ TTimo: added some verbosity in debug
=================
*/
static void *VM_TryLoadNativeModule( const char *moduleName, char *filename, int filenameSize ) {
void *libHandle;

Com_sprintf( filename, filenameSize, "%s.so", moduleName );
libHandle = FS_LoadLibrary( filename );
if ( libHandle ) {
return libHandle;
}
int candidateIndex;

/* module.arch.so (e.g. client.aarch64.so, ui.x86_64.so) */
Com_sprintf( filename, filenameSize, "%s." ARCH_STRING DLL_EXT, moduleName );
libHandle = FS_LoadLibrary( filename );
if ( libHandle ) {
return libHandle;
for ( candidateIndex = 0; candidateIndex < VM_NATIVE_MODULE_CANDIDATE_COUNT; ++candidateIndex ) {
void *libHandle;
if ( !VM_BuildNativeModuleCandidate( moduleName, candidateIndex, filename, filenameSize ) ) {
continue;
}
libHandle = FS_LoadLibrary( filename );
if ( libHandle ) {
return libHandle;
}
}

/* modulearch.so (e.g. clientaarch64.so, uix86_64.so) */
Com_sprintf( filename, filenameSize, "%s" ARCH_STRING DLL_EXT, moduleName );
libHandle = FS_LoadLibrary( filename );
return libHandle;
return NULL;
}

static void * QDECL loadNative( const char *name, vmMainFunc_t *entryPoint, dllSyscall_t systemcalls ) {
Expand Down
21 changes: 21 additions & 0 deletions src/qcommon/vm_native_loader.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include "vm_native_loader.h"

qboolean VM_BuildNativeModuleCandidate( const char *moduleName, int candidateIndex, char *filename, int filenameSize ) {
if ( !moduleName || !moduleName[0] || !filename || filenameSize <= 0 ) {
return qfalse;
}

switch ( candidateIndex ) {
case 0:
Com_sprintf( filename, filenameSize, "%s.so", moduleName );
return qtrue;
case 1:
Com_sprintf( filename, filenameSize, "%s." ARCH_STRING DLL_EXT, moduleName );
return qtrue;
case 2:
Com_sprintf( filename, filenameSize, "%s" ARCH_STRING DLL_EXT, moduleName );
return qtrue;
default:
return qfalse;
}
}
24 changes: 24 additions & 0 deletions src/qcommon/vm_native_loader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef VM_NATIVE_LOADER_H
#define VM_NATIVE_LOADER_H

#include "q_shared.h"
#include "q_platform.h"

#define VM_NATIVE_MODULE_CANDIDATE_COUNT 3

/*
=================
VM_BuildNativeModuleCandidate

Build one native module filename candidate for VM loading.
candidateIndex order is intentionally stable:
0 -> module.so
1 -> module.arch.so
2 -> modulearch.so
Returns qtrue on success, qfalse when candidateIndex is out of range or
input/output buffers are invalid.
=================
*/
qboolean VM_BuildNativeModuleCandidate( const char *moduleName, int candidateIndex, char *filename, int filenameSize );

#endif
90 changes: 90 additions & 0 deletions tests/unit/test_vm_native_loader.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Unit test: VM native module filename candidate generation
* Run: ctest -R unit_vm_native_loader
*/
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

#include "qcommon/vm_native_loader.h"

#define ASSERT(cond, msg) do { \
if (!(cond)) { \
fprintf(stderr, "FAIL: %s\n", msg); \
return 1; \
} \
} while (0)

/*
* Standalone unit target shim for vm_native_loader.c:
* provide Com_sprintf without linking the full engine runtime.
*/
int QDECL Com_sprintf( char *dest, int size, const char *fmt, ... ) {
int written;
va_list args;

va_start( args, fmt );
written = vsnprintf( dest, (size_t)size, fmt, args );
va_end( args );

if ( written < 0 ) {
if ( size > 0 ) {
dest[0] = '\0';
}
return 0;
}

if ( written >= size && size > 0 ) {
dest[size - 1] = '\0';
return size - 1;
}

return written;
}

static int test_candidate_order(void) {
char candidate[128];
char expected0[128];
char expected1[128];
char expected2[128];

Com_sprintf(expected0, sizeof(expected0), "%s.so", "client");
Com_sprintf(expected1, sizeof(expected1), "%s." ARCH_STRING DLL_EXT, "client");
Com_sprintf(expected2, sizeof(expected2), "%s" ARCH_STRING DLL_EXT, "client");

ASSERT(VM_BuildNativeModuleCandidate("client", 0, candidate, sizeof(candidate)) == qtrue, "candidate 0 valid");
ASSERT(strcmp(candidate, expected0) == 0, "candidate 0 value");

ASSERT(VM_BuildNativeModuleCandidate("client", 1, candidate, sizeof(candidate)) == qtrue, "candidate 1 valid");
ASSERT(strcmp(candidate, expected1) == 0, "candidate 1 value");

ASSERT(VM_BuildNativeModuleCandidate("client", 2, candidate, sizeof(candidate)) == qtrue, "candidate 2 valid");
ASSERT(strcmp(candidate, expected2) == 0, "candidate 2 value");

return 0;
}

static int test_input_validation(void) {
char candidate[64];

ASSERT(VM_BuildNativeModuleCandidate("client", -1, candidate, sizeof(candidate)) == qfalse, "negative index invalid");
ASSERT(VM_BuildNativeModuleCandidate("client", VM_NATIVE_MODULE_CANDIDATE_COUNT, candidate, sizeof(candidate)) == qfalse, "out of range index invalid");
ASSERT(VM_BuildNativeModuleCandidate(NULL, 0, candidate, sizeof(candidate)) == qfalse, "null module invalid");
ASSERT(VM_BuildNativeModuleCandidate("", 0, candidate, sizeof(candidate)) == qfalse, "empty module invalid");
ASSERT(VM_BuildNativeModuleCandidate("client", 0, NULL, sizeof(candidate)) == qfalse, "null output invalid");
ASSERT(VM_BuildNativeModuleCandidate("client", 0, candidate, 0) == qfalse, "zero size invalid");

return 0;
}

int main(void) {
if (test_candidate_order() != 0) {
return 1;
}
if (test_input_validation() != 0) {
return 1;
}

printf("PASS: unit_vm_native_loader\n");
return 0;
}
Loading