diff --git a/cli/all.c b/cli/all.c new file mode 100644 index 0000000..0594f18 --- /dev/null +++ b/cli/all.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020-2022 Thakee Nathees + * Copyright (c) 2021-2022 Pocketlang Contributors + * Distributed Under The MIT License + */ + +// This file will include all source files from the modules, modules/thirdparty +// sub directories here into this single file. That'll make it easy to compile +// with the command `gcc cli/*.c ...` instead of having to add every single +// sub directory to the list of source directory. + +/*****************************************************************************/ +/* STD MODULE SOURCES */ +/*****************************************************************************/ + +#include "modules/modules.c" + +#include "modules/std_file.c" +#include "modules/std_path.c" + +/*****************************************************************************/ +/* THIRDPARTY SOURCES */ +/*****************************************************************************/ + +// Library : cwalk +// Source : https://github.com/likle/cwalk/ +// Doc : https://likle.github.io/cwalk/ +// About : Path library for C/C++. Cross-Platform for Windows, MacOS and +// Linux. Supports UNIX and Windows path styles on those platforms. +#include "modules/thirdparty/cwalk/cwalk.c" + +// Library : argparse +// Source : https://github.com/cofyc/argparse/ +// About : Command-line arguments parsing library. +#include "thirdparty/argparse/argparse.c" diff --git a/cli/internal.h b/cli/internal.h index 890b8b9..c802ddb 100644 --- a/cli/internal.h +++ b/cli/internal.h @@ -16,14 +16,7 @@ #include #include -// Note that the cli itself is not a part of the pocketlang compiler, instead -// it's a host application to run pocketlang from the command line. We're -// embedding the pocketlang VM and we can only use its public APIs, not any -// internals of it, including assertion macros. So that we've copyied the -// "common.h" header. This can be moved to "src/include/common.h" and include -// as optional header, which is something I don't like because it makes -// pocketlang contain 2 headers (we'll always try to be minimal). -#include "common.h" +#include "modules/common.h" #define CLI_NOTICE \ "PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \ diff --git a/cli/main.c b/cli/main.c index 636d8ef..1c69f74 100644 --- a/cli/main.c +++ b/cli/main.c @@ -5,8 +5,7 @@ */ #include "internal.h" -#include "modules.h" - +#include "modules/modules.h" #include "thirdparty/argparse/argparse.h" // FIXME: Everything below here is temporary and for testing. diff --git a/cli/common.h b/cli/modules/common.h similarity index 93% rename from cli/common.h rename to cli/modules/common.h index 7b50d00..6df98a3 100644 --- a/cli/common.h +++ b/cli/modules/common.h @@ -4,17 +4,9 @@ * Distributed Under The MIT License */ -// A collection of reusable macros that pocketlang use. This file doesn't have -// any dependencies, you can just drag and drop this file in your project if -// you want to use these macros. - #ifndef PK_COMMON_H #define PK_COMMON_H -#ifndef __has_builtin - #define __has_builtin(x) 0 -#endif - #include //< Only needed here for ASSERT() macro and for release mode //< TODO; macro use this to print a crash report. diff --git a/cli/modules/modules.c b/cli/modules/modules.c new file mode 100644 index 0000000..9ff605b --- /dev/null +++ b/cli/modules/modules.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020-2022 Thakee Nathees + * Copyright (c) 2021-2022 Pocketlang Contributors + * Distributed Under The MIT License + */ + +#include "modules.h" + +// Note: Everything here is for testing the native API, and will have to +// refactor everything. + +// Allocate a new module object of type [Ty]. +#define NEW_OBJ(Ty) (Ty*)malloc(sizeof(Ty)) + +// Dellocate module object, allocated by NEW_OBJ(). Called by the freeObj +// callback. +#define FREE_OBJ(ptr) free(ptr) + +/*****************************************************************************/ +/* MODULE FUNCTIONS DECLARATION */ +/*****************************************************************************/ + +void fileGetAttrib(PKVM* vm, File* file, const char* attrib); +bool fileSetAttrib(PKVM* vm, File* file, const char* attrib); +void fileClean(PKVM* vm, File* file); + +void registerModuleFile(PKVM* vm); +void registerModulePath(PKVM* vm); + +/*****************************************************************************/ +/* MODULE PUBLIC FUNCTIONS */ +/*****************************************************************************/ + +void initObj(Obj* obj, ObjType type) { + obj->type = type; +} + +void objGetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) { + Obj* obj = (Obj*)instance; + ASSERT(obj->type == (ObjType)id, OOPS); + + switch (obj->type) { + case OBJ_FILE: + fileGetAttrib(vm, (File*)obj, attrib.string); + return; + } + STATIC_ASSERT(_OBJ_MAX_ == 2); +} + +bool objSetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) { + Obj* obj = (Obj*)instance; + ASSERT(obj->type == (ObjType)id, OOPS); + + switch (obj->type) { + case OBJ_FILE: + return fileSetAttrib(vm, (File*)obj, attrib.string); + } + STATIC_ASSERT(_OBJ_MAX_ == 2); + + return false; +} + +void freeObj(PKVM* vm, void* instance, uint32_t id) { + Obj* obj = (Obj*)instance; + ASSERT(obj->type == (ObjType)id, OOPS); + + switch (obj->type) { + case OBJ_FILE: + fileClean(vm, (File*)obj); + } + STATIC_ASSERT(_OBJ_MAX_ == 2); + + FREE_OBJ(obj); +} + +const char* getObjName(uint32_t id) { + switch ((ObjType)id) { + case OBJ_FILE: return "File"; + } + STATIC_ASSERT(_OBJ_MAX_ == 2); + return NULL; +} + +/*****************************************************************************/ +/* REGISTER MODULES */ +/*****************************************************************************/ + +void registerModules(PKVM* vm) { + registerModuleFile(vm); + registerModulePath(vm); +} diff --git a/cli/modules.h b/cli/modules/modules.h similarity index 69% rename from cli/modules.h rename to cli/modules/modules.h index 930dd63..842feb2 100644 --- a/cli/modules.h +++ b/cli/modules/modules.h @@ -4,35 +4,20 @@ * Distributed Under The MIT License */ -#include "internal.h" +#ifndef MODULES_H +#define MODULES_H + +#include + +#include "common.h" +#include +#include +#include /*****************************************************************************/ /* MODULE OBJECTS */ /*****************************************************************************/ -// Type enums of cli module objects. -typedef enum { - OBJ_FILE = 1, -} ObjType; - -// The abstract type of the objects. -typedef struct { - ObjType type; -} Obj; - -// File access mode. -typedef enum { - - FMODE_READ = (1 << 0), - FMODE_WRITE = (1 << 1), - FMODE_APPEND = (1 << 2), - - _FMODE_EXT = (1 << 3), - FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ), - FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE), - FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND), -} FileAccessMode; - // Str | If already exists | If does not exist | // -----+-------------------+-------------------| // 'r' | read from start | failure to open | @@ -41,11 +26,28 @@ typedef enum { // 'r+' | read from start | error | // 'w+' | destroy contents | create new | // 'a+' | write to end | create new | +typedef enum { + FMODE_READ = (1 << 0), + FMODE_WRITE = (1 << 1), + FMODE_APPEND = (1 << 2), + _FMODE_EXT = (1 << 3), + FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ), + FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE), + FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND), +} FileAccessMode; + +typedef enum { + OBJ_FILE = 1, + + _OBJ_MAX_ +} ObjType; + +typedef struct { + ObjType type; +} Obj; -// A wrapper around the FILE* for the File module. typedef struct { Obj _super; - FILE* fp; // C file poinnter. FileAccessMode mode; // Access mode of the file. bool closed; // True if the file isn't closed yet. @@ -59,7 +61,10 @@ typedef struct { void initObj(Obj* obj, ObjType type); // A function callback called by pocket VM to get attribute of a native -// instance. +// instance. The value of the attributes will be returned with pkReturn...() +// functions and if the attribute doesn't exists on the instance we're +// shouldn't return anything, PKVM will know it and set error (or use some +// common attributes like "as_string", "as_repr", etc). void objGetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib); // A function callback called by pocket VM to set attribute of a native @@ -77,9 +82,21 @@ const char* getObjName(uint32_t id); // Registers all the cli modules. void registerModules(PKVM* vm); -// 'path' moudle public functions used at various cli functions. +/*****************************************************************************/ +/* SHARED FUNCTIONS */ +/*****************************************************************************/ + +// These are "public" module functions that can be shared. Since some modules +// can be used for cli's internals we're defining such functions here and they +// will be imported in the cli. + bool pathIsAbsolute(const char* path); + void pathGetDirName(const char* path, size_t* length); + size_t pathNormalize(const char* path, char* buff, size_t buff_size); + size_t pathJoin(const char* from, const char* path, char* buffer, size_t buff_size); + +#endif // MODULES_H diff --git a/cli/modules/std_file.c b/cli/modules/std_file.c new file mode 100644 index 0000000..37d02ee --- /dev/null +++ b/cli/modules/std_file.c @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2020-2022 Thakee Nathees + * Copyright (c) 2021-2022 Pocketlang Contributors + * Distributed Under The MIT License + */ + +#include "modules.h" + +/*****************************************************************************/ +/* FILE OBJECT OPERATORS */ +/*****************************************************************************/ + +void fileGetAttrib(PKVM* vm, File* file, const char* attrib) { + if (strcmp(attrib, "closed") == 0) { + pkReturnBool(vm, file->closed); + return; + } +} + +bool fileSetAttrib(PKVM* vm, File* file, const char* attrib) { + return false; +} + +void fileClean(PKVM* vm, File* file) { + if (!file->closed) { + if (fclose(file->fp) != 0) { /* TODO: error! */ } + file->closed = true; + } +} + +/*****************************************************************************/ +/* FILE MODULE FUNCTIONS */ +/*****************************************************************************/ + +static void _fileOpen(PKVM* vm) { + + int argc = pkGetArgc(vm); + if (!pkCheckArgcRange(vm, argc, 1, 2)) return; + + const char* path; + if (!pkGetArgString(vm, 1, &path, NULL)) return; + + const char* mode_str = "r"; + FileAccessMode mode = FMODE_READ; + + if (argc == 2) { + if (!pkGetArgString(vm, 2, &mode_str, NULL)) return; + + // Check if the mode string is valid, and update the mode value. + do { + if (strcmp(mode_str, "r") == 0) { mode = FMODE_READ; break; } + if (strcmp(mode_str, "w") == 0) { mode = FMODE_WRITE; break; } + if (strcmp(mode_str, "a") == 0) { mode = FMODE_APPEND; break; } + if (strcmp(mode_str, "r+") == 0) { mode = FMODE_READ_EXT; break; } + if (strcmp(mode_str, "w+") == 0) { mode = FMODE_WRITE_EXT; break; } + if (strcmp(mode_str, "a+") == 0) { mode = FMODE_APPEND_EXT; break; } + + // TODO: (fmt, ...) va_arg for runtime error public api. + // If we reached here, that means it's an invalid mode string. + pkSetRuntimeError(vm, "Invalid mode string."); + return; + } while (false); + } + + FILE* fp = fopen(path, mode_str); + + if (fp != NULL) { + File* file = NEW_OBJ(File); + initObj(&file->_super, OBJ_FILE); + file->fp = fp; + file->mode = mode; + file->closed = false; + + pkReturnInstNative(vm, (void*)file, OBJ_FILE); + + } else { + pkReturnNull(vm); + } +} + +static void _fileRead(PKVM* vm) { + File* file; + if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; + + if (file->closed) { + pkSetRuntimeError(vm, "Cannot read from a closed file."); + return; + } + + if ((file->mode != FMODE_READ) && ((_FMODE_EXT & file->mode) == 0)) { + pkSetRuntimeError(vm, "File is not readable."); + return; + } + + // TODO: this is temporary. + char buff[2048]; + fread((void*)buff, sizeof(char), sizeof(buff), file->fp); + pkReturnString(vm, (const char*)buff); +} + +static void _fileWrite(PKVM* vm) { + File* file; + const char* text; uint32_t length; + if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; + if (!pkGetArgString(vm, 2, &text, &length)) return; + + if (file->closed) { + pkSetRuntimeError(vm, "Cannot write to a closed file."); + return; + } + + if ((file->mode != FMODE_WRITE) && ((_FMODE_EXT & file->mode) == 0)) { + pkSetRuntimeError(vm, "File is not writable."); + return; + } + + fwrite(text, sizeof(char), (size_t)length, file->fp); +} + +static void _fileClose(PKVM* vm) { + File* file; + if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; + + if (file->closed) { + pkSetRuntimeError(vm, "File already closed."); + return; + } + + if (fclose(file->fp) != 0) { + pkSetRuntimeError(vm, "fclose() failed!\n" \ + " at " __FILE__ ":" STRINGIFY(__LINE__) "."); + } + file->closed = true; +} + +void registerModuleFile(PKVM* vm) { + PkHandle* file = pkNewModule(vm, "File"); + + pkModuleAddFunction(vm, file, "open", _fileOpen, -1); + pkModuleAddFunction(vm, file, "read", _fileRead, 1); + pkModuleAddFunction(vm, file, "write", _fileWrite, 2); + pkModuleAddFunction(vm, file, "close", _fileClose, 1); + + pkReleaseHandle(vm, file); +} diff --git a/cli/modules.c b/cli/modules/std_path.c similarity index 54% rename from cli/modules.c rename to cli/modules/std_path.c index 3159a77..fb9679e 100644 --- a/cli/modules.c +++ b/cli/modules/std_path.c @@ -4,76 +4,6 @@ * Distributed Under The MIT License */ -#include "modules.h" - -// Note: Everything here is for testing the native API, and will have to -// refactor everything. - -// Allocate a new module object of type [Ty]. -#define NEW_OBJ(Ty) (Ty*)malloc(sizeof(Ty)) - -// Dellocate module object, allocated by NEW_OBJ(). Called by the freeObj -// callback. -#define FREE_OBJ(ptr) free(ptr) - -void initObj(Obj* obj, ObjType type) { - obj->type = type; -} - -void objGetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) { - Obj* obj = (Obj*)instance; - ASSERT(obj->type == (ObjType)id, OOPS); - - if (obj->type == OBJ_FILE) { - File* file = (File*)obj; - if (strcmp(attrib.string, "closed") == 0) { - pkReturnBool(vm, file->closed); - return; - } - } - - return; // Attribute not found. -} - -bool objSetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) { - Obj* obj = (Obj*)instance; - ASSERT(obj->type == (ObjType)id, OOPS); - - if (obj->type == OBJ_FILE) { - File* file = (File*)obj; - // Nothing to change. - } - - return false; -} - -void freeObj(PKVM* vm, void* instance, uint32_t id) { - Obj* obj = (Obj*)instance; - ASSERT(obj->type == (ObjType)id, OOPS); - - // If the file isn't closed, close it to flush it's buffer. - if (obj->type == OBJ_FILE) { - File* file = (File*)obj; - if (!file->closed) { - if (fclose(file->fp) != 0) { /* TODO: error! */ } - file->closed = true; - } - } - - FREE_OBJ(obj); -} - -const char* getObjName(uint32_t id) { - switch ((ObjType)id) { - case OBJ_FILE: return "File"; - } - return NULL; -} - -/*****************************************************************************/ -/* PATH MODULE */ -/*****************************************************************************/ - #include "thirdparty/cwalk/cwalk.h" #if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) #include "thirdparty/dirent/dirent.h" @@ -95,7 +25,7 @@ const char* getObjName(uint32_t id) { #define MAX_JOIN_PATHS 8 /*****************************************************************************/ -/* PUBLIC FUNCTIONS */ +/* PATH SHARED FUNCTIONS */ /*****************************************************************************/ bool pathIsAbsolute(const char* path) { @@ -157,7 +87,7 @@ static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) { } /*****************************************************************************/ -/* PATH MODULES FUNCTIONS */ +/* PATH MODULE FUNCTIONS */ /*****************************************************************************/ static void _pathSetStyleUnix(PKVM* vm) { @@ -305,128 +235,3 @@ void registerModulePath(PKVM* vm) { pkReleaseHandle(vm, path); } - -/*****************************************************************************/ -/* FILE MODULE */ -/*****************************************************************************/ - -static void _fileOpen(PKVM* vm) { - - int argc = pkGetArgc(vm); - if (!pkCheckArgcRange(vm, argc, 1, 2)) return; - - const char* path; - if (!pkGetArgString(vm, 1, &path, NULL)) return; - - const char* mode_str = "r"; - FileAccessMode mode = FMODE_READ; - - if (argc == 2) { - if (!pkGetArgString(vm, 2, &mode_str, NULL)) return; - - // Check if the mode string is valid, and update the mode value. - do { - if (strcmp(mode_str, "r") == 0) { mode = FMODE_READ; break; } - if (strcmp(mode_str, "w") == 0) { mode = FMODE_WRITE; break; } - if (strcmp(mode_str, "a") == 0) { mode = FMODE_APPEND; break; } - if (strcmp(mode_str, "r+") == 0) { mode = FMODE_READ_EXT; break; } - if (strcmp(mode_str, "w+") == 0) { mode = FMODE_WRITE_EXT; break; } - if (strcmp(mode_str, "a+") == 0) { mode = FMODE_APPEND_EXT; break; } - - // TODO: (fmt, ...) va_arg for runtime error public api. - // If we reached here, that means it's an invalid mode string. - pkSetRuntimeError(vm, "Invalid mode string."); - return; - } while (false); - } - - FILE* fp = fopen(path, mode_str); - - if (fp != NULL) { - File* file = NEW_OBJ(File); - initObj(&file->_super, OBJ_FILE); - file->fp = fp; - file->mode = mode; - file->closed = false; - - pkReturnInstNative(vm, (void*)file, OBJ_FILE); - - } else { - pkReturnNull(vm); - } -} - -static void _fileRead(PKVM* vm) { - File* file; - if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; - - if (file->closed) { - pkSetRuntimeError(vm, "Cannot read from a closed file."); - return; - } - - if ((file->mode != FMODE_READ) && ((_FMODE_EXT & file->mode) == 0)) { - pkSetRuntimeError(vm, "File is not readable."); - return; - } - - // TODO: this is temporary. - char buff[2048]; - fread((void*)buff, sizeof(char), sizeof(buff), file->fp); - pkReturnString(vm, (const char*)buff); -} - -static void _fileWrite(PKVM* vm) { - File* file; - const char* text; uint32_t length; - if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; - if (!pkGetArgString(vm, 2, &text, &length)) return; - - if (file->closed) { - pkSetRuntimeError(vm, "Cannot write to a closed file."); - return; - } - - if ((file->mode != FMODE_WRITE) && ((_FMODE_EXT & file->mode) == 0)) { - pkSetRuntimeError(vm, "File is not writable."); - return; - } - - fwrite(text, sizeof(char), (size_t)length, file->fp); -} - -static void _fileClose(PKVM* vm) { - File* file; - if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; - - if (file->closed) { - pkSetRuntimeError(vm, "File already closed."); - return; - } - - if (fclose(file->fp) != 0) { - pkSetRuntimeError(vm, "fclose() failed!\n" \ - " at " __FILE__ ":" STRINGIFY(__LINE__) "."); - } - file->closed = true; -} - -void registerModuleFile(PKVM* vm) { - PkHandle* file = pkNewModule(vm, "File"); - - pkModuleAddFunction(vm, file, "open", _fileOpen, -1); - pkModuleAddFunction(vm, file, "read", _fileRead, 1); - pkModuleAddFunction(vm, file, "write", _fileWrite, 2); - pkModuleAddFunction(vm, file, "close", _fileClose, 1); - - pkReleaseHandle(vm, file); -} - -/*****************************************************************************/ -/* REGISTER MODULES */ -/*****************************************************************************/ - -void registerModules(PKVM* vm) { - registerModuleFile(vm); - registerModulePath(vm); -} diff --git a/cli/thirdparty/cwalk/LICENSE.md b/cli/modules/thirdparty/cwalk/LICENSE.md similarity index 100% rename from cli/thirdparty/cwalk/LICENSE.md rename to cli/modules/thirdparty/cwalk/LICENSE.md diff --git a/cli/thirdparty/cwalk/cwalk.c b/cli/modules/thirdparty/cwalk/cwalk.c similarity index 100% rename from cli/thirdparty/cwalk/cwalk.c rename to cli/modules/thirdparty/cwalk/cwalk.c diff --git a/cli/thirdparty/cwalk/cwalk.h b/cli/modules/thirdparty/cwalk/cwalk.h similarity index 100% rename from cli/thirdparty/cwalk/cwalk.h rename to cli/modules/thirdparty/cwalk/cwalk.h diff --git a/cli/thirdparty/dirent/LICENSE b/cli/modules/thirdparty/dirent/LICENSE similarity index 100% rename from cli/thirdparty/dirent/LICENSE rename to cli/modules/thirdparty/dirent/LICENSE diff --git a/cli/thirdparty/dirent/dirent.h b/cli/modules/thirdparty/dirent/dirent.h similarity index 100% rename from cli/thirdparty/dirent/dirent.h rename to cli/modules/thirdparty/dirent/dirent.h diff --git a/cli/modules/thirdparty/dlfcn-win32/COPYING b/cli/modules/thirdparty/dlfcn-win32/COPYING new file mode 100644 index 0000000..30e8e2e --- /dev/null +++ b/cli/modules/thirdparty/dlfcn-win32/COPYING @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/cli/modules/thirdparty/dlfcn-win32/dlfcn.c b/cli/modules/thirdparty/dlfcn-win32/dlfcn.c new file mode 100644 index 0000000..7277f0e --- /dev/null +++ b/cli/modules/thirdparty/dlfcn-win32/dlfcn.c @@ -0,0 +1,785 @@ +/* + * dlfcn-win32 + * Copyright (c) 2007 Ramiro Polla + * Copyright (c) 2015 Tiancheng "Timothy" Gu + * Copyright (c) 2019 Pali Rohár + * Copyright (c) 2020 Ralf Habacker + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef _DEBUG +#define _CRTDBG_MAP_ALLOC +#include +#include +#endif +#include +#include +#include + +/* Older versions do not have this type */ +#if _WIN32_WINNT < 0x0500 +typedef ULONG ULONG_PTR; +#endif + +/* Older SDK versions do not have these macros */ +#ifndef GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS +#define GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS 0x4 +#endif +#ifndef GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT +#define GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT 0x2 +#endif + +#ifdef _MSC_VER +/* https://docs.microsoft.com/en-us/cpp/intrinsics/returnaddress */ +#pragma intrinsic( _ReturnAddress ) +#else +/* https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html */ +#ifndef _ReturnAddress +#define _ReturnAddress( ) ( __builtin_extract_return_addr( __builtin_return_address( 0 ) ) ) +#endif +#endif + +#ifdef DLFCN_WIN32_SHARED +#define DLFCN_WIN32_EXPORTS +#endif +#include "dlfcn.h" + +#if defined( _MSC_VER ) && _MSC_VER >= 1300 +/* https://docs.microsoft.com/en-us/cpp/cpp/noinline */ +#define DLFCN_NOINLINE __declspec( noinline ) +#elif defined( __GNUC__ ) && ( ( __GNUC__ > 3 ) || ( __GNUC__ == 3 && __GNUC_MINOR__ >= 1 ) ) +/* https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html */ +#define DLFCN_NOINLINE __attribute__(( noinline )) +#else +#define DLFCN_NOINLINE +#endif + +/* Note: + * MSDN says these functions are not thread-safe. We make no efforts to have + * any kind of thread safety. + */ + +typedef struct local_object { + HMODULE hModule; + struct local_object *previous; + struct local_object *next; +} local_object; + +static local_object first_object; + +/* These functions implement a double linked list for the local objects. */ +static local_object *local_search( HMODULE hModule ) +{ + local_object *pobject; + + if( hModule == NULL ) + return NULL; + + for( pobject = &first_object; pobject; pobject = pobject->next ) + if( pobject->hModule == hModule ) + return pobject; + + return NULL; +} + +static BOOL local_add( HMODULE hModule ) +{ + local_object *pobject; + local_object *nobject; + + if( hModule == NULL ) + return TRUE; + + pobject = local_search( hModule ); + + /* Do not add object again if it's already on the list */ + if( pobject != NULL ) + return TRUE; + + for( pobject = &first_object; pobject->next; pobject = pobject->next ); + + nobject = (local_object *) malloc( sizeof( local_object ) ); + + if( !nobject ) + return FALSE; + + pobject->next = nobject; + nobject->next = NULL; + nobject->previous = pobject; + nobject->hModule = hModule; + + return TRUE; +} + +static void local_rem( HMODULE hModule ) +{ + local_object *pobject; + + if( hModule == NULL ) + return; + + pobject = local_search( hModule ); + + if( pobject == NULL ) + return; + + if( pobject->next ) + pobject->next->previous = pobject->previous; + if( pobject->previous ) + pobject->previous->next = pobject->next; + + free( pobject ); +} + +/* POSIX says dlerror( ) doesn't have to be thread-safe, so we use one + * static buffer. + * MSDN says the buffer cannot be larger than 64K bytes, so we set it to + * the limit. + */ +static char error_buffer[65535]; +static BOOL error_occurred; + +static void save_err_str( const char *str, DWORD dwMessageId ) +{ + DWORD ret; + size_t pos, len; + + len = strlen( str ); + if( len > sizeof( error_buffer ) - 5 ) + len = sizeof( error_buffer ) - 5; + + /* Format error message to: + * "": + */ + pos = 0; + error_buffer[pos++] = '"'; + memcpy( error_buffer + pos, str, len ); + pos += len; + error_buffer[pos++] = '"'; + error_buffer[pos++] = ':'; + error_buffer[pos++] = ' '; + + ret = FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwMessageId, + MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + error_buffer + pos, (DWORD) ( sizeof( error_buffer ) - pos ), NULL ); + pos += ret; + + /* When FormatMessageA() fails it returns zero and does not touch buffer + * so add trailing null byte */ + if( ret == 0 ) + error_buffer[pos] = '\0'; + + if( pos > 1 ) + { + /* POSIX says the string must not have trailing */ + if( error_buffer[pos-2] == '\r' && error_buffer[pos-1] == '\n' ) + error_buffer[pos-2] = '\0'; + } + + error_occurred = TRUE; +} + +static void save_err_ptr_str( const void *ptr, DWORD dwMessageId ) +{ + char ptr_buf[2 + 2 * sizeof( ptr ) + 1]; + char num; + size_t i; + + ptr_buf[0] = '0'; + ptr_buf[1] = 'x'; + + for( i = 0; i < 2 * sizeof( ptr ); i++ ) + { + num = (char) ( ( ( (ULONG_PTR) ptr ) >> ( 8 * sizeof( ptr ) - 4 * ( i + 1 ) ) ) & 0xF ); + ptr_buf[2 + i] = num + ( ( num < 0xA ) ? '0' : ( 'A' - 0xA ) ); + } + + ptr_buf[2 + 2 * sizeof( ptr )] = 0; + + save_err_str( ptr_buf, dwMessageId ); +} + +static HMODULE MyGetModuleHandleFromAddress( void *addr ) +{ + static BOOL (WINAPI *GetModuleHandleExAPtr)(DWORD, LPCSTR, HMODULE *) = NULL; + static BOOL failed = FALSE; + HMODULE kernel32; + HMODULE hModule; + MEMORY_BASIC_INFORMATION info; + SIZE_T sLen; + + if( !failed && GetModuleHandleExAPtr == NULL ) + { + kernel32 = GetModuleHandleA( "Kernel32.dll" ); + if( kernel32 != NULL ) + GetModuleHandleExAPtr = (BOOL (WINAPI *)(DWORD, LPCSTR, HMODULE *)) GetProcAddress( kernel32, "GetModuleHandleExA" ); + if( GetModuleHandleExAPtr == NULL ) + failed = TRUE; + } + + if( !failed ) + { + /* If GetModuleHandleExA is available use it with GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS */ + if( !GetModuleHandleExAPtr( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR) addr, &hModule ) ) + return NULL; + } + else + { + /* To get HMODULE from address use undocumented hack from https://stackoverflow.com/a/2396380 + * The HMODULE of a DLL is the same value as the module's base address. + */ + sLen = VirtualQuery( addr, &info, sizeof( info ) ); + if( sLen != sizeof( info ) ) + return NULL; + hModule = (HMODULE) info.AllocationBase; + } + + return hModule; +} + +/* Load Psapi.dll at runtime, this avoids linking caveat */ +static BOOL MyEnumProcessModules( HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded ) +{ + static BOOL (WINAPI *EnumProcessModulesPtr)(HANDLE, HMODULE *, DWORD, LPDWORD) = NULL; + static BOOL failed = FALSE; + UINT uMode; + HMODULE psapi; + + if( failed ) + return FALSE; + + if( EnumProcessModulesPtr == NULL ) + { + /* Windows 7 and newer versions have K32EnumProcessModules in Kernel32.dll which is always pre-loaded */ + psapi = GetModuleHandleA( "Kernel32.dll" ); + if( psapi != NULL ) + EnumProcessModulesPtr = (BOOL (WINAPI *)(HANDLE, HMODULE *, DWORD, LPDWORD)) GetProcAddress( psapi, "K32EnumProcessModules" ); + + /* Windows Vista and older version have EnumProcessModules in Psapi.dll which needs to be loaded */ + if( EnumProcessModulesPtr == NULL ) + { + /* Do not let Windows display the critical-error-handler message box */ + uMode = SetErrorMode( SEM_FAILCRITICALERRORS ); + psapi = LoadLibraryA( "Psapi.dll" ); + if( psapi != NULL ) + { + EnumProcessModulesPtr = (BOOL (WINAPI *)(HANDLE, HMODULE *, DWORD, LPDWORD)) GetProcAddress( psapi, "EnumProcessModules" ); + if( EnumProcessModulesPtr == NULL ) + FreeLibrary( psapi ); + } + SetErrorMode( uMode ); + } + + if( EnumProcessModulesPtr == NULL ) + { + failed = TRUE; + return FALSE; + } + } + + return EnumProcessModulesPtr( hProcess, lphModule, cb, lpcbNeeded ); +} + +DLFCN_EXPORT +void *dlopen( const char *file, int mode ) +{ + HMODULE hModule; + UINT uMode; + + error_occurred = FALSE; + + /* Do not let Windows display the critical-error-handler message box */ + uMode = SetErrorMode( SEM_FAILCRITICALERRORS ); + + if( file == NULL ) + { + /* POSIX says that if the value of file is NULL, a handle on a global + * symbol object must be provided. That object must be able to access + * all symbols from the original program file, and any objects loaded + * with the RTLD_GLOBAL flag. + * The return value from GetModuleHandle( ) allows us to retrieve + * symbols only from the original program file. EnumProcessModules() is + * used to access symbols from other libraries. For objects loaded + * with the RTLD_LOCAL flag, we create our own list later on. They are + * excluded from EnumProcessModules() iteration. + */ + hModule = GetModuleHandle( NULL ); + + if( !hModule ) + save_err_str( "(null)", GetLastError( ) ); + } + else + { + HANDLE hCurrentProc; + DWORD dwProcModsBefore, dwProcModsAfter; + char lpFileName[MAX_PATH]; + size_t i, len; + + len = strlen( file ); + + if( len >= sizeof( lpFileName ) ) + { + save_err_str( file, ERROR_FILENAME_EXCED_RANGE ); + hModule = NULL; + } + else + { + /* MSDN says backslashes *must* be used instead of forward slashes. */ + for( i = 0; i < len; i++ ) + { + if( file[i] == '/' ) + lpFileName[i] = '\\'; + else + lpFileName[i] = file[i]; + } + lpFileName[len] = '\0'; + + hCurrentProc = GetCurrentProcess( ); + + if( MyEnumProcessModules( hCurrentProc, NULL, 0, &dwProcModsBefore ) == 0 ) + dwProcModsBefore = 0; + + /* POSIX says the search path is implementation-defined. + * LOAD_WITH_ALTERED_SEARCH_PATH is used to make it behave more closely + * to UNIX's search paths (start with system folders instead of current + * folder). + */ + hModule = LoadLibraryExA( lpFileName, NULL, LOAD_WITH_ALTERED_SEARCH_PATH ); + + if( !hModule ) + { + save_err_str( lpFileName, GetLastError( ) ); + } + else + { + if( MyEnumProcessModules( hCurrentProc, NULL, 0, &dwProcModsAfter ) == 0 ) + dwProcModsAfter = 0; + + /* If the object was loaded with RTLD_LOCAL, add it to list of local + * objects, so that its symbols cannot be retrieved even if the handle for + * the original program file is passed. POSIX says that if the same + * file is specified in multiple invocations, and any of them are + * RTLD_GLOBAL, even if any further invocations use RTLD_LOCAL, the + * symbols will remain global. If number of loaded modules was not + * changed after calling LoadLibraryEx(), it means that library was + * already loaded. + */ + if( (mode & RTLD_LOCAL) && dwProcModsBefore != dwProcModsAfter ) + { + if( !local_add( hModule ) ) + { + save_err_str( lpFileName, ERROR_NOT_ENOUGH_MEMORY ); + FreeLibrary( hModule ); + hModule = NULL; + } + } + else if( !(mode & RTLD_LOCAL) && dwProcModsBefore == dwProcModsAfter ) + { + local_rem( hModule ); + } + } + } + } + + /* Return to previous state of the error-mode bit flags. */ + SetErrorMode( uMode ); + + return (void *) hModule; +} + +DLFCN_EXPORT +int dlclose( void *handle ) +{ + HMODULE hModule = (HMODULE) handle; + BOOL ret; + + error_occurred = FALSE; + + ret = FreeLibrary( hModule ); + + /* If the object was loaded with RTLD_LOCAL, remove it from list of local + * objects. + */ + if( ret ) + local_rem( hModule ); + else + save_err_ptr_str( handle, GetLastError( ) ); + + /* dlclose's return value in inverted in relation to FreeLibrary's. */ + ret = !ret; + + return (int) ret; +} + +DLFCN_NOINLINE /* Needed for _ReturnAddress() */ +DLFCN_EXPORT +void *dlsym( void *handle, const char *name ) +{ + FARPROC symbol; + HMODULE hCaller; + HMODULE hModule; + DWORD dwMessageId; + + error_occurred = FALSE; + + symbol = NULL; + hCaller = NULL; + hModule = GetModuleHandle( NULL ); + dwMessageId = 0; + + if( handle == RTLD_DEFAULT ) + { + /* The symbol lookup happens in the normal global scope; that is, + * a search for a symbol using this handle would find the same + * definition as a direct use of this symbol in the program code. + * So use same lookup procedure as when filename is NULL. + */ + handle = hModule; + } + else if( handle == RTLD_NEXT ) + { + /* Specifies the next object after this one that defines name. + * This one refers to the object containing the invocation of dlsym(). + * The next object is the one found upon the application of a load + * order symbol resolution algorithm. To get caller function of dlsym() + * use _ReturnAddress() intrinsic. To get HMODULE of caller function + * use MyGetModuleHandleFromAddress() which calls either standard + * GetModuleHandleExA() function or hack via VirtualQuery(). + */ + hCaller = MyGetModuleHandleFromAddress( _ReturnAddress( ) ); + + if( hCaller == NULL ) + { + dwMessageId = ERROR_INVALID_PARAMETER; + goto end; + } + } + + if( handle != RTLD_NEXT ) + { + symbol = GetProcAddress( (HMODULE) handle, name ); + + if( symbol != NULL ) + goto end; + } + + /* If the handle for the original program file is passed, also search + * in all globally loaded objects. + */ + + if( hModule == handle || handle == RTLD_NEXT ) + { + HANDLE hCurrentProc; + HMODULE *modules; + DWORD cbNeeded; + DWORD dwSize; + size_t i; + + hCurrentProc = GetCurrentProcess( ); + + /* GetModuleHandle( NULL ) only returns the current program file. So + * if we want to get ALL loaded module including those in linked DLLs, + * we have to use EnumProcessModules( ). + */ + if( MyEnumProcessModules( hCurrentProc, NULL, 0, &dwSize ) != 0 ) + { + modules = malloc( dwSize ); + if( modules ) + { + if( MyEnumProcessModules( hCurrentProc, modules, dwSize, &cbNeeded ) != 0 && dwSize == cbNeeded ) + { + for( i = 0; i < dwSize / sizeof( HMODULE ); i++ ) + { + if( handle == RTLD_NEXT && hCaller ) + { + /* Next modules can be used for RTLD_NEXT */ + if( hCaller == modules[i] ) + hCaller = NULL; + continue; + } + if( local_search( modules[i] ) ) + continue; + symbol = GetProcAddress( modules[i], name ); + if( symbol != NULL ) + { + free( modules ); + goto end; + } + } + + } + free( modules ); + } + else + { + dwMessageId = ERROR_NOT_ENOUGH_MEMORY; + goto end; + } + } + } + +end: + if( symbol == NULL ) + { + if( !dwMessageId ) + dwMessageId = ERROR_PROC_NOT_FOUND; + save_err_str( name, dwMessageId ); + } + + return *(void **) (&symbol); +} + +DLFCN_EXPORT +char *dlerror( void ) +{ + /* If this is the second consecutive call to dlerror, return NULL */ + if( !error_occurred ) + return NULL; + + /* POSIX says that invoking dlerror( ) a second time, immediately following + * a prior invocation, shall result in NULL being returned. + */ + error_occurred = FALSE; + + return error_buffer; +} + +/* See https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/march/inside-windows-an-in-depth-look-into-the-win32-portable-executable-file-format-part-2 + * for details */ + +/* Get specific image section */ +static BOOL get_image_section( HMODULE module, int index, void **ptr, DWORD *size ) +{ + IMAGE_DOS_HEADER *dosHeader; + IMAGE_OPTIONAL_HEADER *optionalHeader; + + dosHeader = (IMAGE_DOS_HEADER *) module; + + if( dosHeader->e_magic != 0x5A4D ) + return FALSE; + + optionalHeader = (IMAGE_OPTIONAL_HEADER *) ( (BYTE *) module + dosHeader->e_lfanew + 24 ); + + if( optionalHeader->Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC ) + return FALSE; + + if( index < 0 || index > IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR ) + return FALSE; + + if( optionalHeader->DataDirectory[index].Size == 0 || optionalHeader->DataDirectory[index].VirtualAddress == 0 ) + return FALSE; + + if( size != NULL ) + *size = optionalHeader->DataDirectory[index].Size; + + *ptr = (void *)( (BYTE *) module + optionalHeader->DataDirectory[index].VirtualAddress ); + + return TRUE; +} + +/* Return symbol name for a given address from export table */ +static const char *get_export_symbol_name( HMODULE module, IMAGE_EXPORT_DIRECTORY *ied, void *addr, void **func_address ) +{ + DWORD i; + void *candidateAddr = NULL; + int candidateIndex = -1; + BYTE *base = (BYTE *) module; + DWORD *functionAddressesOffsets = (DWORD *) (base + ied->AddressOfFunctions); + DWORD *functionNamesOffsets = (DWORD *) (base + ied->AddressOfNames); + USHORT *functionNameOrdinalsIndexes = (USHORT *) (base + ied->AddressOfNameOrdinals); + + for( i = 0; i < ied->NumberOfFunctions; i++ ) + { + if( (void *) ( base + functionAddressesOffsets[i] ) > addr || candidateAddr >= (void *) ( base + functionAddressesOffsets[i] ) ) + continue; + + candidateAddr = (void *) ( base + functionAddressesOffsets[i] ); + candidateIndex = i; + } + + if( candidateIndex == -1 ) + return NULL; + + *func_address = candidateAddr; + + for( i = 0; i < ied->NumberOfNames; i++ ) + { + if( functionNameOrdinalsIndexes[i] == candidateIndex ) + return (const char *) ( base + functionNamesOffsets[i] ); + } + + return NULL; +} + +static BOOL is_valid_address( void *addr ) +{ + MEMORY_BASIC_INFORMATION info; + SIZE_T result; + + if( addr == NULL ) + return FALSE; + + /* check valid pointer */ + result = VirtualQuery( addr, &info, sizeof( info ) ); + + if( result == 0 || info.AllocationBase == NULL || info.AllocationProtect == 0 || info.AllocationProtect == PAGE_NOACCESS ) + return FALSE; + + return TRUE; +} + +/* Return state if address points to an import thunk + * + * An import thunk is setup with a 'jmp' instruction followed by an + * absolute address (32bit) or relative offset (64bit) pointing into + * the import address table (iat), which is partially maintained by + * the runtime linker. + */ +static BOOL is_import_thunk( void *addr ) +{ + return *(short *) addr == 0x25ff ? TRUE : FALSE; +} + +/* Return adress from the import address table (iat), + * if the original address points to a thunk table entry. + */ +static void *get_address_from_import_address_table( void *iat, DWORD iat_size, void *addr ) +{ + BYTE *thkp = (BYTE *) addr; + /* Get offset from thunk table (after instruction 0xff 0x25) + * 4018c8 <_VirtualQuery>: ff 25 4a 8a 00 00 + */ + ULONG offset = *(ULONG *)( thkp + 2 ); +#ifdef _WIN64 + /* On 64 bit the offset is relative + * 4018c8: ff 25 4a 8a 00 00 jmpq *0x8a4a(%rip) # 40a318 <__imp_VirtualQuery> + * And can be also negative (MSVC in WDK) + * 100002f20: ff 25 3a e1 ff ff jmpq *-0x1ec6(%rip) # 0x100001060 + * So cast to signed LONG type + */ + BYTE *ptr = (BYTE *)( thkp + 6 + (LONG) offset ); +#else + /* On 32 bit the offset is absolute + * 4019b4: ff 25 90 71 40 00 jmp *0x40719 + */ + BYTE *ptr = (BYTE *) offset; +#endif + + if( !is_valid_address( ptr ) || ptr < (BYTE *) iat || ptr > (BYTE *) iat + iat_size ) + return NULL; + + return *(void **) ptr; +} + +/* Holds module filename */ +static char module_filename[2*MAX_PATH]; + +static BOOL fill_info( void *addr, Dl_info *info ) +{ + HMODULE hModule; + DWORD dwSize; + IMAGE_EXPORT_DIRECTORY *ied; + void *funcAddress = NULL; + + /* Get module of the specified address */ + hModule = MyGetModuleHandleFromAddress( addr ); + + if( hModule == NULL ) + return FALSE; + + dwSize = GetModuleFileNameA( hModule, module_filename, sizeof( module_filename ) ); + + if( dwSize == 0 || dwSize == sizeof( module_filename ) ) + return FALSE; + + info->dli_fname = module_filename; + info->dli_fbase = (void *) hModule; + + /* Find function name and function address in module's export table */ + if( get_image_section( hModule, IMAGE_DIRECTORY_ENTRY_EXPORT, (void **) &ied, NULL ) ) + info->dli_sname = get_export_symbol_name( hModule, ied, addr, &funcAddress ); + else + info->dli_sname = NULL; + + info->dli_saddr = info->dli_sname == NULL ? NULL : funcAddress != NULL ? funcAddress : addr; + + return TRUE; +} + +DLFCN_EXPORT +int dladdr( void *addr, Dl_info *info ) +{ + if( info == NULL ) + return 0; + + if( !is_valid_address( addr ) ) + return 0; + + if( is_import_thunk( addr ) ) + { + void *iat; + DWORD iatSize; + HMODULE hModule; + + /* Get module of the import thunk address */ + hModule = MyGetModuleHandleFromAddress( addr ); + + if( hModule == NULL ) + return 0; + + if( !get_image_section( hModule, IMAGE_DIRECTORY_ENTRY_IAT, &iat, &iatSize ) ) + { + /* Fallback for cases where the iat is not defined, + * for example i586-mingw32msvc-gcc */ + IMAGE_IMPORT_DESCRIPTOR *iid; + DWORD iidSize; + + if( !get_image_section( hModule, IMAGE_DIRECTORY_ENTRY_IMPORT, (void **) &iid, &iidSize ) ) + return 0; + + if( iid == NULL || iid->Characteristics == 0 || iid->FirstThunk == 0 ) + return 0; + + iat = (void *)( (BYTE *) hModule + iid->FirstThunk ); + /* We assume that in this case iid and iat's are in linear order */ + iatSize = iidSize - (DWORD) ( (BYTE *) iat - (BYTE *) iid ); + } + + addr = get_address_from_import_address_table( iat, iatSize, addr ); + + if( !is_valid_address( addr ) ) + return 0; + } + + if( !fill_info( addr, info ) ) + return 0; + + return 1; +} + +#ifdef DLFCN_WIN32_SHARED +BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) +{ + (void) hinstDLL; + (void) fdwReason; + (void) lpvReserved; + return TRUE; +} +#endif diff --git a/cli/modules/thirdparty/dlfcn-win32/dlfcn.h b/cli/modules/thirdparty/dlfcn-win32/dlfcn.h new file mode 100644 index 0000000..04188ca --- /dev/null +++ b/cli/modules/thirdparty/dlfcn-win32/dlfcn.h @@ -0,0 +1,94 @@ +/* + * dlfcn-win32 + * Copyright (c) 2007 Ramiro Polla + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef DLFCN_H +#define DLFCN_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(DLFCN_WIN32_SHARED) +#if defined(DLFCN_WIN32_EXPORTS) +# define DLFCN_EXPORT __declspec(dllexport) +#else +# define DLFCN_EXPORT __declspec(dllimport) +#endif +#else +# define DLFCN_EXPORT +#endif + +/* Relocations are performed when the object is loaded. */ +#define RTLD_NOW 0 + +/* Relocations are performed at an implementation-defined time. + * Windows API does not support lazy symbol resolving (when first reference + * to a given symbol occurs). So RTLD_LAZY implementation is same as RTLD_NOW. + */ +#define RTLD_LAZY RTLD_NOW + +/* All symbols are available for relocation processing of other modules. */ +#define RTLD_GLOBAL (1 << 1) + +/* All symbols are not made available for relocation processing by other modules. */ +#define RTLD_LOCAL (1 << 2) + +/* These two were added in The Open Group Base Specifications Issue 6. + * Note: All other RTLD_* flags in any dlfcn.h are not standard compliant. + */ + +/* The symbol lookup happens in the normal global scope. */ +#define RTLD_DEFAULT ((void *)0) + +/* Specifies the next object after this one that defines name. */ +#define RTLD_NEXT ((void *)-1) + +/* Structure filled in by dladdr() */ +typedef struct dl_info +{ + const char *dli_fname; /* Filename of defining object (thread unsafe and reused on every call to dladdr) */ + void *dli_fbase; /* Load address of that object */ + const char *dli_sname; /* Name of nearest lower symbol */ + void *dli_saddr; /* Exact value of nearest symbol */ +} Dl_info; + +/* Open a symbol table handle. */ +DLFCN_EXPORT void *dlopen(const char *file, int mode); + +/* Close a symbol table handle. */ +DLFCN_EXPORT int dlclose(void *handle); + +/* Get the address of a symbol from a symbol table handle. */ +DLFCN_EXPORT void *dlsym(void *handle, const char *name); + +/* Get diagnostic information. */ +DLFCN_EXPORT char *dlerror(void); + +/* Translate address to symbolic information (no POSIX standard) */ +DLFCN_EXPORT int dladdr(void *addr, Dl_info *info); + +#ifdef __cplusplus +} +#endif + +#endif /* DLFCN_H */ diff --git a/cli/native.py b/cli/native.py new file mode 100644 index 0000000..7f3c3d3 --- /dev/null +++ b/cli/native.py @@ -0,0 +1,122 @@ +#!python +## Copyright (c) 2020-2021 Thakee Nathees +## Copyright (c) 2021-2022 Pocketlang Contributors +## Distributed Under The MIT License + +import re, os +from os.path import (join, exists, abspath, + relpath, dirname) + +## The absolute path of this file, when run as a script. +## This file is not intended to be included in other files at the moment. +THIS_PATH = abspath(dirname(__file__)) + +POCKET_HEADER = join(THIS_PATH, "../src/include/pocketlang.h") +TARGET = join(THIS_PATH, "./modules/pknative.gen.c") + +PK_API = "pk_api" +PK_API_TYPE = "PkNativeApi" +API_DEF = f'''\ +static {PK_API_TYPE} {PK_API}; +void pkInitApi({PK_API_TYPE}* api) {{%s +}} +''' + +SOURCE_GEN = f'''\ +/* + * Copyright (c) 2020-2022 Thakee Nathees + * Copyright (c) 2021-2022 Pocketlang Contributors + * Distributed Under The MIT License + */ + +#include + +// !! THIS FILE IS GENERATED DO NOT EDIT !! + +''' + +def get_source(): + f = open(POCKET_HEADER, 'r') + source = f.read() + f.close() + start = source.find("POCKETLANG PUBLIC API") + source = source[start:] + return source + +MULTIPLE_WHITE_SPACES = re.compile(r"\s+") +def flaten(s): + return MULTIPLE_WHITE_SPACES.sub(" ", s).strip() + +def parse_definition(_def): + assert '\n' not in _def + pattern = r'PK_PUBLIC (.*?) ([a-zA-Z0-9_]+)\(' + match = re.search(pattern, _def) + assert(match) + return_type = match.group(1) + fn_name = match.group(2) + params = [] + if '()' in _def or '(void)' in _def: + pass ## No parameters. + else: + params_match = _def[_def.find('(')+1:_def.find(')')] + for param in params_match.split(','): + last_space = param.rfind(' ') + param_type = param[:last_space].strip() + param_name = param[last_space:].strip() + params.append((param_name, param_type)) + return fn_name, params, return_type + +def get_api_functions(): + api_functions = [] + source = get_source() + match = re.findall(r'^PK_PUBLIC [\S\n ]+?;', source, re.MULTILINE) + for m in match: + definition = flaten(m) + api_functions.append(parse_definition(definition)) + return api_functions + +def fn_typedefs(api_functions): + typedefs = "" + for fn, params, ret in api_functions: + params_join = ", ".join(map(lambda x: x[1], params)) + typedefs += f'typedef {ret} (*{fn}_t)({params_join});\n' + return typedefs + '\n' + +def api_typedef(api_functions): + typedef = "typedef struct {\n" + for fn, params, ret in api_functions: + typedef += f" {fn}_t {fn}_ptr;\n" + typedef += f"}} {PK_API_TYPE};\n" + return typedef + '\n' + +def define_functions(api_functions): + definitions = "" + for fn, params, ret in api_functions: + fn_def = "" + params_join = ", ".join(map(lambda x: x[1] + " " + x[0], params)) + param_names_join = ", ".join(map(lambda x: x[0], params)) + return_ = "" if ret == "void" else "return " + fn_def += f'{ret} {fn}({params_join}) {{\n' + fn_def += f" {return_}{PK_API}.{fn}_ptr({param_names_join});\n" + fn_def += "}\n\n" + definitions += fn_def + return definitions[:-1] ## Skip the last newline. + +def init_api(api_functions): + assign = "" + for fn, params, ret in api_functions: + assign += f"\n {PK_API}.{fn}_ptr = api->{fn}_ptr;" + return API_DEF % assign + '\n' + +def generate(): + api_functions = get_api_functions() + with open(TARGET, 'w') as fp: + fp.write(SOURCE_GEN) + fp.write(fn_typedefs(api_functions)) + fp.write(api_typedef(api_functions)) + fp.write(init_api(api_functions)) + fp.write(define_functions(api_functions)) + +if __name__ == "__main__": + generate() + print("Generated:", relpath(TARGET, os.getcwd())) diff --git a/cli/thirdparty.c b/cli/thirdparty.c deleted file mode 100644 index 2f32f99..0000000 --- a/cli/thirdparty.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2020-2022 Thakee Nathees - * Copyright (c) 2021-2022 Pocketlang Contributors - * Distributed Under The MIT License - */ - -// This file will include all thirdparty source files from the thirdparty sub -// directories here into a single file. That'll make it easy to compile with -// the command `gcc cli/*.c ...` instead of having to add every single sub -// directory to the list of source directory. - -// Library : cwalk -// Source : https://github.com/likle/cwalk/ -// Doc : https://likle.github.io/cwalk/ -// About : Path library for C/C++. Cross-Platform for Windows, MacOS and -// Linux. Supports UNIX and Windows path styles on those platforms. -#include "thirdparty/cwalk/cwalk.c" - -// Library : argparse -// Source : https://github.com/cofyc/argparse/ -// About : Command-line arguments parsing library. -#include "thirdparty/argparse/argparse.c" diff --git a/src/pk_compiler.c b/src/pk_compiler.c index cb4a16e..2f9723c 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -2070,6 +2070,7 @@ static int compilerAddVariable(Compiler* compiler, const char* name, } UNREACHABLE(); + return -1; } static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn, @@ -2655,6 +2656,7 @@ static int compilerImportName(Compiler* compiler, int line, } UNREACHABLE(); + return -1; } // This will called by the compilerImportAll() function to import a single diff --git a/src/pk_value.c b/src/pk_value.c index ca2c7d4..7e88a0d 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -40,6 +40,7 @@ PkVarType pkGetValueType(const PkVar value) { } UNREACHABLE(); + return PK_NULL; } PkHandle* pkNewString(PKVM* vm, const char* value) { @@ -872,6 +873,7 @@ static uint32_t _hashObject(Object* obj) { } UNREACHABLE(); + return 0; } uint32_t varHashValue(Var v) { @@ -1282,6 +1284,7 @@ bool instGetAttrib(PKVM* vm, Instance* inst, String* attrib, Var* value) { } UNREACHABLE(); + return false; } bool instSetAttrib(PKVM* vm, Instance* inst, String* attrib, Var value) { @@ -1338,6 +1341,7 @@ bool instSetAttrib(PKVM* vm, Instance* inst, String* attrib, Var value) { } UNREACHABLE(); + return false; } /*****************************************************************************/ @@ -1365,6 +1369,7 @@ const char* getPkVarTypeName(PkVarType type) { } UNREACHABLE(); + return NULL; } const char* getObjectTypeName(ObjectType type) { @@ -1382,6 +1387,7 @@ const char* getObjectTypeName(ObjectType type) { case OBJ_INST: return "Inst"; } UNREACHABLE(); + return NULL; } const char* varTypeName(Var v) { @@ -1774,4 +1780,5 @@ bool toBool(Var v) { } UNREACHABLE(); + return false; } diff --git a/src/pk_vm.c b/src/pk_vm.c index bf41a1a..0c50868 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -1664,5 +1664,6 @@ L_vm_main_loop: } - UNREACHABLE(); //return PK_RESULT_SUCCESS; + UNREACHABLE(); + return PK_RESULT_RUNTIME_ERROR; } diff --git a/tests/check.py b/tests/check.py index d6a3cc7..0fb8ef1 100644 --- a/tests/check.py +++ b/tests/check.py @@ -8,22 +8,12 @@ import os, sys, re from os import listdir -from os.path import join, abspath, dirname, relpath +from os.path import join, abspath, dirname, relpath, normpath ## The absolute path of this file, when run as a script. ## This file is not intended to be included in other files at the moment. THIS_PATH = abspath(dirname(__file__)) -## Converts a list of relative paths from the working directory -## to a list of relative paths from this file's absolute directory. -def to_abs_paths(sources): - return map(lambda s: os.path.join(THIS_PATH, s), sources) - -## Converts the path from absolute path to relative path from the -## toplelve of the project. -def to_rel_path(path): - return relpath(path, join(THIS_PATH, '..')) - ## A list of source files, to check if the fnv1a hash values match it's ## corresponding cstring in the CASE_ATTRIB(name, hash) macro calls. HASH_CHECK_LIST = [ @@ -37,13 +27,20 @@ CHECK_EXTENTIONS = ('.c', '.h', '.py', '.pk', '.js') ## A list of strings, if a line contains it we allow it to be longer than ## 79 characters, It's not "the correct way" but it works. -ALLOW_LONG = ('http://', 'https://', '