modules were moved to cli/modules

now it's easier to re-use cli modules into another hosting
application and a little python script have added to generate
native api to support native extension library.
This commit is contained in:
Thakee Nathees 2022-04-15 18:05:15 +05:30
parent ec569aba74
commit 5aee9616c1
22 changed files with 1379 additions and 281 deletions

35
cli/all.c Normal file
View File

@ -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"

View File

@ -16,14 +16,7 @@
#include <stdint.h>
#include <stdio.h>
// 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" \

View File

@ -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.

View File

@ -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 <stdio.h> //< Only needed here for ASSERT() macro and for release mode
//< TODO; macro use this to print a crash report.

91
cli/modules/modules.c Normal file
View File

@ -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);
}

View File

@ -4,35 +4,20 @@
* Distributed Under The MIT License
*/
#include "internal.h"
#ifndef MODULES_H
#define MODULES_H
#include <pocketlang.h>
#include "common.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
/*****************************************************************************/
/* 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

145
cli/modules/std_file.c Normal file
View File

@ -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);
}

View File

@ -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);
}

View File

@ -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.

View File

@ -0,0 +1,785 @@
/*
* dlfcn-win32
* Copyright (c) 2007 Ramiro Polla
* Copyright (c) 2015 Tiancheng "Timothy" Gu
* Copyright (c) 2019 Pali Rohár <pali.rohar@gmail.com>
* Copyright (c) 2020 Ralf Habacker <ralf.habacker@freenet.de>
*
* 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 <stdlib.h>
#include <crtdbg.h>
#endif
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
/* 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:
* "<argument to function that failed>": <Windows localized error message>
*/
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 <newline> */
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

View File

@ -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 */

122
cli/native.py Normal file
View File

@ -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 <pocketlang.h>
// !! 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()))

View File

@ -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"

View File

@ -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

View File

@ -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;
}

View File

@ -1664,5 +1664,6 @@ L_vm_main_loop:
}
UNREACHABLE(); //return PK_RESULT_SUCCESS;
UNREACHABLE();
return PK_RESULT_RUNTIME_ERROR;
}

View File

@ -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://', '<script ', '<link ', '<svg ')
ALLOW_LONG_LINES = ('http://', 'https://', '<script ', '<link ', '<svg ')
## A list of files that are allowed to be longer than 79 characters.
ALLOW_LONG_FILES = (
"../cli/native.py",
"../cli/modules/pknative.gen.c",
)
## A list of directory, contains C source files to perform static checks.
## This will include all files with extension from CHECK_EXTENTIONS.
SOURCE_DIRS = [
"../src/",
"../cli/",
"../cli/modules/",
"../docs/",
"../docs/wasm/",
@ -52,6 +49,16 @@ SOURCE_DIRS = [
## This global variable will be set to true if any check failed.
checks_failed = False
## 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, '..'))
def main():
check_fnv1_hash(to_abs_paths(HASH_CHECK_LIST))
check_static(to_abs_paths(SOURCE_DIRS))
@ -90,7 +97,8 @@ def check_static(dirs):
if not file.endswith(CHECK_EXTENTIONS): continue
if os.path.isdir(join(dir, file)): continue
fp = open(join(dir, file), 'r')
curr_file = normpath(join(dir, file))
fp = open(curr_file, 'r')
## Path of the file relative to top-level.
file_path = to_rel_path(join(dir, file))
@ -112,8 +120,15 @@ def check_static(dirs):
if len(line) >= 80:
skip = False
for ignore in ALLOW_LONG:
if ignore in line: skip = True; break
for ignore in ALLOW_LONG_LINES:
if ignore in line:
skip = True
break
for ignore in ALLOW_LONG_FILES:
## TODO: the bellow normpath(join()) should be calcuated once.
if curr_file == normpath(join(THIS_PATH, ignore)):
skip = True
break
if skip: continue
_location = location(file_path, line_no)