#include "deserialiser_bridge.h" #include "deserialiser_host.h" #include #include #include #include #include extern void *factorio_deser_original(void *,void *,void *,unsigned); extern const uint8_t native_deser_vtable[]; static _Alignas(16) uint8_t native_heap[65536],native_object[1224],native_external[144]; static size_t native_cursor,native_limit; static unsigned native_allocations,native_reads,native_zero_calls,native_version_errors; static jmp_buf abort_target; typedef struct {const uint8_t *data;size_t length,cursor;} NativeReader; static NativeReader native_reader; void *native_deser_allocate(size_t size) { size_t address=(native_cursor+15)&~(size_t)15; if(address>native_limit || size>native_limit-address) longjmp(abort_target,5); native_cursor=address+size;++native_allocations;return native_heap+address; } static uint64_t get(const uint8_t *data,size_t at) {uint64_t x;memcpy(&x,data+at,8);return x;} static void put(uint8_t *data,size_t at,uint64_t x) {memcpy(data+at,&x,8);} void native_deser_read_or_throw(const uint8_t *source,void *destination,size_t size) { NativeReader *reader=(NativeReader *)(uintptr_t)get(source,8); assert(reader==&native_reader); if(reader->cursor>reader->length || size>reader->length-reader->cursor) longjmp(abort_target,1); memcpy(destination,reader->data+reader->cursor,size);reader->cursor+=size;++native_reads; } void native_deser_zero_memory(void *destination,size_t size) {memset(destination,0,size);++native_zero_calls;} void native_deser_version_error(void) {++native_version_errors;longjmp(abort_target,7);} void native_deser_unsupported(void) {abort();} void native_deser_unsupported_delete(void) {abort();} void native_deser_unsupported_length(void) {abort();} void native_deser_unsupported_array_length(void) {abort();} static uint64_t canonical(uint64_t value,uint64_t heap,uint64_t object) { if(value>=(uintptr_t)native_heap && value<=(uintptr_t)native_heap+native_cursor) return heap+value-(uintptr_t)native_heap; if(value>=(uintptr_t)native_object && value<=(uintptr_t)native_object+sizeof(native_object)) return object+value-(uintptr_t)native_object; if(value>=(uintptr_t)native_external && value<=(uintptr_t)native_external+sizeof(native_external)) return 8192+value-(uintptr_t)native_external; if(value==(uintptr_t)&native_reader) return 512; if(value==(uintptr_t)native_deser_vtable+16) return 65536+0xda8; return value; } static void normalize(uint8_t *data,size_t length,uint64_t heap,uint64_t object) { for(size_t i=0;i+8<=length;i+=8) put(data,i,canonical(get(data,i),heap,object)); } static uint8_t payload[9]; static void version(unsigned major,unsigned minor,unsigned patch,unsigned build,unsigned flag) { const uint16_t fields[]={(uint16_t)major,(uint16_t)minor,(uint16_t)patch,(uint16_t)build}; memcpy(payload,fields,8);payload[8]=(uint8_t)flag; } static void compare(w2c_factorio__deser *module,struct w2c_deser__host *env,unsigned iteration, size_t length,size_t limit,int external,unsigned owns,int expected) { uint8_t *memory=w2c_factorio__deser_memory(module)->data; uint64_t begin=131072+(iteration%32)*16,object=4096+(iteration%8)*256; memset(memory,0xa5,1048576);memset(native_heap,0xa5,sizeof(native_heap)); memset(native_object,0xa5,sizeof(native_object));memset(native_external,0xa5,sizeof(native_external)); native_cursor=0;native_allocations=0;native_reads=0;native_zero_calls=0;native_version_errors=0; native_limit=limit;native_reader=(NativeReader){payload,length,0}; int native_status=setjmp(abort_target); if(!native_status) assert(factorio_deser_original(native_object,&native_reader,external?native_external:NULL,owns)==native_object); assert(native_status==expected); *env=(struct w2c_deser__host){.module=module}; assert(factorio_guest_heap_init(&env->heap,memory,1048576,begin,begin+limit)==0); uint64_t registers[32]={0};registers[0]=object;registers[1]=512; registers[2]=external?8192:0;registers[3]=owns;registers[31]=1048576-256; for(unsigned i=19;i<30;++i) registers[i]=UINT64_C(0x123456789abc0000)+iteration+i; uint64_t stream[]={16384,length,0}; memcpy(memory,registers,sizeof(registers));memcpy(memory+512,stream,sizeof(stream)); memcpy(memory+16384,payload,length); unsigned status=w2c_factorio__deser_run(module,0,10000); assert(status==(unsigned)native_status); assert(env->heap.allocations==native_allocations && env->heap.cursor-begin==native_cursor); assert(env->reads==native_reads && env->zero_calls==native_zero_calls && env->version_errors==native_version_errors); assert(get(memory,528)==native_reader.cursor && !env->unsupported_calls); if(!status) { normalize(native_object,sizeof(native_object),begin,object); normalize(native_heap,native_cursor,begin,object); assert(memcmp(memory+object,native_object,sizeof(native_object))==0); assert(memcmp(memory+begin,native_heap,native_cursor)==0); assert(get(memory,0)==object); for(unsigned i=19;i<32;++i) assert(get(memory,i*8)==registers[i]); } for(size_t i=256;i<1048576;++i) { if(i>=object && i=begin && i=512 && i<536) continue; if(i>=16384 && i<16384+length) continue; if(i>=1048576-256-368 && i<1048576-256) continue; assert(memory[i]==0xa5); } } int main(void) { wasm_rt_init();w2c_factorio__deser module; struct w2c_deser__host env={.module=&module};wasm2c_factorio__deser_instantiate(&module,&env); for(unsigned i=0;i<512;++i) { if(i%2) version(2,0,i%78,i%78==77?0:i,0); else version(1,i%65500,i*13,i*29,0); compare(&module,&env,i,9,65536,i%3==0,i%2,0); } for(unsigned length=0;length<9;++length) { version(2,0,77,0,0);compare(&module,&env,length,length,65536,0,0,1); } const unsigned invalid[][5]={{0,0,0,0,0},{2,0,77,1,0},{2,0,78,0,0},{2,1,0,0,0}, {3,0,0,0,0},{2,0,77,0,1},{1,2,0,0,0},{1,2,0,27,0}}; for(unsigned i=0;i