Major refactoring (and overcomplicating) of compiler and messages to prepare for threaded dynamic code.

This commit is contained in:
Scott Duensing 2023-05-09 20:02:31 -05:00
parent cc6eb18baf
commit 721b23d584
8 changed files with 351 additions and 177 deletions

View file

@ -24,7 +24,57 @@
#define COMPILER_H
int compilerRunRecipe(char *recipe, char *input, char *outputPath, void *context);
#include "libtcc.h"
#include "sigsegv.h"
#if HAVE_SIGSEGV_RECOVERY
#include <setjmp.h>
#include <signal.h>
#if defined _WIN32 && !defined __CYGWIN__
// Windows doesn't have sigset_t.
typedef int sigset_t;
#define sigemptyset(set)
#define sigprocmask(how,set,oldset)
#endif
#endif
enum CompilerErrorsE {
COMPILER_ERROR_NONE = 0,
COMPILER_ERROR_ALREADY_RUNNING,
COMPILER_ERROR_IN_CODE,
COMPILER_ERROR_CANNOT_RELOCATE,
COMPILER_ERROR_NO_ENTRYPOINT,
COMPILER_ERROR_SIGHANDLER_FAILED,
COMPILER_ERROR_SEGFAULT,
COMPILER_ERROR_COUNT
};
struct CompilerContextS;
typedef void (*CompilerCallback)(struct CompilerContextS **context);
typedef struct CompilerContextS {
TCCState *s;
int compilerResult;
int programResult;
CompilerCallback callback;
void *userData;
#if HAVE_SIGSEGV_RECOVERY
volatile int runPass;
jmp_buf runJump;
sigset_t runSigSet;
#endif
} CompilerContextT;
void compilerDeleteContext(CompilerContextT **context);
gboolean compilerHadError(CompilerContextT **context);
CompilerContextT *compilerNewContext(CompilerCallback callback, void *userData);
void compilerRunRecipe(CompilerContextT *context, char *recipe, char *input, char *outputPath);
#endif // COMPILER_H

View file

@ -35,6 +35,8 @@ typedef enum MessageTypesE MessageTypesT;
void message(MessageTypesT level, char *format, ...);
void messageShutdown(void);
void messageStartup(void);
#endif //MESSAGES_H
#endif // MESSAGES_H

View file

@ -20,10 +20,10 @@
*/
#include <pthread.h>
#include "common.h"
#include "compiler.h"
#include "libtcc.h"
#include "sigsegv.h"
#include "project.h"
#include "messages.h"
#include "utils.h"
@ -31,156 +31,214 @@
#if HAVE_SIGSEGV_RECOVERY
#include <setjmp.h>
#include <signal.h>
#if defined _WIN32 && !defined __CYGWIN__
// Windows doesn't have sigset_t.
typedef int sigset_t;
#define sigemptyset(set)
#define sigprocmask(how,set,oldset)
#endif
volatile int runPass = 0;
jmp_buf runRecipe;
sigset_t runSigSet;
static void sigHandlerContinuation(void *arg1, void *arg2, void *arg3);
int sigHandler(void *faultAddress, int serious);
#endif
static CompilerContextT *_currentRunningContext = NULL; // Right now we can only have one compile running at a time.
char **___recipeTargets = NULL;
static void compilerErrorHandler(void *opaque, const char *msg);
// Exposed to dynamic code. Not static.
void recipeAddTarget(char *target);
void recipeMessage(char *format, ...);
static void compilerErrorHandler(void *opaque, const char *msg) {
char *isWarning = strstr(msg, " warning: ");
(void)opaque;
message(isWarning == NULL ? MSG_ERROR : MSG_WARN, "%s", msg);
void compilerDeleteContext(CompilerContextT **context) {
CompilerContextT *c = *context;
tcc_delete(c->s);
DEL(c);
}
// All the paths passed in here are expected to be complete and absolute.
int compilerRunRecipe(char *recipe, char *input, char *outputPath, void *context) {
TCCState *s;
char *oldLocation;
char c;
static void compilerErrorHandler(void *opaque, const char *msg) {
(void)opaque;
message(strstr(msg, " warning: ") == NULL ? MSG_ERROR : MSG_WARN, "%s", msg);
}
gboolean compilerHadError(CompilerContextT **context) {
CompilerContextT *c = *context;
if (c->compilerResult != COMPILER_ERROR_NONE) {
// Handle errors not handled elsewhere.
switch (c->compilerResult) {
case COMPILER_ERROR_ALREADY_RUNNING:
message(MSG_ERROR, "Compiled code is already running");
break;
case COMPILER_ERROR_CANNOT_RELOCATE:
message(MSG_ERROR, "Unable to link compiled code");
break;
case COMPILER_ERROR_NO_ENTRYPOINT:
message(MSG_ERROR, "Compiled code is missing entrypoint");
break;
case COMPILER_ERROR_SIGHANDLER_FAILED:
message(MSG_ERROR, "Unable to set up sigsegv handler");
break;
}
return TRUE;
}
return FALSE;
}
CompilerContextT *compilerNewContext(CompilerCallback callback, void *userData) {
CompilerContextT *c;
int x;
int result = -255;
int (*entry)(char *, char *);
char ch;
___recipeTargets = NULL;
c = NEW(CompilerContextT);
s = tcc_new();
if (!s) {
c->compilerResult = COMPILER_ERROR_NONE;
c->programResult = 0;
c->callback = callback;
c->userData = userData;
c->s = tcc_new();
if (!c->s) {
DEL(c);
// Something bad happened.
return -1;
return NULL;
}
// __resourcePath comes in with a trailing slash. Temporarily remove it.
x = (int)strlen(__resourcePath) - 1;
c = __resourcePath[x];
ch = __resourcePath[x];
__resourcePath[x] = 0;
tcc_set_lib_path(s, __resourcePath);
tcc_add_sysinclude_path(s, __resourcePath);
__resourcePath[x] = c;
tcc_set_lib_path(c->s, __resourcePath);
tcc_add_sysinclude_path(c->s, __resourcePath);
__resourcePath[x] = ch;
tcc_set_options(s, "-Wall -Wno-write-strings");
tcc_set_error_func(s, stdout, compilerErrorHandler);
tcc_set_output_type(s, TCC_OUTPUT_MEMORY);
tcc_set_options(c->s, "-Wall -Wno-write-strings"); // Debug options not working right: -g -bt 5
tcc_set_error_func(c->s, NULL, compilerErrorHandler);
tcc_set_output_type(c->s, TCC_OUTPUT_MEMORY);
if (tcc_add_file(s, recipe) < 0) {
tcc_add_symbol(c->s, "fputc", fputc);
tcc_add_symbol(c->s, "qsort", qsort);
return c;
}
void compilerRunRecipe(CompilerContextT *context, char *recipe, char *input, char *outputPath) {
char *oldLocation;
int (*entry)(char *, char *);
void *self;
// All the paths passed in here are expected to be complete and absolute.
//***TODO*** Oh my god this is so bad. The next line depends on 'userData' being set to 'self' when called from project.c.
self = context->userData;
if (_currentRunningContext != NULL) {
context->compilerResult = COMPILER_ERROR_ALREADY_RUNNING;
context->callback(&context);
return;
}
___recipeTargets = NULL;
if (tcc_add_file(context->s, recipe) < 0) {
// Errors in code.
tcc_delete(s);
return -4;
context->compilerResult = COMPILER_ERROR_IN_CODE;
context->callback(&context);
return;
}
tcc_add_symbol(s, "fputc", fputc);
tcc_add_symbol(s, "qsort", qsort);
tcc_add_symbol(context->s, "___recipeTargets", ___recipeTargets);
tcc_add_symbol(context->s, "recipeAddTarget", recipeAddTarget);
tcc_add_symbol(context->s, "recipeMessage", recipeMessage);
tcc_add_symbol(context->s, "utilCreateString", utilCreateString);
tcc_add_symbol(s, "___recipeTargets", ___recipeTargets);
tcc_add_symbol(s, "recipeAddTarget", recipeAddTarget);
tcc_add_symbol(s, "recipeMessage", recipeMessage);
tcc_add_symbol(s, "utilCreateString", utilCreateString);
if (tcc_relocate(s, TCC_RELOCATE_AUTO) < 0) {
if (tcc_relocate(context->s, TCC_RELOCATE_AUTO) < 0) {
// Something bad happened.
tcc_delete(s);
return -2;
context->compilerResult = COMPILER_ERROR_CANNOT_RELOCATE;
context->callback(&context);
return;
}
entry = tcc_get_symbol(s, "recipe");
entry = tcc_get_symbol(context->s, "recipe");
if (!entry) {
// Something bad happened.
tcc_delete(s);
return -3;
context->compilerResult = COMPILER_ERROR_NO_ENTRYPOINT;
context->callback(&context);
return;
}
getcwd(__utilFilenameBuffer, FILENAME_MAX);
oldLocation = strdup(__utilFilenameBuffer);
chdir(outputPath);
_currentRunningContext = context;
#if HAVE_SIGSEGV_RECOVERY
sigset_t emptySet;
runPass = 0;
context->runPass = 0;
if (sigsegv_install_handler(&sigHandler) < 0)
return -5;
if (sigsegv_install_handler(&sigHandler) < 0) {
context->compilerResult = COMPILER_ERROR_SIGHANDLER_FAILED;
context->callback(&context);
return;
}
sigemptyset(&emptySet);
sigprocmask(SIG_BLOCK, &emptySet, &runSigSet);
sigprocmask(SIG_BLOCK, &emptySet, &context->runSigSet);
switch (setjmp(runRecipe)) {
switch (setjmp(context->runJump)) {
case 0:
result = entry(input, outputPath);
context->programResult = entry(input, outputPath);
case 1:
sigprocmask(SIG_SETMASK, &runSigSet, NULL);
sigprocmask(SIG_SETMASK, &context->runSigSet, NULL);
sigsegv_install_handler(NULL);
if (runPass != 0) {
if (context->runPass != 0) {
message(MSG_SEVERE, "%s caused a segmentation fault!", recipe);
context->compilerResult = COMPILER_ERROR_SEGFAULT;
}
break;
}
context->callback(&context);
#else
result = entry(input, outputPath);
context->programResult = entry(input, outputPath);
context->callback(&context);
#endif
_currentRunningContext = NULL;
chdir(oldLocation);
DEL(oldLocation);
while (arrlen(___recipeTargets) > 0) {
if (projectAddToTree(context, ___recipeTargets[0])) {
if (projectAddToTree(self, ___recipeTargets[0])) {
utilSetDirty((WindowDataT *)context, TRUE);
}
DEL(___recipeTargets[0]);
arrdel(___recipeTargets, 0);
}
ARRFREE(___recipeTargets);
tcc_delete(s);
return result;
}
#if HAVE_SIGSEGV_RECOVERY
static void sigHandlerContinuation(void *arg1, void *arg2, void *arg3) {
(void)arg1;
CompilerContextT *c = (CompilerContextT *)arg1;
(void)arg2;
(void)arg3;
longjmp(runRecipe, runPass);
longjmp(c->runJump, c->runPass);
}
@ -188,11 +246,12 @@ int sigHandler(void *faultAddress, int serious) {
(void)faultAddress;
(void)serious;
runPass++;
return sigsegv_leave_handler(sigHandlerContinuation, NULL, NULL, NULL);
_currentRunningContext->runPass++;
return sigsegv_leave_handler(sigHandlerContinuation, _currentRunningContext, NULL, NULL);
}
#endif
void recipeAddTarget(char *target) {
arrput(___recipeTargets, strdup(target));
}

View file

@ -28,6 +28,7 @@
#include "ssh.h"
#include "utils.h"
#include "cwalk.h"
#include "messages.h"
char *__resourcePath = NULL;
@ -47,6 +48,7 @@ int main(int argc, char **argv) {
gtk_init(&argc, &argv);
utilStartup();
messageStartup();
// Make sure we have a config & resources folder.
__resourcePath = utilCreateString("%s%cjoeydev%cresources%c", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR, UTIL_PATH_CHAR);
@ -79,6 +81,7 @@ int main(int argc, char **argv) {
scintilla_release_resources();
sshShutdown();
httpShutdown();
messageShutdown();
utilShutdown();
DEL(__resourcePath);

View file

@ -20,21 +20,74 @@
*/
#include <pthread.h>
#include "common.h"
#include "messages.h"
#include "utils.h"
static GtkWidget *_lstMessages = NULL;
static int _autoScroll = 0;
static char **_pendingMessages = NULL;
static pthread_mutex_t _mtxMessage;
static gboolean messagesScroll(gpointer userData);
static gboolean messagesUpdate(gpointer userData);
EVENT gboolean winMessagesClose(GtkWidget *object, gpointer userData);
static void winMessagesDelete(gpointer userData);
void message(MessageTypesT level, char *format, ...) {
va_list args;
char *string;
char *msg;
char *tok;
char *labels[MSG_COUNT] = {
"<span foreground=\"gray\"> Info:</span>",
"<span foreground=\"yellow\">Warning:</span>",
"<span foreground=\"red\"> Error:</span>",
"<span foreground=\"red\"><b> Severe:</b></span>"
};
// Construct message.
va_start(args, format);
msg = utilCreateStringVArgs(format, args);
va_end(args);
// Break multiline messages down into individual lines.
tok = strtok(msg, "\n");
while (tok != NULL) {
//***TODO*** Filter out things that could be mistaken as markup tags.
string = utilCreateString("<tt>%s %s</tt>", labels[level], tok);
// Save for UI thread. message() is not always called from the UI thread!
pthread_mutex_lock(&_mtxMessage);
arrput(_pendingMessages, string); // Do not free string.
pthread_mutex_unlock(&_mtxMessage);
tok = strtok(NULL, "\n");
}
}
void messageShutdown(void) {
g_idle_remove_by_data(messagesUpdate);
pthread_mutex_destroy(&_mtxMessage);
}
void messageStartup(void) {
if (pthread_mutex_init(&_mtxMessage, NULL) != 0) {
debug("Message mutex init failed!\n");
exit(1);
}
// Set up updates on the UI thread.
g_idle_add(messagesUpdate, messagesUpdate);
}
static gboolean messagesUpdate(gpointer userData) {
WindowDataT *self = NULL;
char *widgetNames[] = {
"winMessages",
@ -48,18 +101,23 @@ void message(MessageTypesT level, char *format, ...) {
GtkWidget *row;
GtkWidget *box;
GtkWidget *label;
va_list args;
char *string;
char *msg;
char *tok;
char *labels[MSG_COUNT] = {
"<span foreground=\"gray\"> Info:</span>",
"<span foreground=\"yellow\">Warning:</span>",
"<span foreground=\"red\"> Error:</span>",
"<span foreground=\"red\"><b> Severe:</b></span>"
};
GtkAdjustment *adjustment;
char *string = NULL;
// Do we need to open this window?
(void)userData;
// Lock and access the pending message list.
pthread_mutex_lock(&_mtxMessage);
if (arrlen(_pendingMessages) > 0) {
string = _pendingMessages[0]; // We just need the pointer.
arrdel(_pendingMessages, 0);
}
pthread_mutex_unlock(&_mtxMessage);
// Are there pending messages?
if (string != NULL) {
// Do we need to open the message window?
if (!_lstMessages) {
// Set up instance data. We only need WindowDataT since this is a "singleton" window.
self = NEW(WindowDataT);
@ -73,58 +131,29 @@ void message(MessageTypesT level, char *format, ...) {
// Show window.
gtk_widget_show_all(self->window);
// Set up automatic scrolling.
g_idle_add(messagesScroll, messagesScroll);
}
// Display message.
va_start(args, format);
msg = utilCreateStringVArgs(format, args);
va_end(args);
// Break multiline messages down into individual lines.
tok = strtok(msg, "\n");
while (tok != NULL) {
//***TODO*** Filter out things that could be mistaken as markup tags.
string = utilCreateString("<tt>%s %s</tt>", labels[level], tok);
// Create new row with the message string.
row = gtk_list_box_row_new();
box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
gtk_widget_set_hexpand(box, TRUE);
label = gtk_label_new(string);
gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
DEL(string);
// Add new row to the message box.
gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
gtk_container_add(GTK_CONTAINER(row), box);
gtk_list_box_insert(GTK_LIST_BOX(_lstMessages), row, -1);
// Force paint.
gtk_widget_show_all(row);
utilForceUpdate();
_autoScroll = 15; // Try several times to show the new row.
tok = strtok(NULL, "\n");
}
}
static gboolean messagesScroll(gpointer userData) {
GtkAdjustment *adjustment;
(void)userData;
if (_autoScroll <= 0) return G_SOURCE_CONTINUE;
// Scroll to show new line.
// Scroll to show new row.
//***TODO*** This doesn't always scroll to the very end.
adjustment = gtk_list_box_get_adjustment(GTK_LIST_BOX(_lstMessages));
gtk_adjustment_set_value(adjustment, gtk_adjustment_get_upper(adjustment));
gtk_widget_show_all(_lstMessages);
utilForceUpdate();
_autoScroll--;
DEL(string); // Finally free the string allocated in message().
}
return G_SOURCE_CONTINUE;
}
@ -142,7 +171,13 @@ EVENT gboolean winMessagesClose(GtkWidget *object, gpointer userData) {
static void winMessagesDelete(gpointer userData) {
g_idle_remove_by_data(messagesScroll);
// Delete any pending messages.
while (arrlen(_pendingMessages) > 0) {
DEL(_pendingMessages[0]);
arrdel(_pendingMessages, 0);
}
ARRFREE(_pendingMessages);
utilWindowUnRegister(userData);
_lstMessages = NULL;
DEL(userData);

View file

@ -85,6 +85,7 @@ typedef struct ProjectDataS {
char *buildPassword;
TargetT **targets;
GtkWidget *tempWidget; // Used to pass data around dialogs.
int tempInteger; // Used during recipe building.
} ProjectDataT;
typedef struct SectionDataS {
@ -107,7 +108,6 @@ static SectionDataT _sectionData[] = {
{ NULL, NULL }
};
static ProjectDataT *_cookingProjectData = NULL;
static char **_sendList = NULL;
@ -120,6 +120,7 @@ EVENT void btnEditRawClicked(GtkButton *widget, gpointer userData);
EVENT void btnEditRecipeClicked(GtkButton *widget, gpointer userData);
EVENT void btnNewRecipeClicked(GtkButton *widget, gpointer userData);
static void clearRecipeData(ProjectDataT *self);
static void cookFinished(CompilerContextT **context);
static void decompressBuild(ArchiveT *archive);
static void dialogCookOptions(char *filename, ProjectDataT *self);
static int findRecipeData(ProjectDataT *self, char *key);
@ -244,6 +245,50 @@ static void clearRecipeData(ProjectDataT *self) {
}
static void cookFinished(CompilerContextT **context) {
CompilerContextT *ctx = *context;
ProjectDataT *self = (ProjectDataT *)ctx->userData;
char *raw;
char *recipe;
// Was there a compiler error?
if (compilerHadError(context)) {
compilerDeleteContext(context);
return;
}
// If this is not first call, process context.
if (self->tempInteger != -1) {
if (ctx->programResult != 0) {
//***TODO*** Not all negative returns are severe.
message(ctx->programResult > 0 ? MSG_ERROR : MSG_SEVERE, "Recipe %s returned %d", self->recipes[self->tempInteger]->value, ctx->programResult);
}
}
compilerDeleteContext(context);
// Is there something to cook?
self->tempInteger++;
if (self->tempInteger < arrlen(self->recipes)) {
// Build path names.
cwk_path_change_basename(self->windowData.filename, self->recipes[self->tempInteger]->key, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
raw = strdup(__utilFilenameBuffer);
cwk_path_change_basename(self->windowData.filename, self->recipes[self->tempInteger]->value, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
recipe = strdup(__utilFilenameBuffer);
// Run it!
message(MSG_INFO, "Cooking %s", self->recipes[self->tempInteger]->key);
ctx = compilerNewContext(cookFinished, self);
compilerRunRecipe(ctx, recipe, raw, self->windowData.path);
DEL(recipe);
DEL(raw);
} else {
// Finished with recipe list.
message(MSG_INFO, "Finished Cooking");
}
}
static void decompressBuild(ArchiveT *archive) {
ProjectDataT *self = (ProjectDataT *)archive->userData;
char *temp;
@ -254,6 +299,7 @@ static void decompressBuild(ArchiveT *archive) {
DEL(temp);
//***TODO*** Process build results.
message(MSG_INFO, "Processing build results");
}
@ -923,38 +969,14 @@ EVENT void menuProjectBuildTargets(GtkWidget *object, gpointer userData) {
EVENT void menuProjectBuildCookRecipes(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
int i;
char *raw;
char *recipe;
int result;
CompilerContextT *ctx;
// Only one cook at a time. Should not be able to happen.
if (_cookingProjectData) {
message(MSG_ERROR, "Cook currently running");
return;
// Are there recipes to cook?
if (arrlen(self->recipes) > 0) {
self->tempInteger = -1; // Tell cookFinished() we're just starting.
ctx = compilerNewContext(cookFinished, self);
cookFinished(&ctx);
}
// Remember who started the cook.
_cookingProjectData = self;
for (i=0; i<arrlen(self->recipes); i++) {
// Build pathnames.
cwk_path_change_basename(self->windowData.filename, self->recipes[i]->key, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
raw = strdup(__utilFilenameBuffer);
cwk_path_change_basename(self->windowData.filename, self->recipes[i]->value, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
recipe = strdup(__utilFilenameBuffer);
// Run it!
message(MSG_INFO, "Cooking %s", self->recipes[i]->key);
result = compilerRunRecipe(recipe, raw, self->windowData.path, self);
if (result != 0) {
//***TODO*** Not all negative returns are severe.
message(result > 0 ? MSG_ERROR : MSG_SEVERE, "Recipe %s returned %d", self->recipes[i]->value, result);
}
message(MSG_INFO, "Finished Cooking");
}
_cookingProjectData = NULL;
}
@ -975,8 +997,6 @@ EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData) {
gboolean archPrinted;
FILE *out;
menuProjectBuildCookRecipes(object, userData);
ssh = NEW(SSHT);
ssh = sshConnect(self->buildHost, self->buildSSHPort, self->buildUser, self->buildPassword);

View file

@ -118,6 +118,8 @@ SSHT *sshConnect(char *hostname, uint16_t port, char *user, char *password) {
}
}
//***TODO*** Move SFTP session setup here so we don't keep repeating it.
return data;
}
@ -125,6 +127,8 @@ SSHT *sshConnect(char *hostname, uint16_t port, char *user, char *password) {
void sshDisconnect(SSHT **sshData) {
SSHT *data = *sshData;
//***TODO*** Move SFTP session teardown here so we don't keep repeating it.
libssh2_session_disconnect(data->session, "Normal Shutdown.");
libssh2_session_free(data->session);
socketclose(data->sock);

View file

@ -233,6 +233,7 @@ gboolean utilDecompressUpdate(gpointer userData) {
if (toClose >= 0) {
a = _activeArchives[toClose];
arrdel(_activeArchives, toClose);
if (a->callback) {
a->callback(a);
}