From ccb449ae57786ee27cfe499ffa717a59f7d6422a Mon Sep 17 00:00:00 2001 From: fedir Date: Mon, 28 Apr 2025 10:11:50 +0200 Subject: [PATCH 01/33] Added a new dialogue --- src/gui/Makefile | 66 ++++++++++++++++++++++++++++++++++++++++++ src/gui/zenity-clone.c | 41 ++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/gui/Makefile create mode 100644 src/gui/zenity-clone.c diff --git a/src/gui/Makefile b/src/gui/Makefile new file mode 100644 index 0000000..f5cdba7 --- /dev/null +++ b/src/gui/Makefile @@ -0,0 +1,66 @@ +SHELL=/bin/bash + +# configurable options + +SOURCES_DIR := . +TESTS_DIR := . +BUILD_DIR := . + +CC := gcc +CXX := g++ + + +# dependencies + +PACKAGE_NAMES := gtk4 libadwaita-1 + +ifeq ($(TEST), 1) + # PACKAGE_NAMES += check # TODO: use check? +endif + + +# set up cflags and libs + +CFLAGS := -D_FILE_OFFSET_BITS=64 +LDFLAGS := + +CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES)) +LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES)) + +ifeq ($(DEBUG),1) + CFLAGS += -O0 -pedantic -g -Wall -Wextra -Wcast-align \ + -Wcast-qual -Wdisabled-optimization -Wformat=2 \ + -Winit-self -Wlogical-op -Wmissing-declarations \ + -Wmissing-include-dirs -Wredundant-decls -Wshadow \ + -Wsign-conversion -Wstrict-overflow=5 \ + -Wswitch-default -Wundef -Wno-unused + LDFLAGS += +else + CFLAGS += -O3 + LDFLAGS += +endif + + +# set up targets + +TARGETS := $(BUILD_DIR)/zenity + +ifeq ($(TEST), 1) + #TARGETS += icfs_test +endif + + +# build! + +default: $(TARGETS) + +.PHONY: clean + +$(BUILD_DIR)/zenity: $(BUILD_DIR)/zenity.o + $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/zenity + +$(BUILD_DIR)/zenity.o: $(SOURCES_DIR)/zenity-clone.c + $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $(BUILD_DIR)/zenity.o + +clean: + rm $(BUILD_DIR)/*.o $(BUILD_DIR)/zenity diff --git a/src/gui/zenity-clone.c b/src/gui/zenity-clone.c new file mode 100644 index 0000000..071b6c1 --- /dev/null +++ b/src/gui/zenity-clone.c @@ -0,0 +1,41 @@ +#include "gio/gio.h" +#include +#include + +static void on_activate(GtkApplication *app, gpointer user_data) { + // Create the main window + AdwMessageDialog *dialog = ADW_MESSAGE_DIALOG( + adw_message_dialog_new(NULL, "Allow access?", "allow_access?")); + + // Set the dialog as transient for the (non-existent) parent window + gtk_window_set_application(GTK_WINDOW(dialog), app); + + // Add response buttons + adw_message_dialog_add_response(dialog, "cancel", "Cancel"); + adw_message_dialog_add_response(dialog, "ok", "OK"); + + // Set default response + adw_message_dialog_set_default_response(dialog, "ok"); + + // Set close response (when clicking X) + adw_message_dialog_set_close_response(dialog, "cancel"); + + // Connect response handler + // g_signal_connect(dialog, "response", G_CALLBACK(gtk_window_close), dialog); + + // Show the dialog + gtk_window_present(GTK_WINDOW(dialog)); +} + +int main(int argc, char **argv) { + // Create a new application + AdwApplication *app = adw_application_new("com.example.zenityclone", + G_APPLICATION_DEFAULT_FLAGS); + g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL); + + // Run the application + int status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + + return status; +} From 747077f365e98fd966255e2b22fecb7dc15236b7 Mon Sep 17 00:00:00 2001 From: fedir Date: Thu, 1 May 2025 16:13:31 +0200 Subject: [PATCH 02/33] Update Makefile for the zenity clone --- src/gui/Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gui/Makefile b/src/gui/Makefile index f5cdba7..107b266 100644 --- a/src/gui/Makefile +++ b/src/gui/Makefile @@ -46,7 +46,7 @@ endif TARGETS := $(BUILD_DIR)/zenity ifeq ($(TEST), 1) - #TARGETS += icfs_test + TARGETS += zenity_test endif @@ -54,7 +54,10 @@ endif default: $(TARGETS) -.PHONY: clean +.PHONY: clean zenity_test + +zenity_test: $(BUILD_DIR)/zenity + ./zenity 666 cat /home/fedir Downloads $(BUILD_DIR)/zenity: $(BUILD_DIR)/zenity.o $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/zenity From 07cb76f425a8cb1a20e853e8aceb112226c70a85 Mon Sep 17 00:00:00 2001 From: fedir Date: Thu, 1 May 2025 16:15:00 +0200 Subject: [PATCH 03/33] Updated the dialogue --- src/gui/zenity-clone.c | 144 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 128 insertions(+), 16 deletions(-) diff --git a/src/gui/zenity-clone.c b/src/gui/zenity-clone.c index 071b6c1..4efa120 100644 --- a/src/gui/zenity-clone.c +++ b/src/gui/zenity-clone.c @@ -1,41 +1,153 @@ #include "gio/gio.h" +#include "glib-object.h" +#include "glib.h" #include #include +#include +#include +#include + +#define YES 0 +#define NO 1 +#define PERM 2 + +int exit_code = 0; +gboolean is_permanent = false; +GtkEntryBuffer *entry_buffer = NULL; +GtkWidget *checkbox = NULL; + +static void negative_response(GtkWindow *window) { + fprintf(stdout, "%s", gtk_entry_buffer_get_text(entry_buffer)); + exit_code = (gtk_check_button_get_active(GTK_CHECK_BUTTON(checkbox))) + ? YES | PERM + : YES; + gtk_window_close(window); +} + +static void positive_response(GtkWindow *window) { + fprintf(stdout, "%s", gtk_entry_buffer_get_text(entry_buffer)); + exit_code = (gtk_check_button_get_active(GTK_CHECK_BUTTON(checkbox))) + ? NO | PERM + : NO; + gtk_window_close(window); +} + +static void on_check_button_toggled(GtkToggleButton *button, + gpointer user_data) { + gboolean active = gtk_toggle_button_get_active(button); +} static void on_activate(GtkApplication *app, gpointer user_data) { // Create the main window - AdwMessageDialog *dialog = ADW_MESSAGE_DIALOG( - adw_message_dialog_new(NULL, "Allow access?", "allow_access?")); + AdwWindow *window = ADW_WINDOW(adw_window_new()); + gtk_window_set_application(GTK_WINDOW(window), app); + gtk_window_set_title(GTK_WINDOW(window), "icfs"); + // gtk_window_set_default_size(GTK_WINDOW(window), 300, 150); - // Set the dialog as transient for the (non-existent) parent window - gtk_window_set_application(GTK_WINDOW(dialog), app); + AdwStatusPage *content = ADW_STATUS_PAGE(adw_status_page_new()); + adw_status_page_set_title(content, "Allow access?"); - // Add response buttons - adw_message_dialog_add_response(dialog, "cancel", "Cancel"); - adw_message_dialog_add_response(dialog, "ok", "OK"); + char *description = NULL; + asprintf( + &description, + "Allow process %s with PID %s to access %s", + g_object_get_data(G_OBJECT(app), "accessing_name"), + g_object_get_data(G_OBJECT(app), "accessing_pid"), + g_object_get_data(G_OBJECT(app), "access_dir")); + adw_status_page_set_description(content, description); + free(description); - // Set default response - adw_message_dialog_set_default_response(dialog, "ok"); + entry_buffer = gtk_entry_buffer_new( + g_object_get_data(G_OBJECT(app), "access_dir"), + strlen(g_object_get_data(G_OBJECT(app), "access_dir"))); - // Set close response (when clicking X) - adw_message_dialog_set_close_response(dialog, "cancel"); + GtkWidget *entry = gtk_entry_new(); + gtk_entry_set_buffer(GTK_ENTRY(entry), entry_buffer); + gtk_entry_set_placeholder_text(GTK_ENTRY(entry), "Enter filename"); + gtk_widget_set_hexpand(entry, TRUE); - // Connect response handler - // g_signal_connect(dialog, "response", G_CALLBACK(gtk_window_close), dialog); + // Create a prefix label and box + GtkWidget *entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + GtkWidget *prefix_label = + gtk_label_new(g_object_get_data(G_OBJECT(app), "root_folder")); + gtk_box_append(GTK_BOX(entry_box), prefix_label); + gtk_box_append(GTK_BOX(entry_box), entry); + + checkbox = gtk_check_button_new_with_label("Permanent"); + gtk_check_button_set_active(GTK_CHECK_BUTTON(checkbox), false); + // gtk_widget_set_halign(checkbox, GTK_ALIGN_CENTER); + + GtkWidget *yes_button = gtk_button_new_with_label("Yes"); + gtk_widget_set_hexpand(yes_button, TRUE); + g_signal_connect_swapped(yes_button, "clicked", G_CALLBACK(positive_response), + window); + + GtkWidget *no_button = gtk_button_new_with_label("No"); + gtk_widget_set_hexpand(no_button, TRUE); + g_signal_connect_swapped(no_button, "clicked", G_CALLBACK(negative_response), + window); + + GtkWidget *button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_append(GTK_BOX(button_box), yes_button); + gtk_box_append(GTK_BOX(button_box), no_button); + gtk_widget_set_halign(button_box, GTK_ALIGN_FILL); + + // Combine everything in a box + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); + gtk_box_append(GTK_BOX(box), GTK_WIDGET(content)); + gtk_box_append(GTK_BOX(box), entry_box); + gtk_box_append(GTK_BOX(box), checkbox); + gtk_box_append(GTK_BOX(box), button_box); + gtk_widget_set_margin_top(GTK_WIDGET(box), 12); + gtk_widget_set_margin_bottom(GTK_WIDGET(box), 12); + gtk_widget_set_margin_start(GTK_WIDGET(box), 12); + gtk_widget_set_margin_end(GTK_WIDGET(box), 12); + + // g_signal_connect(window, "response", G_CALLBACK(gtk_window_close), window); // Show the dialog - gtk_window_present(GTK_WINDOW(dialog)); + adw_window_set_content(window, box); + gtk_window_present(GTK_WINDOW(window)); +} + +static int on_command_line(GApplication *app, GApplicationCommandLine *cmdline, + gpointer user_data) { + gchar **argv; + gint argc; + + argv = g_application_command_line_get_arguments(cmdline, &argc); + + // Handle your arguments here + if (argc >= 4) { + fprintf(stderr, "%s\n", argv[1]); + g_object_set_data_full(G_OBJECT(app), "accessing_pid", g_strdup(argv[1]), + g_free); + g_object_set_data_full(G_OBJECT(app), "accessing_name", g_strdup(argv[2]), + g_free); + g_object_set_data_full(G_OBJECT(app), "root_folder", g_strdup(argv[3]), + g_free); + g_object_set_data_full(G_OBJECT(app), "access_dir", g_strdup(argv[4]), + g_free); + } + + g_strfreev(argv); + + // Activate the application + g_application_activate(app); + return 0; } int main(int argc, char **argv) { // Create a new application AdwApplication *app = adw_application_new("com.example.zenityclone", - G_APPLICATION_DEFAULT_FLAGS); + G_APPLICATION_HANDLES_COMMAND_LINE); + + g_signal_connect(app, "command-line", G_CALLBACK(on_command_line), NULL); g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL); // Run the application int status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); - return status; + return (status == 0) ? exit_code : status; } From 31b70b60693c5c178343ef1f1013ab7a79fb72ae Mon Sep 17 00:00:00 2001 From: fedir Date: Thu, 1 May 2025 16:16:09 +0200 Subject: [PATCH 04/33] Added mountpoint functions to sourcefs --- src/main.c | 1 + src/real_filename.h | 7 +++++++ src/sourcefs.c | 38 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/real_filename.h diff --git a/src/main.c b/src/main.c index a4b2104..a871017 100644 --- a/src/main.c +++ b/src/main.c @@ -56,6 +56,7 @@ int main(int argc, char *argv[]) { ret = fuse_main(argc - 1, argv, get_fuse_operations(), NULL); free((void *)mountpoint); + source_destroy(); destroy_ui_socket(); return ret; } diff --git a/src/real_filename.h b/src/real_filename.h new file mode 100644 index 0000000..ee27b0a --- /dev/null +++ b/src/real_filename.h @@ -0,0 +1,7 @@ +#ifndef REAL_FILENAME_H +#define REAL_FILENAME_H + +const char *real_filename(const char *filename); +const char *get_mountpoint(void); + +#endif // !REAL_FILENAME_H diff --git a/src/sourcefs.c b/src/sourcefs.c index 5a209a2..66def02 100644 --- a/src/sourcefs.c +++ b/src/sourcefs.c @@ -10,14 +10,14 @@ #include "sourcefs.h" #include -#include #include #include +#include #include -#include #include static struct source_files_handle { + const char *mountpoint; int root_fd; } handle; @@ -30,6 +30,14 @@ const char *source_filename_translate(const char *filename) { } int source_init(const char *root_path) { + handle.mountpoint = malloc(strlen(root_path) + 1); + if (handle.mountpoint == NULL) { + perror("Malloc failed"); + return -1; + } + + strcpy(handle.mountpoint, root_path); + int root_fd = open(root_path, O_PATH); if (root_fd == -1) { @@ -43,6 +51,32 @@ int source_init(const char *root_path) { return 0; } +void source_destroy(void) { free(handle.mountpoint); } + +const char *get_mountpoint(void) { return handle.mountpoint; } + +const char *real_filename(const char *filename) { + const char *mountpoint = get_mountpoint(); + // Calculate required length + size_t len1 = strlen(mountpoint); + size_t len2 = strlen(filename); + size_t total_len = len1 + len2; + + // Allocate memory (+1 for null terminator) + char *result = malloc(total_len + 1); + if (result == NULL) { + fprintf(stderr, "Memory allocation failed"); + perror(""); + return NULL; + } + + // Copy strings + strcpy(result, mountpoint); + strcat(result, filename); + + return result; +} + int source_mkdir(const char *filename, mode_t mode) { const char *relative_filename = source_filename_translate(filename); return mkdirat(handle.root_fd, relative_filename, mode); From 48342b0d5f111f1cc1d6ebfeb119621aec4380d3 Mon Sep 17 00:00:00 2001 From: fedir Date: Thu, 1 May 2025 16:16:24 +0200 Subject: [PATCH 05/33] Updated sourcefs header --- src/sourcefs.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sourcefs.h b/src/sourcefs.h index b712106..4611b15 100644 --- a/src/sourcefs.h +++ b/src/sourcefs.h @@ -19,6 +19,7 @@ * @return 0 on success, -1 on failure. */ int source_init(const char *root_path); +void source_destroy(void); /* All of the functions below are designed to behave exactly as their non-source * counterparts. */ From ed441b3c5f8c726ae9f8cbb21650f1ab96fdcd17 Mon Sep 17 00:00:00 2001 From: fedir Date: Thu, 1 May 2025 16:17:11 +0200 Subject: [PATCH 06/33] Absolved fuse operations of responsibility for filename translation --- src/fuse_operations.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/fuse_operations.c b/src/fuse_operations.c index 6d29052..8c6077e 100644 --- a/src/fuse_operations.c +++ b/src/fuse_operations.c @@ -11,6 +11,8 @@ See the file LICENSE. */ +#include "real_filename.h" +#include #include #define FUSE_USE_VERSION 31 @@ -94,9 +96,6 @@ const char *get_process_name_by_pid(const int pid) { */ } -// TODO: move this somewhere else -const char *real_filename(const char *filename) { return filename; } - static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { (void)conn; cfg->use_ino = 1; @@ -120,6 +119,7 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { cfg->attr_timeout = 0; cfg->negative_timeout = 0; fprintf(stderr, "%d\n", getpid()); + assert(get_mountpoint() != NULL); return NULL; } @@ -157,7 +157,7 @@ static int xmp_access(const char *path, int mask) { // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); - if (!interactive_access(real_filename(path), proc_info, 0)) { + if (!interactive_access(path, proc_info, 0)) { free((void *)proc_info.name); return -EACCES; } @@ -323,7 +323,7 @@ static int xmp_unlink(const char *path) { // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); - if (!interactive_access(real_filename(path), pi, 0)) { + if (!interactive_access(path, pi, 0)) { free(pi.name); return -EACCES; } @@ -371,15 +371,14 @@ static int xmp_rename(const char *from, const char *to, unsigned int flags) { // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); - if (!interactive_access(real_filename(from), pi, 0)) { + if (!interactive_access(from, pi, 0)) { free(pi.name); return -EACCES; } // the "to" file may exist and the process needs to get persmission to modify // it - if (source_access(to, F_OK) == 0 && - !interactive_access(real_filename(to), pi, 0)) { + if (source_access(to, F_OK) == 0 && !interactive_access(to, pi, 0)) { free(pi.name); return -EACCES; } @@ -402,7 +401,7 @@ static int xmp_link(const char *from, const char *to) { pi.name = get_process_name_by_pid(pi.PID); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); - if (!interactive_access(real_filename(from), pi, 0)) { + if (!interactive_access(from, pi, 0)) { free(pi.name); return -EACCES; } @@ -427,7 +426,7 @@ static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) { pi.name = get_process_name_by_pid(pi.PID); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); - if (!interactive_access(real_filename(path), pi, 0)) { + if (!interactive_access(path, pi, 0)) { free(pi.name); return -EACCES; } @@ -458,7 +457,7 @@ static int xmp_chown(const char *path, uid_t uid, gid_t gid, pi.name = get_process_name_by_pid(pi.PID); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); - if (!interactive_access(real_filename(path), pi, 0)) { + if (!interactive_access(path, pi, 0)) { free(pi.name); return -EACCES; } @@ -518,7 +517,7 @@ static int xmp_create(const char *path, mode_t mode, // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); - if (!interactive_access(real_filename(path), pi, GRANT_PERM)) { + if (!interactive_access(path, pi, GRANT_PERM)) { free(pi.name); return -EACCES; } @@ -542,7 +541,7 @@ static int xmp_open(const char *path, struct fuse_file_info *fi) { pi.name = get_process_name_by_pid(pi.PID); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); - if (!interactive_access(real_filename(path), pi, 0)) { + if (!interactive_access(path, pi, 0)) { free(pi.name); return -EACCES; } From a1445c542341bea2571571215bea6427bb2120f1 Mon Sep 17 00:00:00 2001 From: fedir Date: Thu, 1 May 2025 16:17:27 +0200 Subject: [PATCH 07/33] Updated Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9436664..81d59b5 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ $(BUILD_DIR)/main.o: $(SOURCES_DIR)/main.c $(BUILD_DIR)/fuse_operations.o: $(SOURCES_DIR)/fuse_operations.c $(SOURCES_DIR)/fuse_operations.h $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@ -$(BUILD_DIR)/sourcefs.o: $(SOURCES_DIR)/sourcefs.c $(SOURCES_DIR)/sourcefs.h +$(BUILD_DIR)/sourcefs.o: $(SOURCES_DIR)/sourcefs.c $(SOURCES_DIR)/sourcefs.h $(SOURCES_DIR)/real_filename.h $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@ $(BUILD_DIR)/ui-socket.o: $(SOURCES_DIR)/ui-socket.c $(SOURCES_DIR)/ui-socket.h From 5452c3d1d7ecc810eaac7a66184e34324dfa84f8 Mon Sep 17 00:00:00 2001 From: fedir Date: Thu, 1 May 2025 16:17:50 +0200 Subject: [PATCH 08/33] Added filename translation to the ui-socker --- src/ui-socket.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/ui-socket.c b/src/ui-socket.c index 2c921cc..ad2549e 100644 --- a/src/ui-socket.c +++ b/src/ui-socket.c @@ -12,6 +12,7 @@ #include #define _GNU_SOURCE #include "perm_permissions_table.h" +#include "real_filename.h" #include "temp_permissions_table.h" #include "ui-socket.h" #include @@ -70,7 +71,7 @@ access_t ask_access(const char *filename, struct process_info proc_info) { "zenity --question --extra-button \"Allow this time\" --title " "\"Allow Access?\" --text \"Allow process " "%s with PID %d to access %s\"", - proc_info.name, proc_info.PID, filename); + proc_info.name, proc_info.PID, filename, get_mountpoint()); if (ret < 0) { // If asprintf fails, the contents of command are undefined (see man @@ -126,20 +127,25 @@ access_t ask_access(const char *filename, struct process_info proc_info) { */ int interactive_access(const char *filename, struct process_info proc_info, int opts) { + char *real_path = real_filename(filename); - access_t access = check_temp_access(filename, proc_info); + access_t access = check_temp_access(real_path, proc_info); if (access == ALLOW) { + free(real_path); return 1; } if (access == DENY) { + free(real_path); return 0; } - access = check_perm_access(filename, proc_info); + access = check_perm_access(real_path, proc_info); if (access == ALLOW) { + free(real_path); return 1; } if (access == DENY) { + free(real_path); return 0; } @@ -147,30 +153,36 @@ int interactive_access(const char *filename, struct process_info proc_info, // permissions are granted if (opts & GRANT_PERM) { - give_perm_access(filename, proc_info); + give_perm_access(real_path, proc_info); + free(real_path); return 1; } if (opts & GRANT_TEMP) { - set_temp_access(filename, proc_info, SET_ALLOW); + set_temp_access(real_path, proc_info, SET_ALLOW); + free(real_path); return 1; } - access_t user_response = ask_access(filename, proc_info); + access_t user_response = ask_access(real_path, proc_info); if (user_response == ALLOW) { - give_perm_access(filename, proc_info); + give_perm_access(real_path, proc_info); + free(real_path); return 1; } if (user_response == ALLOW_TEMP) { - set_temp_access(filename, proc_info, SET_ALLOW); + set_temp_access(real_path, proc_info, SET_ALLOW); + free(real_path); return 1; } if (user_response == DENY) { - set_temp_access(filename, proc_info, SET_DENY); + set_temp_access(real_path, proc_info, SET_DENY); + free(real_path); return 0; } + free(real_path); // deny on unknown options. return 0; } From 683da159535462c5942a46923f9a52052a2c4693 Mon Sep 17 00:00:00 2001 From: fedir Date: Thu, 1 May 2025 20:52:32 +0200 Subject: [PATCH 09/33] Updated the cc.h version --- src/cc.h | 4769 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 3971 insertions(+), 798 deletions(-) diff --git a/src/cc.h b/src/cc.h index d9042df..432f905 100644 --- a/src/cc.h +++ b/src/cc.h @@ -1,23 +1,22 @@ -/*----------------------------------------- CC: CONVENIENT CONTAINERS v1.3.2 ------------------------------------------- +/*----------------------------------------- CC: CONVENIENT CONTAINERS v1.4.2 ------------------------------------------- -This library provides usability-oriented generic containers (vectors, linked lists, unordered maps, unordered sets, -ordered maps, and ordered sets). +This library provides ergonomic, high-performance generic containers (vectors, linked lists, unordered maps, unordered +sets, ordered maps, ordered sets, and null-terminated strings). Features: -* Fully generic API (no need to specify element or key type except when first declaring a container). -* No need to pre-declare container types per element or key/element pair. -* Type safety. +* Fully generic API. +* Type safety without boilerplate container-type definitions. * User-defined destructor, comparison, and hash functions associated with element and key types. -* Handles memory allocation failure. +* No assumption of successful memory allocation. * Single header. * Compiles in C and C++. -It requires C23, or C11 and compiler support for __typeof__, or C++11. +It requires C23, or C11 and support for typeof (available in every major compiler), or C++11. It has been tested with GCC, Clang, MinGW, and MSVC. -Usage example: +Simple usage example (for more advanced examples, see https://github.com/JacksonAllan/CC): +---------------------------------------------------------+----------------------------------------------------------+ | Vector: | List: | @@ -201,6 +200,85 @@ Usage example: | cleanup( &our_omap ); | | | } | | +---------------------------------------------------------+----------------------------------------------------------+ + | String: | + |---------------------------------------------------------| + | #include | + | #include "cc.h" | + | | + | int main( void ) | + | { | + | str( char ) our_str; | + | init( &our_str ); | + | | + | // Appending formatted data. | + | const char model[] = "Hornet CB900F"; | + | const char manufacturer[] = "Honda"; | + | unsigned int year_introduced = 2002; | + | unsigned int year_discontinued = 2007; | + | double horsepower = 103.0; | + | double torque = 84.9; | + | if( | + | !push_fmt( | + | &our_str, "The ", model, | + | " is a motorcycle that was manufactured by ", | + | manufacturer, " from ", year_introduced, " to ", | + | year_discontinued, ".\nIt makes ", horsepower, | + | "hp and ", torque, "Nm of torque.\n" | + | ) | + | ) | + | { | + | // Out of memory, so abort. | + | cleanup( &our_str ); | + | return 1; | + | } | + | | + | // Inserting formatted data at an index. | + | const char alternative_model_name[] = "919"; | + | if( | + | !insert_fmt( | + | &our_str, 17, ", also known as the ", | + | alternative_model_name, "," | + | ) | + | ) | + | { | + | // Out of memory, so abort. | + | cleanup( &our_str ); | + | return 1; | + | } | + | | + | printf( first( &our_str ) ); | + | // Printed: | + | // The Hornet CB900F, also known as the 919, is a | + | // motorcycle that was manufactured by Honda from | + | // 2002 to 2007. | + | // It makes 103.00hp and 84.90Nm of torque. | + | | + | // Erasing elements. | + | erase_n( &our_str, 108, 41 ); | + | | + | printf( first( &our_str ) ); | + | // Printed: | + | // The Hornet CB900F, also known as the 919, is a | + | // motorcycle that was manufactured by Honda from | + | // 2002 to 2007. | + | | + | // Iteration #1. | + | for_each( &our_str, el ) | + | printf( "%c", *el ); | + | // Printed: Same as above. | + | | + | // Iteration #2. | + | for( | + | char *el = first( &our_str ); | + | el != end( &our_str ); | + | el = next( &our_str, el ) | + | ) | + | printf( "%c", *el ); | + | // Printed: Same as above. | + | | + | cleanup( &our_str ); | + | } | + +---------------------------------------------------------+ Including the library: @@ -229,7 +307,7 @@ API: * API macros may evaluate their first argument - the pointer to the container - multiple times, so never use expressions with side effects (e.g. &our_containers[ ++i ] ) for that argument. In GCC and Clang, attempting to do so will cause a compiler warning. All other arguments are only evaluated once. - * If CC_NO_SHORT_NAMES was declared, all API macros are prefixed with "cc_". + * If CC_NO_SHORT_NAMES was declared, all API macros and functions are prefixed with "cc_". * Duplicating a container handle via assignment and then operating on the duplicate will invalidate the original. Hence, only create a duplicate via assignment (including through function parameters and return values) if you have finished with the original. @@ -247,6 +325,15 @@ API: Initializes cntr for use. This call cannot fail (it does not allocate memory). + initialized( *cntr ) + + Returns an initialized instance of the same type as cntr. + This call cannot fail (it does not allocate memory). + The call is a constant expression and can therefore be used to initialize global containers at the site of their + declaration, e.g.: + + vec( int ) our_vec = initialized( &our_vec ); + bool init_clone( *cntr, *src ) Initializes cntr as a shallow copy of src. @@ -258,12 +345,13 @@ API: void clear( *cntr ) - Erases all elements, calling the element and key types' destructors if they exist. + Erases all elements, calling the element and key types' destructors if they exist (unless the container is a + string). void cleanup( *cntr ) - Erases all elements (calling the element and key types' destructors if they exist), frees any other memory - associated with the container, and initializes the container for reuse. + Erases all elements (calling the element and key types' destructors if they exist, unless the container is a + string), frees any other memory associated with the container, and initializes the container for reuse. el_ty *first( *cntr ) @@ -300,7 +388,7 @@ API: bool reserve( vec( el_ty ) *cntr, size_t n ) - Ensures that the the capacity is large enough to accommodate n elements. + Ensures that the capacity is large enough to accommodate n elements. Returns true, or false if unsuccessful due to memory allocation failure. bool resize( vec( el_ty ) *cntr, size_t n ) @@ -342,8 +430,8 @@ API: el_ty *erase( vec( el_ty ) *cntr, size_t i ) Erases the element at index i, calling the element type's destructor if it exists. - Returns a pointer-iterator to the element after the erased element, or an end pointer-iterator if there - is no subsequent element. + Returns a pointer-iterator to the element after the erased element, or an end pointer-iterator if there is no + subsequent element. el_ty *erase_n( vec( el_ty ) *cntr, size_t i, size_t n ) @@ -355,7 +443,7 @@ API: el_ty *last( vec( el_ty ) *cntr ) Returns a pointer-iterator to the last element. - This call is synonymous with get( cntr, size( cntr ) - 1 ) and assumes that at the vector is not empty. + This call is synonymous with get( cntr, size( cntr ) - 1 ) and assumes that the vector is not empty. Notes: * Vector pointer-iterators (including end) are invalidated by any API calls that cause memory reallocation. @@ -637,7 +725,7 @@ API: * Ordered map pointer-iterators (including r_end and end) are not invalidated by any API calls besides init and cleanup, unless they point to erased elements. - Ordered set (an ordered associative container for elements without a separate key, implemented as a red-black tree: + Ordered set (an ordered associative container for elements without a separate key, implemented as a red-black tree): oset( el_ty ) cntr @@ -712,12 +800,159 @@ API: * Ordered set pointer-iterators (including r_end and end) may be invalidated by any API calls that cause memory reallocation. + String (a dynamic, null-terminated array representing a sequence of characters): + + str( el_ty ) cntr + + Declares an uninitialized string named cntr. + el_ty must be char, unsigned char, signed char, char8_t, char16_t, char32_t, or an alias for one of these types. + + size_t cap( str( el_ty ) *cntr ) + + Returns the current capacity, i.e. the number of elements that the string can accommodate (not including the null + terminator) without reallocating its internal buffer. + + bool reserve( str( el_ty ) *cntr, size_t n ) + + Ensures that the the capacity is large enough to accommodate n elements. + Returns true, or false if unsuccessful due to memory allocation failure. + + bool resize( str( el_ty ) *cntr, size_t n, el_ty fill_character ) + Sets the number of elements to n. + If n is above the current size, the new elements are initialized to fill_character. + If n is below the current size, no destructor is called for any erased element, even if a destructor for the + element type has been defined. + Returns true, or false if unsuccessful due to memory allocation failure. + + bool shrink( str( el_ty ) *cntr ) + + Shrinks the capacity to the current size. + Returns true, or false if unsuccessful due to memory allocation failure. + + el_ty *get( str( el_ty ) *cntr, size_t i ) + + Returns a pointer-iterator to the element at index i. + + el_ty *push( str( el_ty ) *cntr, el_ty el ) + + Inserts el at the end of the string. + Returns a pointer-iterator to the new element, or NULL in the case of memory allocation failure. + + el_ty *push_fmt( str( el_ty ) *cntr, ... ) + + Inserts up to 32 formatted values, provided as variadic arguments, at the end of the string. + Returns a pointer-iterator to the first new element, or NULL in the case of memory allocation failure. + Each variadic argument must be one of the following: + + * A null-terminated array of elements of the same type as el_ty (i.e. a C string). + * A CC string with the same element type. + * A fundamental integer type (bool, char, unsigned char, signed char, unsigned short, short, unsigned int, int, + unsigned long, long, unsigned long long, or long long) or alias for such a type. + * A fundamental floating-point type (float or double). + * A void pointer (to be formatted as a memory address). + * The return value of one of the following functions: + + * integer_dec( int min_digits ) + + Causes subsequent integer arguments to be formatted as decimal integers. + min_digits specifies the minimum number of digits, and if the formatted integer is shorter than this number, + it is padded with leading zeros. + + * integer_hex( int min_digits ) + + Causes subsequent integer arguments to be formatted as unsigned hexadecimal integers. + min_digits specifies the minimum number of digits, and if the formatted integer is shorter than this number, + it is padded with leading zeros. + + * integer_oct( int min_digits ) + + Causes subsequent integer arguments to be formatted as unsigned octal integers. + min_digits specifies the minimum number of digits, and if the formatted integer is shorter than this number, + it is padded with leading zeros. + + * float_dec( int precision ) + + Causes subsequent floating-point arguments to be formatted as decimal floating-point numbers. + precision specifies the number of decimal places to include. + + * float_hex( int precision ) + + Causes subsequent floating-point arguments to be formatted as hexadecimal floating-point numbers. + precision specifies the number of decimal places to include. + + * float_sci( int precision ) + + Causes subsequent floating-point arguments to be formatted using scientific notation. + precision specifies the number of decimal places to include. + + * float_shortest( int significant_digits ) + + Causes subsequent floating-point arguments to be formatted as decimal floating-point numbers or using + scientific notation, depending on which representation is shorter. + significant_digits specifies the maximum number of significant digits to include. + + Arguments are type-promoted as follows: + * bool, unsigned char, unsigned short, unsigned int, unsigned long -> unsigned long long. + * signed char, short, int, long, -> long long. + * char -> long long or unsigned long long, depending on whether char is signed. + * float -> double. + + By default, integer arguments are formatted as decimal integers with a minimum of one digit, and floating-point + arguments are formatted as decimal floating-point numbers with two decimal places. + + For formatting, C and CC strings of char16_t and char32_t elements are assumed to be encoded as UTF-16 and UTF-32, + respectively. + + el_ty *push_n( str( el_ty ) *cntr, el_ty *els, size_t n ) + + Inserts n elements from array els at the end of the string. + Returns a pointer-iterator to the first new element, or NULL in the case of memory allocation failure. + + el_ty *insert( str( el_ty ) *cntr, size_t i, el_ty el ) + + Inserts el at index i. + Returns a pointer-iterator to the new element, or NULL in the case of memory allocation failure. + + el_ty *insert_fmt( str( el_ty ) *cntr, size_t i, ... ) + + Inserts up to 32 formatted values, provided as variadic arguments, at index i. + Each variadic argument must be one of the possibilities listed in the above documentation for push_fmt, and the + same type-promotions and encoding assumptions apply. + Returns a pointer-iterator to the first new element, or NULL in the case of memory allocation failure. + + el_ty *insert_n( str( el_ty ) *cntr, size_t i, el_ty *els, size_t n ) + + Inserts n elements from array els at index i. + Returns a pointer-iterator to the first new element, or NULL in the case of memory allocation failure. + + el_ty *erase( str( el_ty ) *cntr, size_t i ) + + Erases the element at index i. + No destructor is called for the erased element, even if a destructor for the element type has been defined. + Returns a pointer-iterator to the element after the erased element, or an end pointer-iterator if there is no + subsequent element. + + el_ty *erase_n( str( el_ty ) *cntr, size_t i, size_t n ) + + Erases n elements beginning at index i. + No destructor is called for erased elements, even if a destructor for the element type has been defined. + Returns a pointer-iterator to the element after the erased elements, or an end pointer-iterator if there is no + subsequent element. + + el_ty *last( str( el_ty ) *cntr ) + + Returns a pointer-iterator to the last element. + This call is synonymous with get( cntr, size( cntr ) - 1 ) and assumes that the string is not empty. + + Notes: + * String pointer-iterators (including end) are invalidated by any API calls that cause memory reallocation. + Destructor, comparison, and hash functions and custom max load factors: This part of the API allows the user to define custom destructor, comparison, and hash functions and max load factors for a type. - Once these functions are defined, any container using that type for its elements or keys will call them - automatically. + Once these functions are defined, any container (except strings) using that type for its elements or keys will call + them automatically. Once the max load factor is defined, any map using the type for its keys and any set using the type for its elements will use the defined load factor to determine when rehashing is necessary. @@ -765,12 +1000,48 @@ API: * Including cc.h in these cases does not include the full header, so you still need to include it separately at the top of your files. * In-built comparison and hash functions are already defined for the following types: char, unsigned char, signed - char, unsigned short, short, unsigned int, int, unsigned long, long, unsigned long long, long long, size_t, and - char * (a NULL-terminated string). Defining a comparison or hash function for one of these types will overwrite - the in-built function. + char, unsigned short, short, unsigned int, int, unsigned long, long, unsigned long long, long long, size_t, + null-terminated C strings (char * and const char *), and CC strings. Defining a comparison or hash function for + one of these types will overwrite the in-built function. + * In-built, memory-freeing destructor functions are already defined for CC strings. Defining a destructor for a CC + string type will overwrite the in-built destructor. + + Heterogeneous string insertion and lookup: + + When CC strings are used as the key and/or element type of another container, most API macros that operate on the + container may alternatively take, as their key and/or element argument, a regular C string of the corresponding + character type. In this case, the library automatically converts the C string into a CC string. + The API macros that support heterogeneous insertion are: + * push + * insert + * get_or_insert + The API macros that support heterogeneous lookup are: + * get + * erase + In the lookup case, CC performs no memory allocations. + + Trivial example: + + map( str( char ), str( char ) ) our_map = initialized( &our_map ); + if( insert( &our_map, "France", "Paris" ) ) // Heterogeneous insertion. + { + str( char ) *el = get( &our_map, "France" ); // Heterogeneous lookup. + printf( first( el ) ); + // Printed: Paris + } Version history: + --/04/2025 1.4.2: Fixed unreachable-code-generic-assoc warning generated by cc_splice and cc_init_clone in Clang. + 21/04/2025 1.4.1: Removed dependency on uchar.h because the header is missing on macOS. + Fixed the internal cc_unmove function to be compatible with C++23. + Fixed incorrect handling of char8_t * arguments in cc_push_fmt and cc_insert_fmt in C++. + Removed superfluous handling of const cc_str arguments in cc_push_fmt and cc_insert_fmt in C. + 10/04/2025 1.4.0: Added CC strings. + Added cc_initialized for in-situ initialization of global containers. + Added support for const char * map and omap keys and set and oset elements. + Added support for const source arguments in cc_splice and cc_init_clone. + Added support for -Wextra and -Wconversion compiler flags. 11/02/2025 1.3.2: Fixed a critical bug causing maps to call the wrong destructors during cleanup. 23/08/2024 1.3.1: Fixed missing static inline qualifier on an internal omap function. 29/07/2024 1.3.0: Added ordered map and ordered set. @@ -806,7 +1077,7 @@ Version history: License (MIT): - Copyright (c) 2022-2025 Jackson L. Allan + Copyright (c) 2022-2024 Jackson L. Allan 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 @@ -839,6 +1110,7 @@ License (MIT): #include #include #include +#include #include #include #ifdef __cplusplus @@ -847,40 +1119,53 @@ License (MIT): #endif #ifndef CC_NO_SHORT_NAMES -#define vec( ... ) CC_MSVC_PP_FIX( cc_vec( __VA_ARGS__ ) ) -#define list( ... ) CC_MSVC_PP_FIX( cc_list( __VA_ARGS__ ) ) -#define map( ... ) CC_MSVC_PP_FIX( cc_map( __VA_ARGS__ ) ) -#define set( ... ) CC_MSVC_PP_FIX( cc_set( __VA_ARGS__ ) ) -#define omap( ... ) CC_MSVC_PP_FIX( cc_omap( __VA_ARGS__ ) ) -#define oset( ... ) CC_MSVC_PP_FIX( cc_oset( __VA_ARGS__ ) ) -#define init( ... ) CC_MSVC_PP_FIX( cc_init( __VA_ARGS__ ) ) -#define init_clone( ... ) CC_MSVC_PP_FIX( cc_init_clone( __VA_ARGS__ ) ) -#define size( ... ) CC_MSVC_PP_FIX( cc_size( __VA_ARGS__ ) ) -#define cap( ... ) CC_MSVC_PP_FIX( cc_cap( __VA_ARGS__ ) ) -#define reserve( ... ) CC_MSVC_PP_FIX( cc_reserve( __VA_ARGS__ ) ) -#define resize( ... ) CC_MSVC_PP_FIX( cc_resize( __VA_ARGS__ ) ) -#define shrink( ... ) CC_MSVC_PP_FIX( cc_shrink( __VA_ARGS__ ) ) -#define insert( ... ) CC_MSVC_PP_FIX( cc_insert( __VA_ARGS__ ) ) -#define insert_n( ... ) CC_MSVC_PP_FIX( cc_insert_n( __VA_ARGS__ ) ) -#define get_or_insert( ... ) CC_MSVC_PP_FIX( cc_get_or_insert( __VA_ARGS__ ) ) -#define push( ... ) CC_MSVC_PP_FIX( cc_push( __VA_ARGS__ ) ) -#define push_n( ... ) CC_MSVC_PP_FIX( cc_push_n( __VA_ARGS__ ) ) -#define splice( ... ) CC_MSVC_PP_FIX( cc_splice( __VA_ARGS__ ) ) -#define get( ... ) CC_MSVC_PP_FIX( cc_get( __VA_ARGS__ ) ) -#define key_for( ... ) CC_MSVC_PP_FIX( cc_key_for( __VA_ARGS__ ) ) -#define erase( ... ) CC_MSVC_PP_FIX( cc_erase( __VA_ARGS__ ) ) -#define erase_n( ... ) CC_MSVC_PP_FIX( cc_erase_n( __VA_ARGS__ ) ) -#define erase_itr( ... ) CC_MSVC_PP_FIX( cc_erase_itr( __VA_ARGS__ ) ) -#define clear( ... ) CC_MSVC_PP_FIX( cc_clear( __VA_ARGS__ ) ) -#define cleanup( ... ) CC_MSVC_PP_FIX( cc_cleanup( __VA_ARGS__ ) ) -#define first( ... ) CC_MSVC_PP_FIX( cc_first( __VA_ARGS__ ) ) -#define last( ... ) CC_MSVC_PP_FIX( cc_last( __VA_ARGS__ ) ) -#define r_end( ... ) CC_MSVC_PP_FIX( cc_r_end( __VA_ARGS__ ) ) -#define end( ... ) CC_MSVC_PP_FIX( cc_end( __VA_ARGS__ ) ) -#define next( ... ) CC_MSVC_PP_FIX( cc_next( __VA_ARGS__ ) ) -#define prev( ... ) CC_MSVC_PP_FIX( cc_prev( __VA_ARGS__ ) ) -#define for_each( ... ) CC_MSVC_PP_FIX( cc_for_each( __VA_ARGS__ ) ) -#define r_for_each( ... ) CC_MSVC_PP_FIX( cc_r_for_each( __VA_ARGS__ ) ) +#define vec( ... ) CC_MSVC_PP_FIX( cc_vec( __VA_ARGS__ ) ) +#define list( ... ) CC_MSVC_PP_FIX( cc_list( __VA_ARGS__ ) ) +#define map( ... ) CC_MSVC_PP_FIX( cc_map( __VA_ARGS__ ) ) +#define set( ... ) CC_MSVC_PP_FIX( cc_set( __VA_ARGS__ ) ) +#define omap( ... ) CC_MSVC_PP_FIX( cc_omap( __VA_ARGS__ ) ) +#define oset( ... ) CC_MSVC_PP_FIX( cc_oset( __VA_ARGS__ ) ) +#define str( ... ) CC_MSVC_PP_FIX( cc_str( __VA_ARGS__ ) ) +#define initialized( ... ) CC_MSVC_PP_FIX( cc_initialized( __VA_ARGS__ ) ) +#define init( ... ) CC_MSVC_PP_FIX( cc_init( __VA_ARGS__ ) ) +#define init_clone( ... ) CC_MSVC_PP_FIX( cc_init_clone( __VA_ARGS__ ) ) +#define size( ... ) CC_MSVC_PP_FIX( cc_size( __VA_ARGS__ ) ) +#define cap( ... ) CC_MSVC_PP_FIX( cc_cap( __VA_ARGS__ ) ) +#define reserve( ... ) CC_MSVC_PP_FIX( cc_reserve( __VA_ARGS__ ) ) +#define resize( ... ) CC_MSVC_PP_FIX( cc_resize( __VA_ARGS__ ) ) +#define shrink( ... ) CC_MSVC_PP_FIX( cc_shrink( __VA_ARGS__ ) ) +#define insert( ... ) CC_MSVC_PP_FIX( cc_insert( __VA_ARGS__ ) ) +#define insert_n( ... ) CC_MSVC_PP_FIX( cc_insert_n( __VA_ARGS__ ) ) +#define insert_fmt( ... ) CC_MSVC_PP_FIX( cc_insert_fmt( __VA_ARGS__ ) ) +#define get_or_insert( ... ) CC_MSVC_PP_FIX( cc_get_or_insert( __VA_ARGS__ ) ) +#define push( ... ) CC_MSVC_PP_FIX( cc_push( __VA_ARGS__ ) ) +#define push_n( ... ) CC_MSVC_PP_FIX( cc_push_n( __VA_ARGS__ ) ) +#define push_fmt( ... ) CC_MSVC_PP_FIX( cc_push_fmt( __VA_ARGS__ ) ) +#define splice( ... ) CC_MSVC_PP_FIX( cc_splice( __VA_ARGS__ ) ) +#define get( ... ) CC_MSVC_PP_FIX( cc_get( __VA_ARGS__ ) ) +#define key_for( ... ) CC_MSVC_PP_FIX( cc_key_for( __VA_ARGS__ ) ) +#define erase( ... ) CC_MSVC_PP_FIX( cc_erase( __VA_ARGS__ ) ) +#define erase_n( ... ) CC_MSVC_PP_FIX( cc_erase_n( __VA_ARGS__ ) ) +#define erase_itr( ... ) CC_MSVC_PP_FIX( cc_erase_itr( __VA_ARGS__ ) ) +#define clear( ... ) CC_MSVC_PP_FIX( cc_clear( __VA_ARGS__ ) ) +#define cleanup( ... ) CC_MSVC_PP_FIX( cc_cleanup( __VA_ARGS__ ) ) +#define first( ... ) CC_MSVC_PP_FIX( cc_first( __VA_ARGS__ ) ) +#define last( ... ) CC_MSVC_PP_FIX( cc_last( __VA_ARGS__ ) ) +#define r_end( ... ) CC_MSVC_PP_FIX( cc_r_end( __VA_ARGS__ ) ) +#define end( ... ) CC_MSVC_PP_FIX( cc_end( __VA_ARGS__ ) ) +#define next( ... ) CC_MSVC_PP_FIX( cc_next( __VA_ARGS__ ) ) +#define prev( ... ) CC_MSVC_PP_FIX( cc_prev( __VA_ARGS__ ) ) +#define for_each( ... ) CC_MSVC_PP_FIX( cc_for_each( __VA_ARGS__ ) ) +#define r_for_each( ... ) CC_MSVC_PP_FIX( cc_r_for_each( __VA_ARGS__ ) ) +#define integer_dec( ... ) CC_MSVC_PP_FIX( cc_integer_dec( __VA_ARGS__ ) ) +#define integer_hex( ... ) CC_MSVC_PP_FIX( cc_integer_hex( __VA_ARGS__ ) ) +#define integer_oct( ... ) CC_MSVC_PP_FIX( cc_integer_oct( __VA_ARGS__ ) ) +#define float_dec( ... ) CC_MSVC_PP_FIX( cc_float_dec( __VA_ARGS__ ) ) +#define float_hex( ... ) CC_MSVC_PP_FIX( cc_float_hex( __VA_ARGS__ ) ) +#define float_sci( ... ) CC_MSVC_PP_FIX( cc_float_sci( __VA_ARGS__ ) ) +#define float_shortest( ... ) CC_MSVC_PP_FIX( cc_float_shortest( __VA_ARGS__ ) ) + + #endif #ifndef CC_H @@ -927,7 +1212,7 @@ License (MIT): // This is used to pass pointers to elements and keys (which the user may have provided as rvalues) into container // functions. #ifdef __cplusplus -template ty& cc_unmove( ty&& var ) { return var; } +template ty& cc_unmove( ty&& var ) { return (ty&)var; } #define CC_MAKE_LVAL_COPY( ty, xp ) cc_unmove( (ty)( xp ) ) #else #define CC_MAKE_LVAL_COPY( ty, xp ) *( ty[ 1 ] ){ xp } @@ -962,11 +1247,11 @@ template ty_1 cc_maybe_unused( ty_2 xp ){ return ( #ifdef __cplusplus -template::type = true> \ -ty_1 cc_if_then_cast_ty_1_else_cast_ty_2( xp_ty xp ){ return (ty_1)xp; } \ +template::type = true> \ +ty_1 cc_if_then_cast_ty_1_else_cast_ty_2( xp_ty xp ){ return (ty_1)xp; } \ -template::type = true> \ -ty_2 cc_if_then_cast_ty_1_else_cast_ty_2( xp_ty xp ){ return (ty_2)xp; } \ +template::type = true> \ +ty_2 cc_if_then_cast_ty_1_else_cast_ty_2( xp_ty xp ){ return (ty_2)xp; } \ #define CC_IF_THEN_CAST_TY_1_ELSE_CAST_TY_2( cond, ty_1, ty_2, xp ) \ cc_if_then_cast_ty_1_else_cast_ty_2( xp ) \ @@ -1023,11 +1308,23 @@ CC_CAST_MAYBE_UNUSED( \ #define CC_MSVC_PP_FIX( xp ) xp // CC_SELECT_ON_NUM_ARGS macro for overloading API macros based on the number of arguments. + #define CC_CAT_2_( a, b ) a##b #define CC_CAT_2( a, b ) CC_CAT_2_( a, b ) -#define CC_N_ARGS_( _1, _2, _3, _4, _5, _6, n, ... ) n -#define CC_N_ARGS( ... ) CC_MSVC_PP_FIX( CC_N_ARGS_( __VA_ARGS__, _6, _5, _4, _3, _2, _1, x ) ) -#define CC_SELECT_ON_NUM_ARGS( func, ... ) CC_CAT_2( func, CC_N_ARGS( __VA_ARGS__ ) )( __VA_ARGS__ ) + +#define CC_N_ARGS_( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, \ + _26, _27, _28, _29, _30, _31, _32, _33, _34, n, ... \ +) n + +#define CC_N_ARGS( ... ) CC_MSVC_PP_FIX( \ + CC_N_ARGS_( \ + __VA_ARGS__, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \ + 9, 8, 7, 6, 5, 4, 3, 2, 1, x \ + ) \ +) \ + +#define CC_SELECT_ON_NUM_ARGS( func, ... ) CC_MSVC_PP_FIX( CC_CAT_2( func, CC_N_ARGS( __VA_ARGS__ ) )( __VA_ARGS__ ) ) // If the user has defined CC_REALLOC and CC_FREE, then CC_GET_REALLOC and CC_GET_FREE are replaced with those macros. // Otherwise, they are replaced by realloc and free from the standard library. @@ -1038,13 +1335,17 @@ CC_CAST_MAYBE_UNUSED( \ #define CC_FREE_COMMA , #define CC_FREE_FN CC_2ND_ARG( CC_CAT_2( CC_FREE, _COMMA ) free, CC_FREE, ) -// Macro used with CC_STATIC_ASSERT to provide type safety in cc_init_clone and cc_splice calls. +// Macro used with CC_STATIC_ASSERT to provide type safety in cc_init_clone and cc_splice API macros. #ifdef __cplusplus -#define CC_IS_SAME_TY( a, b ) std::is_same::value +#define CC_IS_SAME_TY( a, b ) std::is_same::value #else -#define CC_IS_SAME_TY( a, b ) _Generic( (a), CC_TYPEOF_XP( b ): true, default: false ) +#define CC_IS_SAME_TY( a, b ) _Generic( (b), CC_TYPEOF_XP( a ): true, default: false ) #endif +// Returns arg but generates a warning or error if it is not a pointer to the container's element type. +// This macro is used by cc_push_n and cc_insert_n to ensure that the source is an array matching the element type. +#define CC_CHECKED_EL_TY_PTR( cntr, arg ) ( true ? (arg) : (const CC_EL_TY( cntr ) *)NULL ) + // Macro for handling unused parameters in most container functions that plug directly into API macros. // These functions must provide a standardized signature across containers. // The compiler should optimize away unused parameters anyway, but we can nevertheless mark them as redundant. @@ -1082,11 +1383,11 @@ static void *cc_dummy_true_ptr = &cc_dummy_true; // Types for comparison, hash, destructor, realloc, and free functions. // These are only for internal use as user-provided comparison, hash, and destructor functions have different signatures // (see above documentation). -typedef int ( *cc_cmpr_fnptr_ty )( void *, void * ); -typedef size_t ( *cc_hash_fnptr_ty )( void * ); -typedef void ( *cc_dtor_fnptr_ty )( void * ); typedef void *( *cc_realloc_fnptr_ty )( void *, size_t ); typedef void ( *cc_free_fnptr_ty )( void * ); +typedef int ( *cc_cmpr_fnptr_ty )( void *, void * ); +typedef size_t ( *cc_hash_fnptr_ty )( void * ); +typedef void ( *cc_dtor_fnptr_ty )( void *, cc_free_fnptr_ty ); // Container ids to identify container type at compile-time. #define CC_VEC 1 @@ -1095,6 +1396,7 @@ typedef void ( *cc_free_fnptr_ty )( void * ); #define CC_SET 4 #define CC_OMAP 5 #define CC_OSET 6 +#define CC_STR 7 // Produces the underlying function pointer type for a given element/key type pair. #define CC_MAKE_BASE_FNPTR_TY( el_ty, key_ty ) CC_TYPEOF_TY( CC_TYPEOF_TY( el_ty ) (*)( CC_TYPEOF_TY( key_ty )* ) ) @@ -1161,6 +1463,17 @@ typedef void ( *cc_free_fnptr_ty )( void * ); ) ? 1 : -1 ) \ ) \ +#define cc_str( el_ty ) CC_MAKE_CNTR_TY( \ + el_ty, \ + size_t, /* String key type is size_t. */ \ + CC_STR * ( CC_IS_STR_EL_TY( el_ty ) ? 1 : -1 ) \ + ) \ + +// Creates a string type without checking that the element type is one of the compatible character types. +// This macro is used to reduce compilation time and avoid errors when string types are generated from incompatible +// element types in dead _Generic paths. +#define CC_STR_RAW( el_ty ) CC_TYPEOF_TY( el_ty (*(*)[ CC_STR ])( size_t * ) ) + // Retrieves a container's id (e.g. CC_VEC) from its handle. #define CC_CNTR_ID( cntr ) ( sizeof( *cntr ) / sizeof( **cntr ) ) @@ -1187,29 +1500,133 @@ key_ty cc_key_ty( el_ty (*)( key_ty * ) ) // coupled with a comparison function. #define CC_KEY_TY_SLOT( n, arg ) CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( arg ), cc_cmpr_##n##_ty ): ( cc_cmpr_##n##_ty ){ 0 }, -#define CC_KEY_TY( cntr ) \ -CC_TYPEOF_XP( \ - _Generic( (**cntr), \ - CC_FOR_EACH_CMPR( CC_KEY_TY_SLOT, cntr ) \ - default: _Generic( (**cntr), \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_char ): ( char ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned char ): ( unsigned char ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), signed char ): ( signed char ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned short ): ( unsigned short ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), short ): ( short ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned int ): ( unsigned int ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), int ): ( int ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long ): ( unsigned long ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long ): ( long ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long long ): ( unsigned long long ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long long ): ( long long ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_size_t ): ( size_t ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), char * ): ( char * ){ 0 }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), void * ): ( void * ){ 0 }, \ - default: (char){ 0 } /* Nothing. */ \ - ) \ - ) \ -) \ +#define CC_KEY_TY( cntr ) \ +CC_TYPEOF_XP( \ + _Generic( (**cntr), \ + CC_FOR_EACH_CMPR( CC_KEY_TY_SLOT, cntr ) \ + default: _Generic( (**cntr), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_char ): ( char ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned char ): ( unsigned char ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), signed char ): ( signed char ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned short ): ( unsigned short ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), short ): ( short ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned int ): ( unsigned int ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), int ): ( int ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long ): ( unsigned long ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long ): ( long ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long long ): ( unsigned long long ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long long ): ( long long ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_size_t ): ( size_t ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), char * ): ( char * ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), const char * ): ( const char * ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), void * ): ( void * ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ): ( CC_STR_RAW( char ) ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ): ( CC_STR_RAW( unsigned char ) ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ): ( CC_STR_RAW( signed char ) ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ): ( CC_STR_RAW( char16_t ) ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ): ( CC_STR_RAW( char32_t ) ){ 0 }, \ + default: (char){ 0 } /* Nothing. */ \ + ) \ + ) \ +) \ + +#endif + +// In MSVC under C, char is an alias for unsigned char or signed char, contrary to the C Standard, which requires all +// three to be distinct types. +// To accommodate this bug, we have to ensure that char doesn't clash with either of the other two types in _Generic +// statements. +// If char is an alias, cc_maybe_char will be a dummy type used in no other context. +// Otherwise, it will be an alias for char. + +// size_t needs to be handled in a similar way because it could be an alias for a fundamental integer type or a distinct +// builtin type. + +#ifndef __cplusplus + +typedef struct { char nothing; } cc_char_dummy; + +typedef CC_TYPEOF_XP( + _Generic( (char){ 0 }, + unsigned char: (cc_char_dummy){ 0 }, + signed char: (cc_char_dummy){ 0 }, + default: (char){ 0 } + ) +) cc_maybe_char; + +typedef struct { char nothing; } cc_size_t_dummy; + +typedef CC_TYPEOF_XP( + _Generic( (size_t){ 0 }, + unsigned short: (cc_size_t_dummy){ 0 }, + short: (cc_size_t_dummy){ 0 }, + unsigned int: (cc_size_t_dummy){ 0 }, + int: (cc_size_t_dummy){ 0 }, + unsigned long: (cc_size_t_dummy){ 0 }, + long: (cc_size_t_dummy){ 0 }, + unsigned long long: (cc_size_t_dummy){ 0 }, + long long: (cc_size_t_dummy){ 0 }, + default: (size_t){ 0 } + ) +) cc_maybe_size_t; + +#endif + +// Since macOS lacks uchar.h, we define char8_t, char16_t, and char32_t ourselves in accordance with the C Standard. +#ifndef __cplusplus +#if __STDC_VERSION__ >= 202311L +typedef unsigned char char8_t; +#endif +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +#endif + +// Includes the code between parentheses in the case of C++20 or later. +// This macro is used inside certain macros to optionally include char8_t functionality. +// In C++20, char8_t is a distinct type and must therefore be handled explicitly (unlike in C23, wherein char8_t is an +// alias for unsigned char and can therefore be handled implicitly). +#ifdef __cplusplus +#if __cplusplus >= 202101L +#define CC_IF_CPP20( ... ) __VA_ARGS__ +#else +#define CC_IF_CPP20( ... ) +#endif +#endif + +// CC_IS_STR_EL_TY macro for determining whether a type is one of the limited number that string supports. +// In C, char16_t and char32_t are aliases for uint_least16_t and uint_least32_t, respectively, and could therefore be +// the same type on exotic systems that lack a fundamental 16-bit integer type. +// However, because CC's map implementation requires uint16_t and uint32_t, we can be sure that char16_t and char32_t +// are distinct types on any platform with which CC is compatible, and we treat them thusly. +// This assumption is especially necessary because without it, the encoding of strings of the largest type would be +// ambiguous in some instances (e.g. string literals). + +#ifdef __cplusplus + +#define CC_IS_STR_EL_TY( ty ) \ +( \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + CC_IF_CPP20( \ + std::is_same::value ? true : \ + ) \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + false \ +) \ + +#else + +#define CC_IS_STR_EL_TY( ty ) \ +_Generic( (ty){ 0 }, \ + cc_maybe_char: true, \ + unsigned char: true, \ + signed char: true, \ + char16_t: true, \ + char32_t: true, \ + default: false \ +) \ #endif @@ -1347,19 +1764,19 @@ static inline void *cc_memcpy_and_return_ptr( void *dest, void *src, size_t size return ptr; } -// All macros that call functions that could cause memory reallocation do two essential things to circumvent limitations -// of ISO C (such as the inability to declare variables in expressions and _Thread_local linking issues). +// Macros that call functions that could cause memory reallocation must generally do two things to circumvent +// limitations of ISO C (such as the inability to declare variables in expressions and _Thread_local linking issues). // Firstly, they temporarily set the container handle to point to a temporary cc_insert_result object returned by the // call to the function (see CC_MAKE_LVAL_COPY above). -#define CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( cntr, fn_call ) \ - cntr = (CC_TYPEOF_XP( cntr ))&CC_MAKE_LVAL_COPY( cc_allocing_fn_result_ty, fn_call ) \ +#define CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( cntr, fn_call ) \ +cntr = (CC_TYPEOF_XP( cntr ))&CC_MAKE_LVAL_COPY( cc_allocing_fn_result_ty, fn_call ) \ // Secondly, they call cc_memcpy_and_return_ptr to restore the correct container handle (a new pointer in the case of // reallocation) and return a pointer, either to any new elements or to signify success/failure, to the user. // (In the latter case, that pointer is cast to bool in the API macro before being passed to the user.) // Without this call, we would be unable to access the new elements/success-or-failure pointer stored in the // cc_allocing_fn_result_ty after restoring the correct container handle. -// Note that outside the function, a temporary container handle is created from the new handle in the +// Note that outside the function, a temporary container handle is created from the new handle stored in the // cc_allocing_fn_result_ty so that the later (void *) is properly converted to the correct handle type. // This new, correctly typed handle is then memcpy-ed over the user-supplied handle inside the function. #define CC_FIX_HNDL_AND_RETURN_OTHER_PTR( cntr ) \ @@ -1370,6 +1787,99 @@ cc_memcpy_and_return_ptr( ( (cc_allocing_fn_result_ty *)cntr )->other_ptr \ ) \ +// In the case of insertion operations (such as cc_insert, cc_push, and cc_get_or_insert), the process described above +// is complicated by the fact that to support heterogeneous insertion of C strings into containers that expect CC +// strings, we also need to convert the C string and check and potentially free the result. +// This could apply to the user-supplied key, element, or key and element. +// Consequently, we need somewhere to store pointers to the converted key and or element. +// To this end, rather than temporarily pointing the container handle at a cc_allocing_fn_result_ty struct, we point it +// at one of several "helper" structs that contain a cc_allocing_fn_result_ty struct and several other members. +// At the same time, we populate the helper struct's members with the converted key and/or element, a copy of the +// original handle (as the cc_allocing_fn_result_ty "new_cntr" member), and the current size of the container, when +// necessary. +// This way, we can check for conversion failure, pass the container handle and converted key and/or element into the +// relevant container function and store the result, and free the converted key and/or element if necessary. +// Because the cc_allocing_fn_result_ty is the helper struct's first member, CC_FIX_HNDL_AND_RETURN_OTHER_PTR is then +// called to restore the correct container handle. + +// Below, the various helper structs and macros to point the container at them are defined. +// For the other macros that assist in heterogeneous insertion and lookup, see the section "Heterogeneous string +// insertion and lookup conversion". + +#ifdef __cplusplus +#define CC_COMPOUND_LITERAL( ty, ... ) cc_unmove( __VA_ARGS__ ) +#else +#define CC_COMPOUND_LITERAL( ty, ... ) (ty) __VA_ARGS__ +#endif + +typedef struct +{ + alignas( cc_max_align_ty ) + cc_allocing_fn_result_ty result; + void *converted_el; + void *converted_key; +} cc_heteroinsert_helper_el_key_ty; + +#define CC_POINT_HNDL_TO_HETEROINSERT_HELPER_EL_KEY( cntr, converted_el, converted_key ) \ +cntr = (CC_TYPEOF_XP( cntr ))&CC_COMPOUND_LITERAL( \ + cc_heteroinsert_helper_el_key_ty, \ + { \ + { cntr, NULL }, \ + &CC_MAKE_LVAL_COPY( CC_EL_TY( *(cntr) ), converted_el ), \ + &CC_MAKE_LVAL_COPY( CC_KEY_TY( *(cntr) ), converted_key ) \ + } \ +) \ + +typedef struct +{ + alignas( cc_max_align_ty ) + cc_allocing_fn_result_ty result; + void *converted_el; +} cc_heterinsert_helper_el_only_ty; + +#define CC_POINT_HNDL_TO_HETEROINSERT_HELPER_EL_ONLY( cntr, converted_el ) \ +cntr = (CC_TYPEOF_XP( cntr ))&CC_COMPOUND_LITERAL( \ + cc_heterinsert_helper_el_only_ty, { { cntr, NULL }, &CC_MAKE_LVAL_COPY( CC_EL_TY( *(cntr) ), converted_el ) } \ +) \ + +typedef struct +{ + alignas( cc_max_align_ty ) + cc_allocing_fn_result_ty result; + void *converted_el; + size_t cntr_size; +} cc_heteroinsert_helper_el_cntr_size_ty; + +#define CC_POINT_HNDL_TO_HETEROINSERT_HELPER_EL_AND_CNTR_SIZE( cntr, converted_el, cntr_size ) \ +cntr = (CC_TYPEOF_XP( cntr ))&CC_COMPOUND_LITERAL( \ + cc_heteroinsert_helper_el_cntr_size_ty, \ + { \ + { cntr, NULL }, \ + &CC_MAKE_LVAL_COPY( CC_EL_TY( *(cntr) ), converted_el ), \ + cntr_size \ + } \ +) + +typedef struct +{ + alignas( cc_max_align_ty ) + cc_allocing_fn_result_ty result; + void *converted_el; + void *converted_key; + size_t cntr_size; +} cc_heteroinsert_helper_el_key_cntr_size_ty; + +#define CC_POINT_HNDL_TO_HETEROINSERT_HELPER_EL_KEY_CNTR_SIZE( cntr, converted_el, converted_key, cntr_size ) \ +cntr = (CC_TYPEOF_XP( cntr ))&CC_COMPOUND_LITERAL( \ + cc_heteroinsert_helper_el_key_cntr_size_ty, \ + { \ + { cntr, NULL }, \ + &CC_MAKE_LVAL_COPY( CC_EL_TY( *(cntr) ), converted_el ), \ + &CC_MAKE_LVAL_COPY( CC_KEY_TY( *(cntr) ), converted_key ), \ + cntr_size \ + } \ +) \ + // Functions to find the first and last non-zero uint16_t in a uint64_t. // These functions are used when we scan four buckets at a time while iterating over maps and sets. // They rely on compiler intrinsics if possible. @@ -1383,21 +1893,21 @@ static inline bool cc_is_little_endian( void ) #if defined( __GNUC__ ) && ULLONG_MAX == 0xFFFFFFFFFFFFFFFF -static inline int cc_first_nonzero_uint16( uint64_t a ) +static inline unsigned int cc_first_nonzero_uint16( uint64_t a ) { if( cc_is_little_endian() ) - return __builtin_ctzll( a ) / 16; + return (unsigned int)__builtin_ctzll( a ) / 16; - return __builtin_clzll( a ) / 16; + return (unsigned int)__builtin_clzll( a ) / 16; } // DEPRECATED. -static inline int cc_last_nonzero_uint16( uint64_t a ) +static inline unsigned int cc_last_nonzero_uint16( uint64_t a ) { if( cc_is_little_endian() ) - return __builtin_clzll( a ) / 16; + return (unsigned int)__builtin_clzll( a ) / 16; - return __builtin_ctzll( a ) / 16; + return (unsigned int)__builtin_ctzll( a ) / 16; } #elif defined( _MSC_VER ) && ( defined( _M_X64 ) || defined( _M_ARM64 ) ) @@ -1406,7 +1916,7 @@ static inline int cc_last_nonzero_uint16( uint64_t a ) #pragma intrinsic(_BitScanForward64) #pragma intrinsic(_BitScanReverse64) -static inline int cc_first_nonzero_uint16( uint64_t a ) +static inline unsigned int cc_first_nonzero_uint16( uint64_t a ) { unsigned long result; @@ -1422,7 +1932,7 @@ static inline int cc_first_nonzero_uint16( uint64_t a ) } // DEPRECATED. -static inline int cc_last_nonzero_uint16( uint64_t a ) +static inline unsigned int cc_last_nonzero_uint16( uint64_t a ) { unsigned long result; @@ -1439,9 +1949,9 @@ static inline int cc_last_nonzero_uint16( uint64_t a ) #else -static inline int cc_first_nonzero_uint16( uint64_t a ) +static inline unsigned int cc_first_nonzero_uint16( uint64_t a ) { - int result = 0; + unsigned int result = 0; uint32_t half; memcpy( &half, &a, sizeof( uint32_t ) ); @@ -1457,9 +1967,9 @@ static inline int cc_first_nonzero_uint16( uint64_t a ) } // DEPRECATED. -static inline int cc_last_nonzero_uint16( uint64_t a ) +static inline unsigned int cc_last_nonzero_uint16( uint64_t a ) { - int result = 3; + unsigned int result = 3; uint32_t half; memcpy( &half, (char *)&a + sizeof( uint32_t ), sizeof( uint32_t ) ); @@ -1567,7 +2077,7 @@ static inline cc_allocing_fn_result_ty cc_vec_reserve( static inline cc_allocing_fn_result_ty cc_vec_insert_n( void *cntr, size_t index, - void *els, + const void *els, size_t n, size_t el_size, cc_realloc_fnptr_ty realloc_ @@ -1630,7 +2140,7 @@ static inline cc_allocing_fn_result_ty cc_vec_insert( static inline cc_allocing_fn_result_ty cc_vec_push_n( void *cntr, - void *els, + const void *els, size_t n, size_t el_size, cc_realloc_fnptr_ty realloc_ @@ -1657,7 +2167,8 @@ static inline void *cc_vec_erase_n( size_t index, size_t n, size_t el_size, - cc_dtor_fnptr_ty el_dtor + cc_dtor_fnptr_ty el_dtor, + cc_free_fnptr_ty free_ ) { if( n == 0 ) @@ -1665,7 +2176,7 @@ static inline void *cc_vec_erase_n( if( el_dtor ) for( size_t j = 0; j < n; ++j ) - el_dtor( (char *)cntr + sizeof( cc_vec_hdr_ty ) + el_size * ( index + j ) ); + el_dtor( (char *)cntr + sizeof( cc_vec_hdr_ty ) + el_size * ( index + j ), free_ ); memmove( (char *)cntr + sizeof( cc_vec_hdr_ty ) + el_size * index, @@ -1677,9 +2188,6 @@ static inline void *cc_vec_erase_n( return (char *)cntr + sizeof( cc_vec_hdr_ty ) + el_size * index; } -// Shrinks the vector's capacity to its current size. -// Returns a cc_allocing_fn_result_ty containing the new container handle and a pointer that evaluates to true if the -// operation was successful or false in the case of allocation failure. static inline void *cc_vec_erase( void *cntr, void *key, // Pointer to size_t index. @@ -1689,10 +2197,10 @@ static inline void *cc_vec_erase( CC_UNUSED( cc_cmpr_fnptr_ty, cmpr ), cc_dtor_fnptr_ty el_dtor, CC_UNUSED( cc_dtor_fnptr_ty, key_dtor ), - CC_UNUSED( cc_free_fnptr_ty, free_ ) + cc_free_fnptr_ty free_ ) { - return cc_vec_erase_n( cntr, *(size_t *)key, 1, el_size, el_dtor ); + return cc_vec_erase_n( cntr, *(size_t *)key, 1, el_size, el_dtor, free_ ); } // Sets the number of elements in the vector. @@ -1706,7 +2214,8 @@ static inline cc_allocing_fn_result_ty cc_vec_resize( size_t n, size_t el_size, cc_dtor_fnptr_ty el_dtor, - cc_realloc_fnptr_ty realloc_ + cc_realloc_fnptr_ty realloc_, + cc_free_fnptr_ty free_ ) { // No resize necessary (also handles placeholder). @@ -1716,7 +2225,7 @@ static inline cc_allocing_fn_result_ty cc_vec_resize( // Downsizing. if( n < cc_vec_size( cntr ) ) { - cc_vec_erase_n( cntr, n, cc_vec_size( cntr ) - n, el_size, el_dtor ); + cc_vec_erase_n( cntr, n, cc_vec_size( cntr ) - n, el_size, el_dtor, free_ ); return cc_make_allocing_fn_result( cntr, cc_dummy_true_ptr ); } @@ -1739,6 +2248,9 @@ static inline cc_allocing_fn_result_ty cc_vec_resize( return result; } +// Shrinks the vector's capacity to its current size. +// Returns a cc_allocing_fn_result_ty containing the new container handle and a pointer that evaluates to true if the +// operation was successful or false in the case of allocation failure. static inline cc_allocing_fn_result_ty cc_vec_shrink( void *cntr, size_t el_size, @@ -1787,7 +2299,8 @@ static inline void *cc_vec_init_clone( cc_vec_size( src ), el_size, NULL, // Destructor unused. - realloc_ + realloc_, + NULL // Free function unused. ); if( CC_UNLIKELY( !result.other_ptr ) ) @@ -1809,10 +2322,10 @@ static inline void cc_vec_clear( CC_UNUSED( uint64_t, layout ), cc_dtor_fnptr_ty el_dtor, CC_UNUSED( cc_dtor_fnptr_ty, key_dtor ), - CC_UNUSED( cc_free_fnptr_ty, free_ ) + cc_free_fnptr_ty free_ ) { - cc_vec_erase_n( cntr, 0, cc_vec_size( cntr ), el_size, el_dtor ); + cc_vec_erase_n( cntr, 0, cc_vec_size( cntr ), el_size, el_dtor, free_ ); } // Clears the vector and frees its memory if it is not a placeholder. @@ -1831,7 +2344,7 @@ static inline void cc_vec_cleanup( 0, // Dummy. el_dtor, NULL, // Dummy. - NULL // Dummy. + free_ ); if( !cc_vec_is_placeholder( cntr ) ) @@ -2175,7 +2688,7 @@ static inline void *cc_list_erase( next->prev = hdr->prev; if( el_dtor ) - el_dtor( *(void **)key ); + el_dtor( *(void **)key, free_ ); free_( hdr ); --cc_list_hdr( cntr )->size; @@ -2468,7 +2981,7 @@ static inline void *cc_map_key_for( static inline size_t cc_map_bucket_index_from_itr( void *cntr, void *itr, size_t el_size, uint64_t layout ) { - return ( (char *)itr - (char *)cc_map_el( cntr, 0, el_size, layout ) ) / CC_BUCKET_SIZE( el_size, layout ); + return (size_t)( (char *)itr - (char *)cc_map_el( cntr, 0, el_size, layout ) ) / CC_BUCKET_SIZE( el_size, layout ); } static inline size_t cc_map_min_cap_for_n_els( @@ -2481,7 +2994,7 @@ static inline size_t cc_map_min_cap_for_n_els( // Round up to a power of two. size_t cap = CC_MAP_MIN_NONZERO_BUCKET_COUNT; - while( n > cap * max_load ) + while( n > (size_t)( (double)cap * max_load ) ) cap *= 2; return cap; @@ -2592,7 +3105,7 @@ static inline bool cc_map_evict( } // Disconnect the key-element pair from chain. - cc_map_hdr( cntr )->metadata[ prev ] = ( cc_map_hdr( cntr )->metadata[ prev ] & ~CC_MAP_DISPLACEMENT_MASK ) | + cc_map_hdr( cntr )->metadata[ prev ] = (uint16_t)( cc_map_hdr( cntr )->metadata[ prev ] & ~CC_MAP_DISPLACEMENT_MASK ) | ( cc_map_hdr( cntr )->metadata[ bucket ] & CC_MAP_DISPLACEMENT_MASK ); // Find the empty bucket to which to move the key-element pair. @@ -2614,8 +3127,9 @@ static inline bool cc_map_evict( // Re-link the key-element pair to the chain from its new bucket. cc_map_hdr( cntr )->metadata[ empty ] = ( cc_map_hdr( cntr )->metadata[ bucket ] & CC_MAP_HASH_FRAG_MASK ) | ( cc_map_hdr( cntr )->metadata[ prev ] & CC_MAP_DISPLACEMENT_MASK ); - cc_map_hdr( cntr )->metadata[ prev ] = ( cc_map_hdr( cntr )->metadata[ prev ] & ~CC_MAP_DISPLACEMENT_MASK ) | - displacement; + cc_map_hdr( cntr )->metadata[ prev ] = (uint16_t)( + ( cc_map_hdr( cntr )->metadata[ prev ] & ~CC_MAP_DISPLACEMENT_MASK ) | displacement + ); return true; } @@ -2643,7 +3157,8 @@ static inline void *cc_map_insert_raw( cc_hash_fnptr_ty hash, cc_cmpr_fnptr_ty cmpr, cc_dtor_fnptr_ty el_dtor, - cc_dtor_fnptr_ty key_dtor + cc_dtor_fnptr_ty key_dtor, + cc_free_fnptr_ty free_ ) { size_t key_hash = hash( key ); @@ -2657,7 +3172,7 @@ static inline void *cc_map_insert_raw( if( !( cc_map_hdr( cntr )->metadata[ home_bucket ] & CC_MAP_IN_HOME_BUCKET_MASK ) ) { // Load-factor check. - if( CC_UNLIKELY( cc_map_hdr( cntr )->size + 1 > max_load * cc_map_cap( cntr ) ) ) + if( CC_UNLIKELY( cc_map_hdr( cntr )->size + 1 > (size_t)( max_load * (double)cc_map_cap( cntr ) ) ) ) return NULL; // Vacate the home bucket if it contains a key-element pair. @@ -2690,10 +3205,10 @@ static inline void *cc_map_insert_raw( if( replace ) { if( key_dtor ) - key_dtor( cc_map_key( cntr, bucket, el_size, layout ) ); + key_dtor( cc_map_key( cntr, bucket, el_size, layout ), free_ ); if( el_dtor ) - el_dtor( cc_map_el( cntr, bucket, el_size, layout ) ); + el_dtor( cc_map_el( cntr, bucket, el_size, layout ), free_ ); memcpy( cc_map_key( cntr, bucket, el_size, layout ), key, CC_KEY_SIZE( layout ) ); memcpy( cc_map_el( cntr, bucket, el_size, layout ), el, el_size ); @@ -2710,7 +3225,7 @@ static inline void *cc_map_insert_raw( } // Load-factor check. - if( CC_UNLIKELY( cc_map_hdr( cntr )->size + 1 > max_load * cc_map_cap( cntr ) ) ) + if( CC_UNLIKELY( cc_map_hdr( cntr )->size + 1 > (size_t)( max_load * (double)cc_map_cap( cntr ) ) ) ) return NULL; // Find the earliest empty bucket, per quadratic probing. @@ -2728,8 +3243,9 @@ static inline void *cc_map_insert_raw( cc_map_hdr( cntr )->metadata[ empty ] = hashfrag | ( cc_map_hdr( cntr )->metadata[ prev ] & CC_MAP_DISPLACEMENT_MASK ); - cc_map_hdr( cntr )->metadata[ prev ] = ( cc_map_hdr( cntr )->metadata[ prev ] & ~CC_MAP_DISPLACEMENT_MASK ) | - displacement; + cc_map_hdr( cntr )->metadata[ prev ] = (uint16_t)( + ( cc_map_hdr( cntr )->metadata[ prev ] & ~CC_MAP_DISPLACEMENT_MASK ) | displacement + ); ++cc_map_hdr( cntr )->size; @@ -2785,8 +3301,9 @@ static inline void *cc_map_reinsert( cc_map_hdr( cntr )->metadata[ empty ] = hashfrag | ( cc_map_hdr( cntr )->metadata[ prev ] & CC_MAP_DISPLACEMENT_MASK ); - cc_map_hdr( cntr )->metadata[ prev ] = ( cc_map_hdr( cntr )->metadata[ prev ] & ~CC_MAP_DISPLACEMENT_MASK ) | - displacement; + cc_map_hdr( cntr )->metadata[ prev ] = (uint16_t)( + ( cc_map_hdr( cntr )->metadata[ prev ] & ~CC_MAP_DISPLACEMENT_MASK ) | displacement + ); ++cc_map_hdr( cntr )->size; @@ -2939,7 +3456,8 @@ static inline cc_allocing_fn_result_ty cc_map_insert( hash, cmpr, el_dtor, - key_dtor + key_dtor, + free_ ); if( CC_LIKELY( itr ) ) @@ -3134,7 +3652,8 @@ static inline bool cc_map_erase_raw( uint64_t layout, cc_hash_fnptr_ty hash, cc_dtor_fnptr_ty el_dtor, - cc_dtor_fnptr_ty key_dtor + cc_dtor_fnptr_ty key_dtor, + cc_free_fnptr_ty free_ ) { --cc_map_hdr( cntr )->size; @@ -3146,9 +3665,9 @@ static inline bool cc_map_erase_raw( ) { if( el_dtor ) - el_dtor( cc_map_el( cntr, erase_bucket, el_size, layout ) ); + el_dtor( cc_map_el( cntr, erase_bucket, el_size, layout ), free_ ); if( key_dtor ) - key_dtor( cc_map_key( cntr, erase_bucket, el_size, layout ) ); + key_dtor( cc_map_key( cntr, erase_bucket, el_size, layout ), free_ ); cc_map_hdr( cntr )->metadata[ erase_bucket ] = CC_MAP_EMPTY; return true; @@ -3164,9 +3683,9 @@ static inline bool cc_map_erase_raw( } if( el_dtor ) - el_dtor( cc_map_el( cntr, erase_bucket, el_size, layout ) ); + el_dtor( cc_map_el( cntr, erase_bucket, el_size, layout ), free_ ); if( key_dtor ) - key_dtor( cc_map_key( cntr, erase_bucket, el_size, layout ) ); + key_dtor( cc_map_key( cntr, erase_bucket, el_size, layout ), free_ ); // Case 2: The key-element pair is the last in a chain containing multiple key-element pairs. // Traverse the chain from the beginning and find the penultimate key-element pair. @@ -3207,8 +3726,10 @@ static inline bool cc_map_erase_raw( CC_BUCKET_SIZE( el_size, layout ) ); - cc_map_hdr( cntr )->metadata[ erase_bucket ] = ( cc_map_hdr( cntr )->metadata[ erase_bucket ] & - ~CC_MAP_HASH_FRAG_MASK ) | ( cc_map_hdr( cntr )->metadata[ bucket ] & CC_MAP_HASH_FRAG_MASK ); + cc_map_hdr( cntr )->metadata[ erase_bucket ] = (uint16_t)( + ( cc_map_hdr( cntr )->metadata[ erase_bucket ] & ~CC_MAP_HASH_FRAG_MASK ) | + ( cc_map_hdr( cntr )->metadata[ bucket ] & CC_MAP_HASH_FRAG_MASK ) + ); cc_map_hdr( cntr )->metadata[ prev ] |= CC_MAP_DISPLACEMENT_MASK; cc_map_hdr( cntr )->metadata[ bucket ] = CC_MAP_EMPTY; @@ -3237,12 +3758,12 @@ static inline CC_ALWAYS_INLINE void *cc_map_erase_itr( cc_hash_fnptr_ty hash, cc_dtor_fnptr_ty el_dtor, cc_dtor_fnptr_ty key_dtor, - CC_UNUSED( cc_free_fnptr_ty, free_ ) + cc_free_fnptr_ty free_ ) { size_t bucket = cc_map_bucket_index_from_itr( cntr, itr, el_size, layout ); - if( cc_map_erase_raw( cntr, bucket, SIZE_MAX, el_size, layout, hash, el_dtor, key_dtor ) ) + if( cc_map_erase_raw( cntr, bucket, SIZE_MAX, el_size, layout, hash, el_dtor, key_dtor, free_ ) ) return cc_map_next( cntr, itr, el_size, layout ); return itr; @@ -3260,7 +3781,7 @@ static inline void *cc_map_erase( cc_cmpr_fnptr_ty cmpr, cc_dtor_fnptr_ty el_dtor, cc_dtor_fnptr_ty key_dtor, - CC_UNUSED( cc_free_fnptr_ty, free_ ) + cc_free_fnptr_ty free_ ) { size_t key_hash = hash( key ); @@ -3278,7 +3799,7 @@ static inline void *cc_map_erase( CC_LIKELY( cmpr( cc_map_key( cntr, bucket, el_size, layout ), key ) ) ) { - cc_map_erase_raw( cntr, bucket, home_bucket, el_size, layout, hash, el_dtor, key_dtor ); + cc_map_erase_raw( cntr, bucket, home_bucket, el_size, layout, hash, el_dtor, key_dtor, free_ ); return &cc_dummy_true; } @@ -3375,7 +3896,7 @@ static inline void cc_map_clear( uint64_t layout, cc_dtor_fnptr_ty el_dtor, cc_dtor_fnptr_ty key_dtor, - CC_UNUSED( cc_free_fnptr_ty, free_ ) + cc_free_fnptr_ty free_ ) { if( cc_map_size( cntr ) == 0 ) // Also handles placeholder. @@ -3385,10 +3906,10 @@ static inline void cc_map_clear( if( cc_map_hdr( cntr )->metadata[ bucket ] ) { if( key_dtor ) - key_dtor( cc_map_key( cntr, bucket, el_size, layout ) ); + key_dtor( cc_map_key( cntr, bucket, el_size, layout ), free_ ); if( el_dtor ) - el_dtor( cc_map_el( cntr, bucket, el_size, layout ) ); + el_dtor( cc_map_el( cntr, bucket, el_size, layout ), free_ ); cc_map_hdr( cntr )->metadata[ bucket ] = CC_MAP_EMPTY; } @@ -3406,7 +3927,7 @@ static inline void cc_map_cleanup( cc_free_fnptr_ty free_ ) { - cc_map_clear( cntr, el_size, layout, el_dtor, key_dtor, NULL /* Dummy */ ); + cc_map_clear( cntr, el_size, layout, el_dtor, key_dtor, free_ ); if( !cc_map_is_placeholder( cntr ) ) free_( cntr ); @@ -3496,7 +4017,7 @@ static inline void *cc_set_erase_itr( cc_hash_fnptr_ty hash, cc_dtor_fnptr_ty el_dtor, CC_UNUSED( cc_dtor_fnptr_ty, key_dtor ), - CC_UNUSED( cc_free_fnptr_ty, free_ ) + cc_free_fnptr_ty free_ ) { return cc_map_erase_itr( @@ -3507,7 +4028,7 @@ static inline void *cc_set_erase_itr( hash, el_dtor, NULL, // Only one destructor. - NULL // Dummy. + free_ ); } @@ -3520,7 +4041,7 @@ static inline void *cc_set_erase( cc_cmpr_fnptr_ty cmpr, cc_dtor_fnptr_ty el_dtor, CC_UNUSED( cc_dtor_fnptr_ty, key_dtor ), - CC_UNUSED( cc_free_fnptr_ty, free_ ) + cc_free_fnptr_ty free_ ) { return cc_map_erase( @@ -3532,7 +4053,7 @@ static inline void *cc_set_erase( cmpr, el_dtor, NULL, // Only one destructor. - NULL // Dummy. + free_ ); } @@ -3566,10 +4087,10 @@ static inline void cc_set_clear( uint64_t layout, cc_dtor_fnptr_ty el_dtor, CC_UNUSED( cc_dtor_fnptr_ty, key_dtor ), - CC_UNUSED( cc_free_fnptr_ty, free_ ) + cc_free_fnptr_ty free_ ) { - cc_map_clear( cntr, 0 /* Zero element size */, layout, el_dtor, NULL /* Only one destructor */, NULL /* Dummy */ ); + cc_map_clear( cntr, 0 /* Zero element size */, layout, el_dtor, NULL /* Only one destructor */, free_ ); } static inline void cc_set_cleanup( @@ -3950,7 +4471,7 @@ static inline cc_allocing_fn_result_ty cc_omap_insert( cc_dtor_fnptr_ty el_dtor, cc_dtor_fnptr_ty key_dtor, cc_realloc_fnptr_ty realloc_, - CC_UNUSED( cc_free_fnptr_ty, free_ ) + cc_free_fnptr_ty free_ ) { // Allocate a header if necessary. @@ -3981,10 +4502,10 @@ static inline cc_allocing_fn_result_ty cc_omap_insert( if( replace ) { if( key_dtor ) - key_dtor( cc_omap_key( node, el_size, layout ) ); + key_dtor( cc_omap_key( node, el_size, layout ), free_ ); if( el_dtor ) - el_dtor( cc_omap_el( node ) ); + el_dtor( cc_omap_el( node ), free_ ); memcpy( cc_omap_key( node, el_size, layout ), key, CC_KEY_SIZE( layout ) ); memcpy( cc_omap_el( node ), el, el_size ); @@ -4191,10 +4712,10 @@ static inline void cc_omap_erase_raw( } if( key_dtor ) - key_dtor( cc_omap_key( node, el_size, layout ) ); + key_dtor( cc_omap_key( node, el_size, layout ), free_ ); if( el_dtor ) - el_dtor( cc_omap_el( node ) ); + el_dtor( cc_omap_el( node ), free_ ); free_( node ); --cc_omap_hdr( cntr )->size; @@ -4274,10 +4795,10 @@ static inline void cc_omap_clear( next = node->parent; if( key_dtor ) - key_dtor( cc_omap_key( node, el_size, layout ) ); + key_dtor( cc_omap_key( node, el_size, layout ), free_ ); if( el_dtor ) - el_dtor( cc_omap_el( node ) ); + el_dtor( cc_omap_el( node ), free_ ); free_( node ); } @@ -4605,31 +5126,1341 @@ static inline void *cc_oset_next( return cc_omap_next( cntr, itr, 0 /* Zero element size */, 0 /* Dummy */ ); } +/*--------------------------------------------------------------------------------------------------------------------*/ +/* String */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +// cc_str is based largely on the implementation of cc_vec. However, besides null-termination, there are some other +// major differences: +// * cc_str's header contains a pointer to its data buffer. Usually, this pointer simply points to the end of the header +// inside the same memory allocation. However, in the case of a temporary string constructed by the library for the +// purpose of heterogeneous lookup in associative containers, the pointer points to a separate buffer supplied by the +// user. This approach avoids unnecessary allocations at the expense of a slightly larger header struct. +// * cc_str never calls destructors on its elements. This choice allows cc_str itself to have a default destructor so +// that it can easily be used as the key or element type of other containers. +// * cc_str includes formatted push and insertion functions for easy string building. + +// String header. +typedef struct +{ + alignas( alignof( char32_t ) > alignof( size_t ) ? alignof( char32_t ) : alignof( size_t ) ) + size_t size; + size_t cap; + void *data; +} cc_str_hdr_ty; + +// Global placeholders for strings with no allocated storage. +// To avoid any strict-aliasing violations, we need specialized placeholders for different element types. +#if defined( __cplusplus ) && __cplusplus >= 202101L +static const cc_str_hdr_ty cc_str_placeholder_char8 = { 0, 0, (void *)u8"" }; +#else +static const cc_str_hdr_ty cc_str_placeholder_char8 = { 0, 0, (void *)"" }; +#endif +static const cc_str_hdr_ty cc_str_placeholder_char16 = { 0, 0, (void *)u"" }; +static const cc_str_hdr_ty cc_str_placeholder_char32 = { 0, 0, (void *)U"" }; + +// CC_STR_PLACEHOLDER macro for obtaining the correct placeholder. + +#ifdef __cplusplus + +#define CC_STR_PLACEHOLDER( ty ) \ +( \ + std::is_same::value ? (void *)&cc_str_placeholder_char16 : \ + std::is_same::value ? (void *)&cc_str_placeholder_char32 : \ + /* char, unsigned char, signed char, and char8_t: */ \ + (void *)&cc_str_placeholder_char8 \ +) \ + +#else + +#define CC_STR_PLACEHOLDER( ty ) \ +_Generic( (ty){ 0 }, \ + char16_t: &cc_str_placeholder_char16, \ + char32_t: &cc_str_placeholder_char32, \ + /* char, unsigned char, signed char, and char8_t: */ \ + default: &cc_str_placeholder_char8 \ +) \ + +#endif + +// Easy header access function. +static inline cc_str_hdr_ty *cc_str_hdr( void *cntr ) +{ + return (cc_str_hdr_ty *)cntr; +} + +static inline size_t cc_str_size( void *cntr ) +{ + return cc_str_hdr( cntr )->size; +} + +static inline size_t cc_str_cap( void *cntr ) +{ + return cc_str_hdr( cntr )->cap; +} + +static inline bool cc_str_is_placeholder( void *cntr ) +{ + return cc_str_hdr( cntr )->cap == 0; +} + +// Returns a pointer-iterator to the element at a specified index. +static inline void *cc_str_get( + void *cntr, + void *key, // Pointer to a size_t index. + size_t el_size, + CC_UNUSED( uint64_t, layout ), + CC_UNUSED( cc_hash_fnptr_ty, hash ), + CC_UNUSED( cc_cmpr_fnptr_ty, cmpr ) +) +{ + return (char *)cc_str_hdr( cntr )->data + el_size * *(size_t *)key; +} + +// Ensures that the capacity is large enough to accommodate n elements without reallocation. +// Returns a cc_allocing_fn_result_ty containing the new handle and a pointer that evaluates to true if the operation +// was successful. +static inline cc_allocing_fn_result_ty cc_str_reserve( + void *cntr, + size_t n, + size_t el_size, + CC_UNUSED( uint64_t, layout ), + CC_UNUSED( cc_hash_fnptr_ty, hash ), + CC_UNUSED( double, max_load ), + cc_realloc_fnptr_ty realloc_, + CC_UNUSED( cc_free_fnptr_ty, free_ ) +) +{ + if( cc_str_cap( cntr ) >= n ) + return cc_make_allocing_fn_result( cntr, cc_dummy_true_ptr ); + + bool is_placeholder = cc_str_is_placeholder( cntr ); + + cc_str_hdr_ty *new_cntr = (cc_str_hdr_ty *)realloc_( + is_placeholder ? NULL : cntr, + sizeof( cc_str_hdr_ty ) + el_size * n + el_size // Terminator. + ); + + if( CC_UNLIKELY( !new_cntr ) ) + return cc_make_allocing_fn_result( cntr, NULL ); + + new_cntr->data = new_cntr + 1; // Set the internal pointer to point to the end of the header. + + if( is_placeholder ) + { + new_cntr->size = 0; + memset( new_cntr->data, 0, el_size ); // Ensure null termination. + } + + new_cntr->cap = n; + return cc_make_allocing_fn_result( new_cntr, cc_dummy_true_ptr ); +} + +// Inserts elements at the specified index. +// Returns a cc_allocing_fn_result_ty containing the new handle and a pointer-iterator to the newly inserted elements. +// If the underlying storage needed to be expanded and an allocation failure occurred, or if n is zero, the latter +// pointer will be NULL. +static inline cc_allocing_fn_result_ty cc_str_insert_n( + void *cntr, + size_t index, + const void *els, + size_t n, + size_t el_size, + cc_realloc_fnptr_ty realloc_ +) +{ + if( n == 0 ) + return cc_make_allocing_fn_result( cntr, NULL ); + + if( cc_str_size( cntr ) + n > cc_str_cap( cntr ) ) + { + size_t cap = cc_str_cap( cntr ); + if( !cap ) + cap = 2; + + while( cap < cc_str_size( cntr ) + n ) + cap *= 2; + + cc_allocing_fn_result_ty result = cc_str_reserve( + cntr, + cap, + el_size, + 0, // Dummy. + NULL, // Dummy. + 0.0, // Dummy. + realloc_, + NULL // Dummy. + ); + if( CC_UNLIKELY( !result.other_ptr ) ) + return result; + + cntr = result.new_cntr; + } + + char *new_els = (char *)cc_str_hdr( cntr )->data + el_size * index; + memmove( new_els + n * el_size, new_els, el_size * ( cc_str_hdr( cntr )->size - index ) + el_size /* Terminator */ ); + memcpy( new_els, els, el_size * n ); + cc_str_hdr( cntr )->size += n; + + return cc_make_allocing_fn_result( cntr, new_els ); +} + +static inline cc_allocing_fn_result_ty cc_str_insert( + void *cntr, + void *el, + void *key, // Pointer to size_t index. + CC_UNUSED( bool, replace ), + size_t el_size, + CC_UNUSED( uint64_t, layout ), + CC_UNUSED( cc_hash_fnptr_ty, hash ), + CC_UNUSED( cc_cmpr_fnptr_ty, cmpr ), + CC_UNUSED( double, max_load ), + CC_UNUSED( cc_dtor_fnptr_ty, el_dtor ), + CC_UNUSED( cc_dtor_fnptr_ty, key_dtor ), + cc_realloc_fnptr_ty realloc_, + CC_UNUSED( cc_free_fnptr_ty, free_ ) +) +{ + return cc_str_insert_n( cntr, *(size_t *)key, el, 1, el_size, realloc_ ); +} + +static inline cc_allocing_fn_result_ty cc_str_push_n( + void *cntr, + const void *els, + size_t n, + size_t el_size, + cc_realloc_fnptr_ty realloc_ +) +{ + return cc_str_insert_n( cntr, cc_str_size( cntr ), els, n, el_size, realloc_ ); +} + +static inline cc_allocing_fn_result_ty cc_str_push( + void *cntr, + void *el, + size_t el_size, + cc_realloc_fnptr_ty realloc_ +) +{ + return cc_str_push_n( cntr, el, 1, el_size, realloc_ ); +} + +// Generic formatted insertion into strings requires that each argument provided to cc_str_insert_wrapped_fmt_args be +// wrapped in a tagged union (cc_wrapped_str_fmt_arg_ty). +// Format mode-modifying functions also return the same tagged-union type so that they can be passed in alongside other +// insertion arguments. + +#define CC_STR_FMT_ARG_UNSIGNED_INTEGER 0 +#define CC_STR_FMT_ARG_INTEGER 1 +#define CC_STR_FMT_ARG_FLOATING 2 +#define CC_STR_FMT_ARG_C_STRING 3 +#define CC_STR_FMT_ARG_STR 4 +#define CC_STR_FMT_ARG_VOID_POINTER 5 +#define CC_STR_FMT_ARG_MODE_INTEGER_DEC 6 +#define CC_STR_FMT_ARG_MODE_INTEGER_HEX 7 +#define CC_STR_FMT_ARG_MODE_INTEGER_OCT 8 +#define CC_STR_FMT_ARG_MODE_FLOATING_DEC 9 +#define CC_STR_FMT_ARG_MODE_FLOATING_HEX 10 +#define CC_STR_FMT_ARG_MODE_FLOATING_SCI 11 +#define CC_STR_FMT_ARG_MODE_FLOATING_SHORTEST 12 + +typedef struct +{ + unsigned char type; + bool is_final; + size_t formatted_length; + + union + { + unsigned long long unsigned_integer; + long long integer; + double floating; + const void *pointer; + }; +} cc_wrapped_str_fmt_arg_ty; + +// CC_WRAP_STR_FMT_ARG macro to wrap an argument to be formatted and inserted into a string in a tagged union also +// indicating the argument type and whether or not it is the final argument. +// This macro also handles the type promotions described in the string API documentation for cc_push_fmt and +// cc_insert_fmt. +// In C++, the wrapping is achieved via template functions. +// In C, we use a _Generic expression to dispatch the argument to the correct wrapping function. + +#ifdef __cplusplus + +template< + typename el_ty, + typename arg_ty, + typename std::enable_if::value && std::is_unsigned::value, bool>::type = true +> +cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg( arg_ty arg, bool is_final ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_UNSIGNED_INTEGER; + wrapped_arg.is_final = is_final; + wrapped_arg.formatted_length = 0; + wrapped_arg.unsigned_integer = arg; + return wrapped_arg; +} + +template< + typename el_ty, + typename arg_ty, + typename std::enable_if::value && std::is_signed::value, bool>::type = true +> +cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg( arg_ty arg, bool is_final ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_INTEGER; + wrapped_arg.is_final = is_final; + wrapped_arg.formatted_length = 0; + wrapped_arg.integer = arg; + return wrapped_arg; +} + +template< + typename el_ty, + typename arg_ty, + typename std::enable_if::value, bool>::type = true +> +cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg( arg_ty arg, bool is_final ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_FLOATING; + wrapped_arg.is_final = is_final; + wrapped_arg.formatted_length = 0; + wrapped_arg.floating = arg; + return wrapped_arg; +} + +template< + typename el_ty, + typename arg_ty, + typename std::enable_if< + ( std::is_same::value && std::is_same::value) || + ( std::is_same::value && std::is_same::value) || + ( std::is_same::value && std::is_same::value ) || + ( std::is_same::value && std::is_same::value ) || + ( std::is_same::value && std::is_same::value ) || + ( std::is_same::value && std::is_same::value ) || +#if __cplusplus >= 202101L + ( std::is_same::value && std::is_same::value ) || + ( std::is_same::value && std::is_same::value ) || +#endif + ( std::is_same::value && std::is_same::value ) || + ( std::is_same::value && std::is_same::value ) || + ( std::is_same::value && std::is_same::value ) || + ( std::is_same::value && std::is_same::value ) + , bool>::type = true +> +cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg( arg_ty arg, bool is_final ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_C_STRING; + wrapped_arg.is_final = is_final; + wrapped_arg.formatted_length = 0; + wrapped_arg.pointer = arg; + return wrapped_arg; +} + +// For CC strings, we must specify the container type manually rather than relying on the CC_STR_RAW macro because of +// template argument-deduction issues. +template< + typename el_ty, + typename arg_ty, + typename std::enable_if::value, bool>::type = true +> +cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg( arg_ty arg, bool is_final ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_STR; + wrapped_arg.is_final = is_final; + wrapped_arg.formatted_length = 0; + wrapped_arg.pointer = *arg; + return wrapped_arg; +} + +template< + typename el_ty, + typename arg_ty, + typename std::enable_if< + std::is_same::value || std::is_same::value, + bool + >::type = true +> +cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg( arg_ty arg, bool is_final ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_VOID_POINTER; + wrapped_arg.is_final = is_final; + wrapped_arg.formatted_length = 0; + wrapped_arg.pointer = arg; + return wrapped_arg; +} + +template< + typename el_ty, + typename arg_ty, + typename std::enable_if::value, bool>::type = true +> +cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg( arg_ty arg, bool is_final ) +{ + arg.is_final = is_final; + return arg; +} + +#define CC_WRAP_STR_FMT_ARG( el_ty, arg, is_final ) cc_wrap_str_fmt_arg( arg, is_final ) + +#else + +static inline cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg_char( char arg, bool is_final ) +{ + if( CHAR_MIN < 0 ) + return (cc_wrapped_str_fmt_arg_ty){ CC_STR_FMT_ARG_INTEGER, is_final, .integer = (signed long long )arg }; + + return (cc_wrapped_str_fmt_arg_ty){ + CC_STR_FMT_ARG_UNSIGNED_INTEGER, is_final, 0, .unsigned_integer = (unsigned long long)arg + }; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg_unsigned_integer( + unsigned long long arg, + bool is_final +) +{ + return (cc_wrapped_str_fmt_arg_ty){ CC_STR_FMT_ARG_UNSIGNED_INTEGER, is_final, 0, .unsigned_integer = arg }; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg_integer( long long arg, bool is_final ) +{ + return (cc_wrapped_str_fmt_arg_ty){ CC_STR_FMT_ARG_INTEGER, is_final, 0, .integer = arg }; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg_floating( double arg, bool is_final ) +{ + return (cc_wrapped_str_fmt_arg_ty){ CC_STR_FMT_ARG_FLOATING, is_final, 0, .floating = arg }; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg_cstring( const void *arg, bool is_final ) +{ + return (cc_wrapped_str_fmt_arg_ty){ CC_STR_FMT_ARG_C_STRING, is_final, 0, .pointer = arg }; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg_str( const void *arg, bool is_final ) +{ + return (cc_wrapped_str_fmt_arg_ty){ CC_STR_FMT_ARG_STR, is_final, 0, .pointer = arg }; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg_void_pointer( const void *arg, bool is_final ) +{ + return (cc_wrapped_str_fmt_arg_ty){ CC_STR_FMT_ARG_VOID_POINTER, is_final, 0, .pointer = arg }; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_wrap_str_fmt_arg_passthrough( cc_wrapped_str_fmt_arg_ty arg, bool is_final ) +{ + arg.is_final = is_final; + return arg; +} + +#define CC_WRAP_STR_FMT_ARG( el_ty, arg, is_final ) _Generic( (arg), \ + bool: cc_wrap_str_fmt_arg_unsigned_integer, \ + cc_maybe_char: cc_wrap_str_fmt_arg_char, \ + unsigned char: cc_wrap_str_fmt_arg_unsigned_integer, \ + signed char: cc_wrap_str_fmt_arg_integer, \ + unsigned short: cc_wrap_str_fmt_arg_unsigned_integer, \ + short: cc_wrap_str_fmt_arg_integer, \ + unsigned int: cc_wrap_str_fmt_arg_unsigned_integer, \ + int: cc_wrap_str_fmt_arg_integer, \ + unsigned long: cc_wrap_str_fmt_arg_unsigned_integer, \ + long: cc_wrap_str_fmt_arg_integer, \ + unsigned long long: cc_wrap_str_fmt_arg_unsigned_integer, \ + long long: cc_wrap_str_fmt_arg_integer, \ + cc_maybe_size_t: cc_wrap_str_fmt_arg_unsigned_integer, \ + float: cc_wrap_str_fmt_arg_floating, \ + double: cc_wrap_str_fmt_arg_floating, \ + el_ty *: cc_wrap_str_fmt_arg_cstring, \ + const el_ty *: cc_wrap_str_fmt_arg_cstring, \ + cc_wrapped_str_fmt_arg_ty: cc_wrap_str_fmt_arg_passthrough, \ + void *: cc_wrap_str_fmt_arg_void_pointer, \ + const void *: cc_wrap_str_fmt_arg_void_pointer, \ + CC_STR_RAW( el_ty ): cc_wrap_str_fmt_arg_str \ +)( arg, is_final ) \ + +#endif + +// CC_WRAPPED_STR_FMT_ARGS_ARRAY macro for transforming a __VA_ARGS__ of raw string formatted insertion arguments into +// an array of wrapped arguments. + +#define CC_WRAPPED_STR_FMT_ARGS_1( el_ty, arg ) CC_WRAP_STR_FMT_ARG( el_ty, arg, true ) + +#define CC_WRAPPED_STR_FMT_ARGS_2( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_1( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_3( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_2( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_4( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_3( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_5( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_4( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_6( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_5( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_7( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_6( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_8( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_7( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_9( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_8( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_10( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_9( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_11( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_10( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_12( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_11( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_13( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_12( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_14( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_13( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_15( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_14( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_16( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_15( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_17( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_16( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_18( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_17( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_19( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_18( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_20( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_19( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_21( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_20( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_22( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_21( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_23( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_22( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_24( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_23( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_25( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_24( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_26( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_25( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_27( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_26( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_28( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_27( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_29( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_28( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_30( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_29( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_31( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_30( el_ty, __VA_ARGS__ ) ) \ + +#define CC_WRAPPED_STR_FMT_ARGS_32( el_ty, arg, ... ) \ +CC_WRAP_STR_FMT_ARG( el_ty, arg, false ), CC_MSVC_PP_FIX( CC_WRAPPED_STR_FMT_ARGS_31( el_ty, __VA_ARGS__ ) ) \ + +#ifdef __cplusplus + +#define CC_WRAPPED_STR_FMT_ARGS_ARRAY( el_ty, ... ) \ +decltype( std::declval() ){ \ + CC_CAT_2( CC_WRAPPED_STR_FMT_ARGS_, CC_N_ARGS( __VA_ARGS__ ) )( el_ty, __VA_ARGS__ ) \ +} \ + +#else + +#define CC_WRAPPED_STR_FMT_ARGS_ARRAY( el_ty, ... ) \ +(cc_wrapped_str_fmt_arg_ty[]){ \ + CC_MSVC_PP_FIX( CC_CAT_2( CC_WRAPPED_STR_FMT_ARGS_, CC_N_ARGS( __VA_ARGS__ ) )( el_ty, __VA_ARGS__ ) ) \ +} \ + +#endif + +// Functions for setting the formatting mode of integer and floating-point arguments. + +static inline cc_wrapped_str_fmt_arg_ty cc_integer_dec( int min_digits ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_MODE_INTEGER_DEC; + wrapped_arg.formatted_length = 0; + wrapped_arg.integer = min_digits; + return wrapped_arg; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_integer_hex( int min_digits ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_MODE_INTEGER_HEX; + wrapped_arg.formatted_length = 0; + wrapped_arg.integer = min_digits; + return wrapped_arg; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_integer_oct( int min_digits ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_MODE_INTEGER_OCT; + wrapped_arg.formatted_length = 0; + wrapped_arg.integer = min_digits; + return wrapped_arg; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_float_dec( int precision ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_MODE_FLOATING_DEC; + wrapped_arg.formatted_length = 0; + wrapped_arg.integer = precision; + return wrapped_arg; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_float_hex( int precision ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_MODE_FLOATING_HEX; + wrapped_arg.formatted_length = 0; + wrapped_arg.integer = precision; + return wrapped_arg; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_float_sci( int precision ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_MODE_FLOATING_SCI; + wrapped_arg.formatted_length = 0; + wrapped_arg.integer = precision; + return wrapped_arg; +} + +static inline cc_wrapped_str_fmt_arg_ty cc_float_shortest( int significant_digits ) +{ + cc_wrapped_str_fmt_arg_ty wrapped_arg; + wrapped_arg.type = CC_STR_FMT_ARG_MODE_FLOATING_SHORTEST; + wrapped_arg.formatted_length = 0; + wrapped_arg.integer = significant_digits; + return wrapped_arg; +} + +// Functions for determining the length of char16_t and char32_t C strings. + +static inline size_t cc_strlen_char16( const char16_t *string ) +{ + const char16_t *index = string; + while( *index ) + ++index; + return (size_t)( index - string ); +} + +static inline size_t cc_strlen_char32( const char32_t *string ) +{ + const char32_t *index = string; + while( *index ) + ++index; + return (size_t)( index - string ); +} + +// To avoid the need for a temporary buffer, formatted insertions into char16_t and char32_t strings initially print as +// ASCII into the string's own buffer. +// Hence, a post-print fix-up step is needed to distribute the characters properly. + +static inline void cc_snprintf_fixup_char16( char *first_char, size_t char_count ) +{ + char *src_char = first_char + ( char_count - 1 ); + char16_t *dest_char32 = (char16_t *)( first_char + sizeof( char16_t ) * ( char_count - 1 ) ); + while( char_count > 0 ) + { + *dest_char32 = (char16_t)*src_char; + --src_char; + --dest_char32; + --char_count; + } +} + +static inline void cc_snprintf_fixup_char32( char *first_char, size_t char_count ) +{ + char *src_char = first_char + ( char_count - 1 ); + char32_t *dest_char32 = (char32_t *)( first_char + sizeof( char32_t ) * ( char_count - 1 ) ); + while( char_count > 0 ) + { + *dest_char32 = (char32_t)*src_char; + --src_char; + --dest_char32; + --char_count; + } +} + +// Inserts formatted arguments into a string. +// This requires: +// * Iterating over the array of arguments and counting the number of characters that each one comprises when formatted. +// * Growing the string's buffer to accommodate all the insertions, if necessary. +// * Reiterating over the arguments again and performing the insertions. +// Returns a cc_allocing_fn_result_ty containing the new handle and a pointer-iterator to the first newly inserted +// element. +// If the underlying storage needed to be expanded and an allocation failure occurred, the latter pointer will be NULL. +static inline cc_allocing_fn_result_ty cc_str_insert_wrapped_fmt_args( + void *cntr, + cc_wrapped_str_fmt_arg_ty *wrapped_args, + size_t index, + size_t el_size, + cc_realloc_fnptr_ty realloc_ +) +{ + unsigned int integer_mode = CC_STR_FMT_ARG_MODE_INTEGER_DEC; + unsigned int floating_mode = CC_STR_FMT_ARG_MODE_FLOATING_DEC; + int integer_min_digits = 1; + int floating_precision_or_significant_digits = 2; + + // First count the number of characters needed to complete all the insertions, along with the total. + size_t total = 0; + cc_wrapped_str_fmt_arg_ty *wrapped_arg = wrapped_args; + while( true ) + { + switch( wrapped_arg->type ) + { + case CC_STR_FMT_ARG_UNSIGNED_INTEGER: + { + int result = snprintf( + NULL, + 0, + integer_mode == CC_STR_FMT_ARG_MODE_INTEGER_DEC ? "%.*llu" : + integer_mode == CC_STR_FMT_ARG_MODE_INTEGER_HEX ? "%.*llx" : + /* CC_STR_FMT_ARG_MODE_INTEGER_OCT */ "%.*llo" , + integer_min_digits, + wrapped_arg->unsigned_integer + ); + + wrapped_arg->formatted_length = result > 0 ? (size_t)result : SIZE_MAX; + } + break; + case CC_STR_FMT_ARG_INTEGER: + { + int result; + if( integer_mode == CC_STR_FMT_ARG_MODE_INTEGER_DEC ) + result = snprintf( NULL, 0, "%.*lld", integer_min_digits, wrapped_arg->integer ); + else + result = snprintf( + NULL, + 0, + integer_mode == CC_STR_FMT_ARG_MODE_INTEGER_HEX ? "%.*llx" : + /* CC_STR_FMT_ARG_MODE_INTEGER_OCT */ "%.*llo", + integer_min_digits, + (unsigned long long)wrapped_arg->integer + ); + + wrapped_arg->formatted_length = result > 0 ? (size_t)result : SIZE_MAX; + } + break; + case CC_STR_FMT_ARG_FLOATING: + { + int result = snprintf( + NULL, + 0, + floating_mode == CC_STR_FMT_ARG_MODE_FLOATING_DEC ? "%.*f" : + floating_mode == CC_STR_FMT_ARG_MODE_FLOATING_HEX ? "%.*a" : + floating_mode == CC_STR_FMT_ARG_MODE_FLOATING_SCI ? "%.*e" : + /* CC_STR_FMT_ARG_MODE_FLOATING_SHORTEST */ "%.*g", + floating_precision_or_significant_digits, + wrapped_arg->floating + ); + + wrapped_arg->formatted_length = result > 0 ? (size_t)result : SIZE_MAX; + } + break; + case CC_STR_FMT_ARG_C_STRING: + { + wrapped_arg->formatted_length = ( + el_size == sizeof( char ) ? (size_t)strlen( (const char *)wrapped_arg->pointer ) : + el_size == sizeof( char16_t ) ? cc_strlen_char16( (char16_t *)wrapped_arg->pointer ) : + /* char32_t */ cc_strlen_char32( (char32_t *)wrapped_arg->pointer ) + ); + } + break; + case CC_STR_FMT_ARG_VOID_POINTER: + { + int result = snprintf( NULL, 0, "%p", wrapped_arg->pointer ); + wrapped_arg->formatted_length = result > 0 ? (size_t)result : SIZE_MAX; + } + break; + case CC_STR_FMT_ARG_STR: + { + wrapped_arg->formatted_length = cc_str_hdr( (void *)wrapped_arg->pointer )->size; + } + break; + case CC_STR_FMT_ARG_MODE_INTEGER_DEC: + case CC_STR_FMT_ARG_MODE_INTEGER_HEX: + case CC_STR_FMT_ARG_MODE_INTEGER_OCT: + { + integer_mode = wrapped_arg->type; + integer_min_digits = (int)wrapped_arg->integer; + } + break; + case CC_STR_FMT_ARG_MODE_FLOATING_DEC: + case CC_STR_FMT_ARG_MODE_FLOATING_HEX: + case CC_STR_FMT_ARG_MODE_FLOATING_SCI: + case CC_STR_FMT_ARG_MODE_FLOATING_SHORTEST: + { + floating_mode = wrapped_arg->type; + floating_precision_or_significant_digits = (int)wrapped_arg->integer; + } + break; + } + + if( wrapped_arg->formatted_length != SIZE_MAX ) // Check for encoding error. + total += wrapped_arg->formatted_length; + + if( wrapped_arg->is_final ) + break; + ++wrapped_arg; + } + + // Make room. + if( cc_str_size( cntr ) + total > cc_str_cap( cntr ) ) + { + size_t cap = cc_str_cap( cntr ); + if( !cap ) + cap = 2; + + while( cap < cc_str_size( cntr ) + total ) + cap *= 2; + + cc_allocing_fn_result_ty result = cc_str_reserve( + cntr, + cap, + el_size, + 0, // Dummy. + NULL, // Dummy. + 0.0, // Dummy. + realloc_, + NULL // Dummy. + ); + if( CC_UNLIKELY( !result.other_ptr ) ) + return result; + + cntr = result.new_cntr; + } + + // Move the latter part of the string backwards. + char *new_els = (char *)cc_str_hdr( cntr )->data + el_size * index; + memmove( + new_els + total * el_size, + new_els, + el_size * ( cc_str_hdr( cntr )->size - index ) + el_size // Terminator. + ); + cc_str_hdr( cntr )->size += total; + + // Perform the insertions. + wrapped_arg = wrapped_args; + integer_mode = CC_STR_FMT_ARG_MODE_INTEGER_DEC; + floating_mode = CC_STR_FMT_ARG_MODE_FLOATING_DEC; + integer_min_digits = 1; + floating_precision_or_significant_digits = 2; + char *cursor = new_els; + while( true ) + { + if( wrapped_arg->formatted_length != SIZE_MAX ) // Check for encoding error. + switch( wrapped_arg->type ) + { + case CC_STR_FMT_ARG_UNSIGNED_INTEGER: + case CC_STR_FMT_ARG_INTEGER: + case CC_STR_FMT_ARG_FLOATING: + case CC_STR_FMT_ARG_VOID_POINTER: + { + // Save the char that snprintf will overwrite with a null terminator. + char overwritten_char = *( cursor + wrapped_arg->formatted_length ); + + // Use snprintf, rather than sprintf, to silence MSVC's intrusive warnings about the latter. + if( wrapped_arg->type == CC_STR_FMT_ARG_UNSIGNED_INTEGER ) + snprintf( + cursor, + wrapped_arg->formatted_length + 1, + integer_mode == CC_STR_FMT_ARG_MODE_INTEGER_DEC ? "%.*llu" : + integer_mode == CC_STR_FMT_ARG_MODE_INTEGER_HEX ? "%.*llx" : + /* CC_STR_FMT_ARG_MODE_INTEGER_OCT */ "%.*llo" , + integer_min_digits, + wrapped_arg->unsigned_integer + ); + else if( wrapped_arg->type == CC_STR_FMT_ARG_INTEGER ) + { + if( integer_mode == CC_STR_FMT_ARG_MODE_INTEGER_DEC ) + snprintf( cursor, wrapped_arg->formatted_length + 1, "%.*lld", integer_min_digits, wrapped_arg->integer ); + else + snprintf( + cursor, + wrapped_arg->formatted_length + 1, + integer_mode == CC_STR_FMT_ARG_MODE_INTEGER_HEX ? "%.*llx" : + /* CC_STR_FMT_ARG_MODE_INTEGER_OCT */ "%.*llo", + integer_min_digits, + (unsigned long long)wrapped_arg->integer + ); + } + else if( wrapped_arg->type == CC_STR_FMT_ARG_FLOATING ) + snprintf( + cursor, + wrapped_arg->formatted_length + 1, + floating_mode == CC_STR_FMT_ARG_MODE_FLOATING_DEC ? "%.*f" : + floating_mode == CC_STR_FMT_ARG_MODE_FLOATING_HEX ? "%.*a" : + floating_mode == CC_STR_FMT_ARG_MODE_FLOATING_SCI ? "%.*e" : + /* CC_STR_FMT_ARG_MODE_FLOATING_SHORTEST */ "%.*g", + floating_precision_or_significant_digits, + wrapped_arg->floating + ); + else // CC_STR_FMT_ARG_VOID_POINTER. + snprintf( cursor, wrapped_arg->formatted_length + 1, "%p", wrapped_arg->pointer ); + + *( cursor + wrapped_arg->formatted_length ) = overwritten_char; // Restore the overwritten char. + + if( el_size == sizeof( char16_t ) ) + cc_snprintf_fixup_char16( cursor, wrapped_arg->formatted_length ); + else if( el_size == sizeof( char32_t ) ) + cc_snprintf_fixup_char32( cursor, wrapped_arg->formatted_length ); + + cursor += wrapped_arg->formatted_length * el_size; + } + break; + case CC_STR_FMT_ARG_C_STRING: + { + memcpy( cursor, wrapped_arg->pointer, wrapped_arg->formatted_length * el_size ); + cursor += wrapped_arg->formatted_length * el_size; + } + break; + case CC_STR_FMT_ARG_STR: + { + memcpy( cursor, cc_str_hdr( (void *)wrapped_arg->pointer ) + 1, wrapped_arg->formatted_length * el_size ); + cursor += wrapped_arg->formatted_length * el_size; + } + break; + case CC_STR_FMT_ARG_MODE_INTEGER_DEC: + case CC_STR_FMT_ARG_MODE_INTEGER_HEX: + case CC_STR_FMT_ARG_MODE_INTEGER_OCT: + { + integer_mode = wrapped_arg->type; + integer_min_digits = (int)wrapped_arg->integer; + } + break; + case CC_STR_FMT_ARG_MODE_FLOATING_DEC: + case CC_STR_FMT_ARG_MODE_FLOATING_HEX: + case CC_STR_FMT_ARG_MODE_FLOATING_SCI: + case CC_STR_FMT_ARG_MODE_FLOATING_SHORTEST: + { + floating_mode = wrapped_arg->type; + floating_precision_or_significant_digits = (int)wrapped_arg->integer; + } + break; + } + + if( wrapped_arg->is_final ) + break; + ++wrapped_arg; + } + + return cc_make_allocing_fn_result( cntr, new_els ); +} + +static inline cc_allocing_fn_result_ty cc_str_push_fmt( + void *cntr, + cc_wrapped_str_fmt_arg_ty *insert_args, + size_t el_size, + cc_realloc_fnptr_ty realloc_ +) +{ + return cc_str_insert_wrapped_fmt_args( cntr, insert_args, cc_str_size( cntr ), el_size, realloc_ ); +} + +static inline cc_allocing_fn_result_ty cc_str_insert_fmt( + void *cntr, + cc_wrapped_str_fmt_arg_ty *insert_args, + size_t index, + size_t el_size, + cc_realloc_fnptr_ty realloc_ +) +{ + return cc_str_insert_wrapped_fmt_args( cntr, insert_args, index, el_size, realloc_ ); +} + +// Erases n elements at the specified index. +// Returns a pointer-iterator to the element after the erased elements, or an end pointer-iterator if there is no +// subsequent element. +static inline void *cc_str_erase_n( + void *cntr, + size_t index, + size_t n, + size_t el_size, + CC_UNUSED( cc_dtor_fnptr_ty, el_dtor ), + CC_UNUSED( cc_free_fnptr_ty, free_ ) +) +{ + if( n == 0 ) + return (char *)cc_str_hdr( cntr )->data + el_size * index; + + memmove( + (char *)cc_str_hdr( cntr )->data + el_size * index, + (char *)cc_str_hdr( cntr )->data + el_size * ( index + n ), + ( cc_str_hdr( cntr )->size - n - index ) * el_size + el_size // Terminator. + ); + + cc_str_hdr( cntr )->size -= n; + return (char *)cc_str_hdr( cntr )->data + el_size * index; +} + +static inline void *cc_str_erase( + void *cntr, + void *key, // Pointer to size_t index. + size_t el_size, + CC_UNUSED( uint64_t, layout ), + CC_UNUSED( cc_hash_fnptr_ty, hash ), + CC_UNUSED( cc_cmpr_fnptr_ty, cmpr ), + CC_UNUSED( cc_dtor_fnptr_ty, el_dtor ), + CC_UNUSED( cc_dtor_fnptr_ty, key_dtor ), + CC_UNUSED( cc_free_fnptr_ty, free_ ) +) +{ + return cc_str_erase_n( cntr, *(size_t *)key, 1, el_size, NULL /* Dummy */, NULL /* Dummy */ ); +} + +// Sets the number of elements in the string. +// If n is above the current size, the new elements are initialized to fill_el. +// Returns a cc_allocing_fn_result_ty containing new container handle and a pointer that evaluates to true if the +// operation was successful or false in the case of allocation failure. +static inline cc_allocing_fn_result_ty cc_str_resize( + void *cntr, + size_t n, + void *fill_el, + size_t el_size, + CC_UNUSED( cc_dtor_fnptr_ty, el_dtor ), + cc_realloc_fnptr_ty realloc_ +) +{ + // No resize necessary (also handles placeholder). + if( n == cc_str_size( cntr ) ) + return cc_make_allocing_fn_result( cntr, cc_dummy_true_ptr ); + + // Downsizing. + if( n < cc_str_size( cntr ) ) + { + cc_str_erase_n( cntr, n, cc_str_size( cntr ) - n, el_size, NULL /* Dummy */, NULL /* Dummy */ ); + return cc_make_allocing_fn_result( cntr, cc_dummy_true_ptr ); + } + + // Up-sizing. + cc_allocing_fn_result_ty result = cc_str_reserve( + cntr, + n, + el_size, + 0, // Dummy. + NULL, // Dummy. + 0.0, // Dummy. + realloc_, + NULL // Dummy. + ); + if( CC_UNLIKELY( !result.other_ptr ) ) + return result; + + char *new_el = (char *)cc_str_hdr( result.new_cntr )->data + el_size * cc_str_size( result.new_cntr ); + for( size_t i = cc_str_hdr( result.new_cntr )->size; i < n; ++i, new_el += el_size ) + memcpy( new_el, fill_el, el_size ); + + memset( new_el, 0, el_size ); // Terminator. + + cc_str_hdr( result.new_cntr )->size = n; + + return result; +} + +// Erases all elements. +static inline void cc_str_clear( + void *cntr, + size_t el_size, + CC_UNUSED( uint64_t, layout ), + CC_UNUSED( cc_dtor_fnptr_ty, el_dtor ), + CC_UNUSED( cc_dtor_fnptr_ty, key_dtor ), + CC_UNUSED( cc_free_fnptr_ty, free_ ) +) +{ + if( !cc_str_is_placeholder( cntr ) ) + { + cc_str_hdr( cntr )->size = 0; + memset( cc_str_hdr( cntr )->data, 0, el_size ); // Terminator. + } +} + +// Shrinks the string's capacity to its current size. +// Returns a cc_allocing_fn_result_ty containing the new container handle and a pointer that evaluates to true if the +// operation was successful or false in the case of allocation failure. +static inline cc_allocing_fn_result_ty cc_str_shrink( + void *cntr, + size_t el_size, + CC_UNUSED( uint64_t, layout ), + CC_UNUSED( cc_hash_fnptr_ty, hash ), + CC_UNUSED( double, max_load ), + cc_realloc_fnptr_ty realloc_, + cc_free_fnptr_ty free_ +) +{ + if( cc_str_size( cntr ) == cc_str_cap( cntr ) ) // Also handles placeholder. + return cc_make_allocing_fn_result( cntr, cc_dummy_true_ptr ); + + if( cc_str_size( cntr ) == 0 ) + { + // Restore placeholder. + free_( cntr ); + return cc_make_allocing_fn_result( + el_size == sizeof( char ) ? (void *)&cc_str_placeholder_char8 : + el_size == sizeof( char16_t ) ? (void *)&cc_str_placeholder_char16 : + /* char32_t */ (void *)&cc_str_placeholder_char32, + cc_dummy_true_ptr + ); + } + + cc_str_hdr_ty *new_cntr = (cc_str_hdr_ty *)realloc_( + cntr, sizeof( cc_str_hdr_ty ) + el_size * cc_str_size( cntr ) + el_size // Terminator. + ); + if( CC_UNLIKELY( !new_cntr ) ) + return cc_make_allocing_fn_result( cntr, NULL ); + + cc_str_hdr( new_cntr )->cap = cc_str_size( new_cntr ); + cc_str_hdr( new_cntr )->data = new_cntr + 1; + return cc_make_allocing_fn_result( new_cntr, cc_dummy_true_ptr ); +} + +// Initializes a shallow copy of the source string. +// The capacity of the new string is the size of the source string, not its capacity. +// Returns a pointer to the copy, or NULL in the case of allocation failure. +// The return value is cast to bool in the corresponding macro. +static inline void *cc_str_init_clone( + void *src, + size_t el_size, + CC_UNUSED( uint64_t, layout ), + cc_realloc_fnptr_ty realloc_, + CC_UNUSED( cc_free_fnptr_ty, free_ ) +) +{ + if( cc_str_size( src ) == 0 ) + return + el_size == sizeof( char ) ? (void *)&cc_str_placeholder_char8 : + el_size == sizeof( char16_t ) ? (void *)&cc_str_placeholder_char16 : + /* char32_t */ (void *)&cc_str_placeholder_char32; + + cc_allocing_fn_result_ty result = cc_str_reserve( + el_size == sizeof( char ) ? (void *)&cc_str_placeholder_char8 : + el_size == sizeof( char16_t ) ? (void *)&cc_str_placeholder_char16 : + /* char32_t */ (void *)&cc_str_placeholder_char32, + cc_str_size( src ), + el_size, + 0, // Dummy. + NULL, // Dummy. + 0.0, // Dummy. + realloc_, + NULL // Dummy. + ); + + if( CC_UNLIKELY( !result.other_ptr ) ) + return NULL; + + memcpy( + (char *)cc_str_hdr( result.new_cntr )->data, + (char *)cc_str_hdr( src )->data, + el_size * cc_str_size( src ) + el_size // Terminator. + ); + + cc_str_hdr( result.new_cntr )->size = cc_str_size( src ); + + return result.new_cntr; +} + +// Clears the string and frees its memory if it is not a placeholder. +static inline void cc_str_cleanup( + void *cntr, + CC_UNUSED( size_t, el_size ), + CC_UNUSED( uint64_t, layout ), + CC_UNUSED( cc_dtor_fnptr_ty, el_dtor ), + CC_UNUSED( cc_dtor_fnptr_ty, key_dtor ), + cc_free_fnptr_ty free_ +) +{ + if( !cc_str_is_placeholder( cntr ) ) + free_( cntr ); +} + +static inline void *cc_str_end( + void *cntr, + size_t el_size, + CC_UNUSED( uint64_t, layout ) +) +{ + return (char *)cc_str_hdr( cntr )->data + el_size * cc_str_size( cntr ); +} + +static inline void *cc_str_next( + CC_UNUSED( void *, cntr ), + void *itr, + size_t el_size, + CC_UNUSED( uint64_t, layout ) +) +{ + return (char *)itr + el_size; +} + +static inline void *cc_str_first( + void *cntr, + CC_UNUSED( size_t, el_size ), + CC_UNUSED( uint64_t, layout ) +) +{ + return (char *)cc_str_hdr( cntr )->data; +} + +static inline void *cc_str_last( + void *cntr, + size_t el_size, + CC_UNUSED( uint64_t, layout ) +) +{ + return (char *)cc_str_hdr( cntr )->data + el_size * ( cc_str_size( cntr ) - 1 ); +} + +// Functions for converting a C string into a cc_str for heterogeneous insertion. +// In the case of memory allocation failure, these functions return NULL. + +static inline void *cc_cstring_to_str_char8( const void *cstring, cc_realloc_fnptr_ty realloc_ ) +{ + size_t length = strlen( (const char *)cstring ); + if( !length ) + return (void *)&cc_str_placeholder_char8; + + cc_str_hdr_ty *hdr = (cc_str_hdr_ty *)realloc_( NULL, sizeof( cc_str_hdr_ty ) + length + 1 /* Terminator */ ); + if( !hdr ) + return NULL; + + hdr->cap = length; + hdr->size = length; + hdr->data = hdr + 1; + memcpy( hdr->data, cstring, length + 1 ); + return hdr; +} + +static inline CC_STR_RAW( char16_t ) cc_cstring_to_str_char16( const char16_t *cstring, cc_realloc_fnptr_ty realloc_ ) +{ + size_t length = cc_strlen_char16( cstring ); + if( !length ) + return (CC_STR_RAW( char16_t ))&cc_str_placeholder_char8; + + cc_str_hdr_ty *hdr = (cc_str_hdr_ty *)realloc_( + NULL, + sizeof( cc_str_hdr_ty ) + sizeof( char16_t ) * length + sizeof( char16_t ) // Terminator. + ); + if( !hdr ) + return NULL; + + hdr->cap = length; + hdr->size = length; + hdr->data = hdr + 1; + memcpy( hdr->data, cstring, sizeof( char16_t ) * length + sizeof( char16_t ) ); + return (CC_STR_RAW( char16_t ))hdr; +} + +static inline CC_STR_RAW( char32_t ) cc_cstring_to_str_char32( const char32_t *cstring, cc_realloc_fnptr_ty realloc_ ) +{ + size_t length = cc_strlen_char32( cstring ); + if( !length ) + return (CC_STR_RAW( char32_t ))&cc_str_placeholder_char8; + + cc_str_hdr_ty *hdr = (cc_str_hdr_ty *)realloc_( + NULL, + sizeof( cc_str_hdr_ty ) + sizeof( char32_t ) * length + sizeof( char32_t ) // Terminator. + ); + if( !hdr ) + return NULL; + + hdr->cap = length; + hdr->size = length; + hdr->data = hdr + 1; + memcpy( hdr->data, cstring, sizeof( char32_t ) * length + sizeof( char32_t ) ); + return (CC_STR_RAW( char32_t ))hdr; +} + +// Functions for converting a C string into a temporary cc_str for heterogeneous lookup. +// These functions take a second argument, namely a pointer to a stack-allocated header to use as the cc_str. + +static inline void *cc_cstring_to_temp_str_char8( const void *cstring, cc_str_hdr_ty *str_hdr ) +{ + str_hdr->size = strlen( (const char *)cstring ); + str_hdr->cap = str_hdr->size; + str_hdr->data = (void *)cstring; + return str_hdr; +} + +static inline CC_STR_RAW( char16_t ) cc_cstring_to_temp_str_char16( const char16_t *cstring, cc_str_hdr_ty *str_hdr ) +{ + str_hdr->size = cc_strlen_char16( cstring ); + str_hdr->cap = str_hdr->size; + str_hdr->data = (void *)cstring; + return (CC_STR_RAW( char16_t ))str_hdr; +} + +static inline CC_STR_RAW( char32_t ) cc_cstring_to_temp_str_char32( const char32_t *cstring, cc_str_hdr_ty *str_hdr ) +{ + str_hdr->size = cc_strlen_char32( cstring ); + str_hdr->cap = str_hdr->size; + str_hdr->data = (void *)cstring; + return (CC_STR_RAW( char32_t ))str_hdr; +} + /*--------------------------------------------------------------------------------------------------------------------*/ /* API */ /*--------------------------------------------------------------------------------------------------------------------*/ -#define cc_init( cntr ) \ -( \ - CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ - CC_STATIC_ASSERT( \ - CC_CNTR_ID( *(cntr) ) == CC_VEC || \ - CC_CNTR_ID( *(cntr) ) == CC_LIST || \ - CC_CNTR_ID( *(cntr) ) == CC_MAP || \ - CC_CNTR_ID( *(cntr) ) == CC_SET || \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ - ), \ - *(cntr) = ( \ - CC_CNTR_ID( *(cntr) ) == CC_VEC ? (CC_TYPEOF_XP( *(cntr) ))&cc_vec_placeholder : \ - CC_CNTR_ID( *(cntr) ) == CC_LIST ? (CC_TYPEOF_XP( *(cntr) ))&cc_list_placeholder : \ - CC_CNTR_ID( *(cntr) ) == CC_MAP ? (CC_TYPEOF_XP( *(cntr) ))&cc_map_placeholder : \ - CC_CNTR_ID( *(cntr) ) == CC_SET ? (CC_TYPEOF_XP( *(cntr) ))&cc_map_placeholder : \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP ? (CC_TYPEOF_XP( *(cntr) ))&cc_omap_placeholder : \ - /* CC_OSET */ (CC_TYPEOF_XP( *(cntr) ))&cc_omap_placeholder \ - ), \ - (void)0 \ -) \ +#define cc_initialized( cntr ) \ +( \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_VEC || \ + CC_CNTR_ID( *(cntr) ) == CC_LIST || \ + CC_CNTR_ID( *(cntr) ) == CC_MAP || \ + CC_CNTR_ID( *(cntr) ) == CC_SET || \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ + CC_CNTR_ID( *(cntr) ) == CC_OSET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ + ), \ + CC_CNTR_ID( *(cntr) ) == CC_VEC ? (CC_TYPEOF_XP( *(cntr) ))&cc_vec_placeholder : \ + CC_CNTR_ID( *(cntr) ) == CC_LIST ? (CC_TYPEOF_XP( *(cntr) ))&cc_list_placeholder : \ + CC_CNTR_ID( *(cntr) ) == CC_MAP ? (CC_TYPEOF_XP( *(cntr) ))&cc_map_placeholder : \ + CC_CNTR_ID( *(cntr) ) == CC_SET ? (CC_TYPEOF_XP( *(cntr) ))&cc_map_placeholder : \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP ? (CC_TYPEOF_XP( *(cntr) ))&cc_omap_placeholder : \ + CC_CNTR_ID( *(cntr) ) == CC_OSET ? (CC_TYPEOF_XP( *(cntr) ))&cc_omap_placeholder : \ + /* CC_STR */ (CC_TYPEOF_XP( *(cntr) ))CC_STR_PLACEHOLDER( CC_EL_TY( *(cntr) ) ) \ +) \ + +#define cc_init( cntr ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + *(cntr) = cc_initialized( (cntr) ), \ + (void)0 \ +) \ #define cc_size( cntr ) \ ( \ @@ -4640,7 +6471,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP || \ CC_CNTR_ID( *(cntr) ) == CC_SET || \ CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ + CC_CNTR_ID( *(cntr) ) == CC_OSET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ /* Function select */ \ ( \ @@ -4649,7 +6481,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_size : \ CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_size : \ CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_size : \ - /* CC_OSET */ cc_oset_size \ + CC_CNTR_ID( *(cntr) ) == CC_OSET ? cc_oset_size : \ + /* CC_STR */ cc_str_size \ ) \ /* Function arguments */ \ ( \ @@ -4663,13 +6496,15 @@ static inline void *cc_oset_next( CC_STATIC_ASSERT( \ CC_CNTR_ID( *(cntr) ) == CC_VEC || \ CC_CNTR_ID( *(cntr) ) == CC_MAP || \ - CC_CNTR_ID( *(cntr) ) == CC_SET \ + CC_CNTR_ID( *(cntr) ) == CC_SET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ /* Function select */ \ ( \ CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_cap : \ CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_cap : \ - /* CC_SET */ cc_set_cap \ + CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_cap : \ + /* CC_STR */ cc_str_cap \ ) \ /* Function arguments */ \ ( \ @@ -4683,7 +6518,8 @@ static inline void *cc_oset_next( CC_STATIC_ASSERT( \ CC_CNTR_ID( *(cntr) ) == CC_VEC || \ CC_CNTR_ID( *(cntr) ) == CC_MAP || \ - CC_CNTR_ID( *(cntr) ) == CC_SET \ + CC_CNTR_ID( *(cntr) ) == CC_SET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ *(cntr), \ @@ -4691,7 +6527,8 @@ static inline void *cc_oset_next( ( \ CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_reserve : \ CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_reserve : \ - /* CC_SET */ cc_set_reserve \ + CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_reserve : \ + /* CC_STR */ cc_str_reserve \ ) \ /* Function arguments */ \ ( \ @@ -4708,106 +6545,124 @@ static inline void *cc_oset_next( CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ ) \ -#define cc_insert( ... ) CC_SELECT_ON_NUM_ARGS( cc_insert, __VA_ARGS__ ) +#define cc_insert( ... ) CC_SELECT_ON_NUM_ARGS( cc_insert_, __VA_ARGS__ ) -#define cc_insert_2( cntr, key ) \ -( \ - CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ - CC_STATIC_ASSERT( \ - CC_CNTR_ID( *(cntr) ) == CC_SET || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ - ), \ - CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ - *(cntr), \ - /* Function select */ \ - ( \ - CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_insert : \ - /* CC_OSET */ cc_oset_insert \ - ) \ - /* Function arguments */ \ - ( \ - *(cntr), \ - &CC_MAKE_LVAL_COPY( CC_KEY_TY( *(cntr) ), (key) ), \ - true, \ - CC_LAYOUT( *(cntr) ), \ - CC_KEY_HASH( *(cntr) ), \ - CC_KEY_CMPR( *(cntr) ), \ - CC_KEY_LOAD( *(cntr) ), \ - CC_EL_DTOR( *(cntr) ), \ - CC_REALLOC_FN, \ - CC_FREE_FN \ - ) \ - ), \ - CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ -) \ +#define cc_insert_2( cntr, el ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_SET || \ + CC_CNTR_ID( *(cntr) ) == CC_OSET \ + ), \ + CC_POINT_HNDL_TO_HETEROINSERT_HELPER_EL_ONLY( *(cntr), CC_EL_CONVERTED_FOR_HETEROINSERT( *(cntr), (el) ) ), \ + !CC_CHECK_HETEROINSERT_EL_CONVERSION( \ + *(cntr), \ + (el), \ + ( (cc_heterinsert_helper_el_only_ty *)*(cntr) )->converted_el \ + ) || \ + !( ( (cc_heterinsert_helper_el_only_ty *)*(cntr) )->result = \ + /* Function select */ \ + ( \ + CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_insert : \ + /* CC_OSET */ cc_oset_insert \ + ) \ + /* Function arguments */ \ + ( \ + ( (cc_heterinsert_helper_el_only_ty *)*(cntr) )->result.new_cntr, \ + ( (cc_heterinsert_helper_el_only_ty *)*(cntr) )->converted_el, \ + true, \ + CC_LAYOUT( *(cntr) ), \ + CC_KEY_HASH( *(cntr) ), \ + CC_KEY_CMPR( *(cntr) ), \ + CC_KEY_LOAD( *(cntr) ), \ + CC_EL_DTOR( *(cntr) ), \ + CC_REALLOC_FN, \ + CC_FREE_FN \ + ) \ + ).other_ptr ? \ + CC_CLEANUP_HETEROINSERT_EL( *(cntr), (el), ( (cc_heterinsert_helper_el_only_ty *)*(cntr) )->converted_el ) \ + : (void)0, \ + CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ +) \ -#define cc_insert_3( cntr, key, el ) \ -( \ - CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ - CC_STATIC_ASSERT( \ - CC_CNTR_ID( *(cntr) ) == CC_VEC || \ - CC_CNTR_ID( *(cntr) ) == CC_LIST || \ - CC_CNTR_ID( *(cntr) ) == CC_MAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP \ - ), \ - CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ - *(cntr), \ - /* Function select */ \ - ( \ - CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_insert : \ - CC_CNTR_ID( *(cntr) ) == CC_LIST ? cc_list_insert : \ - CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_insert : \ - /* CC_OMAP */ cc_omap_insert \ - ) \ - /* Function arguments */ \ - ( \ - *(cntr), \ - &CC_MAKE_LVAL_COPY( CC_EL_TY( *(cntr) ), (el) ), \ - &CC_MAKE_LVAL_COPY( CC_KEY_TY( *(cntr) ), (key) ), \ - true, \ - CC_EL_SIZE( *(cntr) ), \ - CC_LAYOUT( *(cntr) ), \ - CC_KEY_HASH( *(cntr) ), \ - CC_KEY_CMPR( *(cntr) ), \ - CC_KEY_LOAD( *(cntr) ), \ - CC_EL_DTOR( *(cntr) ), \ - CC_KEY_DTOR( *(cntr) ), \ - CC_REALLOC_FN, \ - CC_FREE_FN \ - ) \ - ), \ - CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ -) \ +#define cc_insert_3( cntr, key, el ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_VEC || \ + CC_CNTR_ID( *(cntr) ) == CC_LIST || \ + CC_CNTR_ID( *(cntr) ) == CC_MAP || \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ + ), \ + CC_POINT_HNDL_TO_HETEROINSERT_HELPER_EL_KEY( \ + *(cntr), \ + CC_EL_CONVERTED_FOR_HETEROINSERT( *(cntr), (el) ), \ + CC_KEY_CONVERTED_FOR_HETEROINSERT( *(cntr), (key) ) \ + ), \ + !CC_CHECK_HETEROINSERT_EL_CONVERSION( \ + *(cntr), \ + (el), \ + ( (cc_heteroinsert_helper_el_key_ty *)*(cntr) )->converted_el \ + ) || \ + !CC_CHECK_HETEROINSERT_KEY_CONVERSION( \ + *(cntr), \ + (key), \ + ( (cc_heteroinsert_helper_el_key_ty *)*(cntr) )->converted_key \ + ) || \ + !( ( (cc_heteroinsert_helper_el_key_ty *)*(cntr) )->result = \ + /* Function select */ \ + ( \ + CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_insert : \ + CC_CNTR_ID( *(cntr) ) == CC_LIST ? cc_list_insert : \ + CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_insert : \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_insert : \ + /* CC_STR */ cc_str_insert \ + ) \ + /* Function arguments */ \ + ( \ + ( (cc_heteroinsert_helper_el_key_ty *)*(cntr) )->result.new_cntr, \ + ( (cc_heteroinsert_helper_el_key_ty *)*(cntr) )->converted_el, \ + ( (cc_heteroinsert_helper_el_key_ty *)*(cntr) )->converted_key, \ + true, \ + CC_EL_SIZE( *(cntr) ), \ + CC_LAYOUT( *(cntr) ), \ + CC_KEY_HASH( *(cntr) ), \ + CC_KEY_CMPR( *(cntr) ), \ + CC_KEY_LOAD( *(cntr) ), \ + CC_EL_DTOR( *(cntr) ), \ + CC_KEY_DTOR( *(cntr) ), \ + CC_REALLOC_FN, \ + CC_FREE_FN \ + ) \ + ).other_ptr ? \ + ( \ + CC_CLEANUP_HETEROINSERT_EL( *(cntr), (el), ( (cc_heteroinsert_helper_el_key_ty *)*(cntr) )->converted_el ), \ + CC_CLEANUP_HETEROINSERT_KEY( *(cntr), (key), ( (cc_heteroinsert_helper_el_key_ty *)*(cntr) )->converted_key ) \ + ) : (void)0, \ + CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ +) \ #define cc_insert_n( cntr, index, els, n ) \ -( \ - CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ - CC_STATIC_ASSERT( CC_CNTR_ID( *(cntr) ) == CC_VEC ), \ - CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ - *(cntr), \ - cc_vec_insert_n( *(cntr), (index), (els), (n), CC_EL_SIZE( *(cntr) ), CC_REALLOC_FN ) \ - ), \ - CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ -) \ - -#define cc_push( cntr, el ) \ ( \ CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ CC_STATIC_ASSERT( \ - CC_CNTR_ID( *(cntr) ) == CC_VEC || \ - CC_CNTR_ID( *(cntr) ) == CC_LIST \ + CC_CNTR_ID( *(cntr) ) == CC_VEC || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ *(cntr), \ /* Function select */ \ ( \ - CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_push : \ - /* CC_LIST */ cc_list_push \ + CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_insert_n : \ + /* CC_STR */ cc_str_insert_n \ ) \ /* Function arguments */ \ ( \ *(cntr), \ - &CC_MAKE_LVAL_COPY( CC_EL_TY( *(cntr) ), (el) ), \ + (index), \ + CC_CHECKED_EL_TY_PTR( *(cntr), (els) ), \ + (n), \ CC_EL_SIZE( *(cntr) ), \ CC_REALLOC_FN \ ) \ @@ -4815,50 +6670,151 @@ static inline void *cc_oset_next( CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ ) \ -#define cc_push_n( cntr, els, n ) \ +#define cc_insert_fmt( cntr, index, ... ) \ ( \ CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ - CC_STATIC_ASSERT( CC_CNTR_ID( *(cntr) ) == CC_VEC ), \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ + ), \ CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ *(cntr), \ - cc_vec_push_n( *(cntr), (els), (n), CC_EL_SIZE( *(cntr) ), CC_REALLOC_FN ) \ + cc_str_insert_fmt( \ + *(cntr), \ + CC_WRAPPED_STR_FMT_ARGS_ARRAY( CC_EL_TY( *(cntr) ), __VA_ARGS__ ), \ + index, \ + CC_EL_SIZE( *(cntr) ), \ + CC_REALLOC_FN \ + ) \ ), \ CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ ) \ -#define cc_get_or_insert( ... ) CC_SELECT_ON_NUM_ARGS( cc_get_or_insert, __VA_ARGS__ ) +#define cc_push( cntr, el ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_VEC || \ + CC_CNTR_ID( *(cntr) ) == CC_LIST || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ + ), \ + CC_POINT_HNDL_TO_HETEROINSERT_HELPER_EL_ONLY( *(cntr), CC_EL_CONVERTED_FOR_HETEROINSERT( *(cntr), (el) ) ), \ + !CC_CHECK_HETEROINSERT_EL_CONVERSION( \ + *(cntr), \ + (el), \ + ( (cc_heterinsert_helper_el_only_ty *)*(cntr) )->converted_el \ + ) || \ + !( ( (cc_heterinsert_helper_el_only_ty *)*(cntr) )->result = \ + /* Function select */ \ + ( \ + CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_push : \ + CC_CNTR_ID( *(cntr) ) == CC_LIST ? cc_list_push : \ + /* CC_STR */ cc_str_push \ + ) \ + /* Function arguments */ \ + ( \ + ( (cc_heterinsert_helper_el_only_ty *)*(cntr) )->result.new_cntr, \ + ( (cc_heterinsert_helper_el_only_ty *)*(cntr) )->converted_el, \ + CC_EL_SIZE( *(cntr) ), \ + CC_REALLOC_FN \ + ) \ + ).other_ptr ? \ + CC_CLEANUP_HETEROINSERT_EL( *(cntr), (el), ( (cc_heterinsert_helper_el_only_ty *)*(cntr) )->converted_el ) \ + : (void)0, \ + CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ +) \ -#define cc_get_or_insert_2( cntr, key ) \ +#define cc_push_n( cntr, els, n ) \ ( \ CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ CC_STATIC_ASSERT( \ - CC_CNTR_ID( *(cntr) ) == CC_SET || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ + CC_CNTR_ID( *(cntr) ) == CC_VEC || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ *(cntr), \ /* Function select */ \ ( \ - CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_insert : \ - /* CC_OSET */ cc_oset_insert \ + CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_push_n : \ + /* CC_STR */ cc_str_push_n \ ) \ /* Function arguments */ \ ( \ *(cntr), \ - &CC_MAKE_LVAL_COPY( CC_KEY_TY( *(cntr) ), (key) ), \ - false, \ - CC_LAYOUT( *(cntr) ), \ - CC_KEY_HASH( *(cntr) ), \ - CC_KEY_CMPR( *(cntr) ), \ - CC_KEY_LOAD( *(cntr) ), \ - CC_EL_DTOR( *(cntr) ), \ - CC_REALLOC_FN, \ - CC_FREE_FN \ + CC_CHECKED_EL_TY_PTR( *(cntr), (els) ), \ + (n), \ + CC_EL_SIZE( *(cntr) ), \ + CC_REALLOC_FN \ ) \ ), \ CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ ) \ +#define cc_push_fmt( cntr, ... ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ + ), \ + CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ + *(cntr), \ + cc_str_push_fmt( \ + *(cntr), \ + CC_WRAPPED_STR_FMT_ARGS_ARRAY( CC_EL_TY( *(cntr) ), __VA_ARGS__ ), \ + CC_EL_SIZE( *(cntr) ), \ + CC_REALLOC_FN \ + ) \ + ), \ + CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ +) \ + +#define cc_get_or_insert( ... ) CC_SELECT_ON_NUM_ARGS( cc_get_or_insert_, __VA_ARGS__ ) + +#define cc_get_or_insert_2( cntr, el ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_SET || \ + CC_CNTR_ID( *(cntr) ) == CC_OSET \ + ), \ + CC_POINT_HNDL_TO_HETEROINSERT_HELPER_EL_AND_CNTR_SIZE( \ + *(cntr), \ + CC_EL_CONVERTED_FOR_HETEROINSERT( *(cntr), (el) ), \ + ( CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_size : cc_oset_size )( *(cntr) ) \ + ), \ + \ + !CC_CHECK_HETEROINSERT_EL_CONVERSION( \ + *(cntr), \ + (el), \ + ( (cc_heteroinsert_helper_el_cntr_size_ty *)*(cntr) )->converted_el \ + ) || \ + ( CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_size : cc_oset_size )( \ + ( \ + ( (cc_heteroinsert_helper_el_cntr_size_ty *)*(cntr) )->result = \ + /* Function select */ \ + ( \ + CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_insert : \ + /* CC_OSET */ cc_oset_insert \ + ) \ + /* Function arguments */ \ + ( \ + ( (cc_heteroinsert_helper_el_cntr_size_ty *)*(cntr) )->result.new_cntr, \ + ( (cc_heteroinsert_helper_el_cntr_size_ty *)*(cntr) )->converted_el, \ + false, \ + CC_LAYOUT( *(cntr) ), \ + CC_KEY_HASH( *(cntr) ), \ + CC_KEY_CMPR( *(cntr) ), \ + CC_KEY_LOAD( *(cntr) ), \ + CC_EL_DTOR( *(cntr) ), \ + CC_REALLOC_FN, \ + CC_FREE_FN \ + ) \ + ).new_cntr \ + ) == ( (cc_heteroinsert_helper_el_cntr_size_ty *)*(cntr) )->cntr_size ? \ + CC_CLEANUP_HETEROINSERT_EL( *(cntr), (el), ( (cc_heteroinsert_helper_el_cntr_size_ty *)*(cntr) )->converted_el ) \ + : (void)0, \ + CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ +) \ + #define cc_get_or_insert_3( cntr, key, el ) \ ( \ CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ @@ -4866,64 +6822,96 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP || \ CC_CNTR_ID( *(cntr) ) == CC_OMAP \ ), \ - CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ + CC_POINT_HNDL_TO_HETEROINSERT_HELPER_EL_KEY_CNTR_SIZE( \ *(cntr), \ - /* Function select */ \ - ( \ - CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_insert : \ - /* CC_OMAP */ cc_omap_insert \ - ) \ - /* Function arguments */ \ - ( \ - *(cntr), \ - &CC_MAKE_LVAL_COPY( CC_EL_TY( *(cntr) ), (el) ), \ - &CC_MAKE_LVAL_COPY( CC_KEY_TY( *(cntr) ), (key) ), \ - false, \ - CC_EL_SIZE( *(cntr) ), \ - CC_LAYOUT( *(cntr) ), \ - CC_KEY_HASH( *(cntr) ), \ - CC_KEY_CMPR( *(cntr) ), \ - CC_KEY_LOAD( *(cntr) ), \ - CC_EL_DTOR( *(cntr) ), \ - CC_KEY_DTOR( *(cntr) ), \ - CC_REALLOC_FN, \ - CC_FREE_FN \ - ) \ + CC_EL_CONVERTED_FOR_HETEROINSERT( *(cntr), (el) ), \ + CC_KEY_CONVERTED_FOR_HETEROINSERT( *(cntr), (key) ), \ + ( CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_size : cc_omap_size )( *(cntr) ) \ ), \ + !CC_CHECK_HETEROINSERT_EL_CONVERSION( \ + *(cntr), \ + (el), \ + ( (cc_heteroinsert_helper_el_key_cntr_size_ty *)*(cntr) )->converted_el \ + ) || \ + !CC_CHECK_HETEROINSERT_KEY_CONVERSION( \ + *(cntr), \ + (key), \ + ( (cc_heteroinsert_helper_el_key_cntr_size_ty *)*(cntr) )->converted_key \ + ) || \ + ( CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_size : cc_omap_size )( \ + ( \ + ( (cc_heteroinsert_helper_el_key_cntr_size_ty *)*(cntr) )->result = \ + /* Function select */ \ + ( \ + CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_insert : \ + /* CC_OMAP */ cc_omap_insert \ + ) \ + /* Function arguments */ \ + ( \ + ( (cc_heteroinsert_helper_el_key_cntr_size_ty *)*(cntr) )->result.new_cntr, \ + ( (cc_heteroinsert_helper_el_key_cntr_size_ty *)*(cntr) )->converted_el, \ + ( (cc_heteroinsert_helper_el_key_cntr_size_ty *)*(cntr) )->converted_key, \ + false, \ + CC_EL_SIZE( *(cntr) ), \ + CC_LAYOUT( *(cntr) ), \ + CC_KEY_HASH( *(cntr) ), \ + CC_KEY_CMPR( *(cntr) ), \ + CC_KEY_LOAD( *(cntr) ), \ + CC_EL_DTOR( *(cntr) ), \ + CC_KEY_DTOR( *(cntr) ), \ + CC_REALLOC_FN, \ + CC_FREE_FN \ + ) \ + ).new_cntr \ + ) == ( (cc_heteroinsert_helper_el_key_cntr_size_ty *)*(cntr) )->cntr_size ? \ + ( \ + CC_CLEANUP_HETEROINSERT_EL( \ + *(cntr), \ + (el), \ + ( (cc_heteroinsert_helper_el_key_cntr_size_ty *)*(cntr) )->converted_el \ + ), \ + CC_CLEANUP_HETEROINSERT_KEY( \ + *(cntr), \ + (key), \ + ( (cc_heteroinsert_helper_el_key_cntr_size_ty *)*(cntr) )->converted_key \ + ) \ + ) : (void)0, \ CC_CAST_MAYBE_UNUSED( CC_EL_TY( *(cntr) ) *, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ ) \ -#define cc_get( cntr, key ) \ -( \ - CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ - CC_STATIC_ASSERT( \ - CC_CNTR_ID( *(cntr) ) == CC_VEC || \ - CC_CNTR_ID( *(cntr) ) == CC_MAP || \ - CC_CNTR_ID( *(cntr) ) == CC_SET || \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ - ), \ - CC_CAST_MAYBE_UNUSED( \ - CC_EL_TY( *(cntr) ) *, \ - /* Function select */ \ - ( \ - CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_get : \ - CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_get : \ - CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_get : \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_get : \ - /* CC_OSET */ cc_oset_get \ - ) \ - /* Function arguments */ \ - ( \ - *(cntr), \ - &CC_MAKE_LVAL_COPY( CC_KEY_TY( *(cntr) ), (key) ), \ - CC_EL_SIZE( *(cntr) ), \ - CC_LAYOUT( *(cntr) ), \ - CC_KEY_HASH( *(cntr) ), \ - CC_KEY_CMPR( *(cntr) ) \ - ) \ - ) \ -) \ +#define cc_get( cntr, key ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_VEC || \ + CC_CNTR_ID( *(cntr) ) == CC_MAP || \ + CC_CNTR_ID( *(cntr) ) == CC_SET || \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ + CC_CNTR_ID( *(cntr) ) == CC_OSET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ + ), \ + CC_CAST_MAYBE_UNUSED( \ + CC_EL_TY( *(cntr) ) *, \ + /* Function select */ \ + ( \ + CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_get : \ + CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_get : \ + CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_get : \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_get : \ + CC_CNTR_ID( *(cntr) ) == CC_OSET ? cc_oset_get : \ + /* CC_STR */ cc_str_get \ + ) \ + /* Function arguments */ \ + ( \ + *(cntr), \ + &CC_MAKE_LVAL_COPY( CC_KEY_TY( *(cntr) ), CC_KEY_CONVERTED_FOR_HETEROLOOKUP( *(cntr), (key) ) ), \ + CC_EL_SIZE( *(cntr) ), \ + CC_LAYOUT( *(cntr) ), \ + CC_KEY_HASH( *(cntr) ), \ + CC_KEY_CMPR( *(cntr) ) \ + ) \ + ) \ +) \ #define cc_key_for( cntr, itr ) \ ( \ @@ -4948,95 +6936,113 @@ static inline void *cc_oset_next( ) \ ) \ -#define cc_erase( cntr, key ) \ -( \ - CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ - CC_STATIC_ASSERT( \ - CC_CNTR_ID( *(cntr) ) == CC_VEC || \ - CC_CNTR_ID( *(cntr) ) == CC_LIST || \ - CC_CNTR_ID( *(cntr) ) == CC_MAP || \ - CC_CNTR_ID( *(cntr) ) == CC_SET || \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ - ), \ - CC_IF_THEN_CAST_TY_1_ELSE_CAST_TY_2( \ - CC_CNTR_ID( *(cntr) ) == CC_MAP || \ - CC_CNTR_ID( *(cntr) ) == CC_SET || \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET, \ - bool, \ - CC_EL_TY( *(cntr) ) *, \ - /* Function select */ \ - ( \ - CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_erase : \ - CC_CNTR_ID( *(cntr) ) == CC_LIST ? cc_list_erase : \ - CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_erase : \ - CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_erase : \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_erase : \ - /* CC_OSET */ cc_oset_erase \ - ) \ - /* Function arguments */ \ - ( \ - *(cntr), \ - &CC_MAKE_LVAL_COPY( CC_KEY_TY( *(cntr) ), (key) ), \ - CC_EL_SIZE( *(cntr) ), \ - CC_LAYOUT( *(cntr) ), \ - CC_KEY_HASH( *(cntr) ), \ - CC_KEY_CMPR( *(cntr) ), \ - CC_EL_DTOR( *(cntr) ), \ - CC_KEY_DTOR( *(cntr) ), \ - CC_FREE_FN \ - ) \ - ) \ -) \ +#define cc_erase( cntr, key ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_VEC || \ + CC_CNTR_ID( *(cntr) ) == CC_LIST || \ + CC_CNTR_ID( *(cntr) ) == CC_MAP || \ + CC_CNTR_ID( *(cntr) ) == CC_SET || \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ + CC_CNTR_ID( *(cntr) ) == CC_OSET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ + ), \ + CC_IF_THEN_CAST_TY_1_ELSE_CAST_TY_2( \ + CC_CNTR_ID( *(cntr) ) == CC_MAP || \ + CC_CNTR_ID( *(cntr) ) == CC_SET || \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ + CC_CNTR_ID( *(cntr) ) == CC_OSET, \ + bool, \ + CC_EL_TY( *(cntr) ) *, \ + /* Function select */ \ + ( \ + CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_erase : \ + CC_CNTR_ID( *(cntr) ) == CC_LIST ? cc_list_erase : \ + CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_erase : \ + CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_erase : \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_erase : \ + CC_CNTR_ID( *(cntr) ) == CC_OSET ? cc_oset_erase : \ + /* CC_STR */ cc_str_erase \ + ) \ + /* Function arguments */ \ + ( \ + *(cntr), \ + &CC_MAKE_LVAL_COPY( CC_KEY_TY( *(cntr) ), CC_KEY_CONVERTED_FOR_HETEROLOOKUP( *(cntr), (key) ) ), \ + CC_EL_SIZE( *(cntr) ), \ + CC_LAYOUT( *(cntr) ), \ + CC_KEY_HASH( *(cntr) ), \ + CC_KEY_CMPR( *(cntr) ), \ + CC_EL_DTOR( *(cntr) ), \ + CC_KEY_DTOR( *(cntr) ), \ + CC_FREE_FN \ + ) \ + ) \ +) \ -#define cc_erase_n( cntr, index, n ) \ -( \ - CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ - CC_STATIC_ASSERT( CC_CNTR_ID( *(cntr) ) == CC_VEC ), \ - CC_CAST_MAYBE_UNUSED( \ - CC_EL_TY( *(cntr) ) *, \ - cc_vec_erase_n( *(cntr), (index), (n), CC_EL_SIZE( *(cntr) ), CC_EL_DTOR( *(cntr) ) ) \ - ) \ -) \ +#define cc_erase_n( cntr, index, n ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_VEC || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ + ), \ + CC_CAST_MAYBE_UNUSED( \ + CC_EL_TY( *(cntr) ) *, \ + /* Function select */ \ + ( \ + CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_erase_n : \ + /* CC_STR */ cc_str_erase_n \ + ) \ + /* Function arguments */ \ + ( \ + *(cntr), \ + (index), \ + (n), \ + CC_EL_SIZE( *(cntr) ), \ + CC_EL_DTOR( *(cntr) ), \ + CC_FREE_FN \ + ) \ + ) \ +) \ -#define cc_erase_itr( cntr, itr ) \ -( \ - CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ - CC_STATIC_ASSERT( \ - CC_CNTR_ID( *(cntr) ) == CC_MAP || \ - CC_CNTR_ID( *(cntr) ) == CC_SET || \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ - ), \ - CC_CAST_MAYBE_UNUSED( \ - CC_EL_TY( *(cntr) ) *, \ - /* Function select */ \ - ( \ - CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_erase_itr : \ - CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_erase_itr : \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_erase_itr : \ - /* CC_OSET */ cc_oset_erase_itr \ - ) \ - /* Function arguments */ \ - ( \ - *(cntr), \ - (itr), \ - CC_EL_SIZE( *(cntr) ), \ - CC_LAYOUT( *(cntr) ), \ - CC_KEY_HASH( *(cntr) ), \ - CC_EL_DTOR( *(cntr) ), \ - CC_KEY_DTOR( *(cntr) ), \ - CC_FREE_FN \ - ) \ - ) \ -) \ +#define cc_erase_itr( cntr, itr ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_MAP || \ + CC_CNTR_ID( *(cntr) ) == CC_SET || \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ + CC_CNTR_ID( *(cntr) ) == CC_OSET \ + ), \ + CC_CAST_MAYBE_UNUSED( \ + CC_EL_TY( *(cntr) ) *, \ + /* Function select */ \ + ( \ + CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_erase_itr : \ + CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_erase_itr : \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_erase_itr : \ + /* CC_OSET */ cc_oset_erase_itr \ + ) \ + /* Function arguments */ \ + ( \ + *(cntr), \ + (itr), \ + CC_EL_SIZE( *(cntr) ), \ + CC_LAYOUT( *(cntr) ), \ + CC_KEY_HASH( *(cntr) ), \ + CC_EL_DTOR( *(cntr) ), \ + CC_KEY_DTOR( *(cntr) ), \ + CC_FREE_FN \ + ) \ + ) \ +) \ #define cc_splice( cntr, itr, src, src_itr ) \ ( \ CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ CC_STATIC_ASSERT( CC_CNTR_ID( *(cntr) ) == CC_LIST ), \ - CC_STATIC_ASSERT( CC_IS_SAME_TY( (cntr), (src) ) ), \ + CC_STATIC_ASSERT( CC_IS_SAME_TY( *(cntr), *(src) ) ), \ CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ *(cntr), \ cc_list_splice( *(cntr), (itr), *(src), (src_itr), CC_REALLOC_FN ) \ @@ -5044,16 +7050,36 @@ static inline void *cc_oset_next( CC_CAST_MAYBE_UNUSED( bool, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ ) \ -#define cc_resize( cntr, n ) \ -( \ - CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ - CC_STATIC_ASSERT( CC_CNTR_ID( *(cntr) ) == CC_VEC ), \ - CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ - *(cntr), \ - cc_vec_resize( *(cntr), (n), CC_EL_SIZE( *(cntr) ), CC_EL_DTOR( *(cntr) ), CC_REALLOC_FN ) \ - ), \ - CC_CAST_MAYBE_UNUSED( bool, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ -) \ +#define cc_resize( ... ) CC_SELECT_ON_NUM_ARGS( cc_resize_, __VA_ARGS__ ) + +#define cc_resize_2( cntr, n ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_STATIC_ASSERT( CC_CNTR_ID( *(cntr) ) == CC_VEC ), \ + CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ + *(cntr), \ + cc_vec_resize( *(cntr), (n), CC_EL_SIZE( *(cntr) ), CC_EL_DTOR( *(cntr) ), CC_REALLOC_FN, CC_FREE_FN ) \ + ), \ + CC_CAST_MAYBE_UNUSED( bool, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ +) \ + +#define cc_resize_3( cntr, n, fill_el ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_STATIC_ASSERT( CC_CNTR_ID( *(cntr) ) == CC_STR ), \ + CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ + *(cntr), \ + cc_str_resize( \ + *(cntr), \ + (n), \ + &CC_MAKE_LVAL_COPY( CC_EL_TY( *(cntr) ), fill_el ), \ + CC_EL_SIZE( *(cntr) ), \ + CC_EL_DTOR( *(cntr) ), \ + CC_REALLOC_FN \ + ) \ + ), \ + CC_CAST_MAYBE_UNUSED( bool, CC_FIX_HNDL_AND_RETURN_OTHER_PTR( *(cntr) ) ) \ +) \ #define cc_shrink( cntr ) \ ( \ @@ -5062,7 +7088,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_VEC || \ CC_CNTR_ID( *(cntr) ) == CC_LIST || \ CC_CNTR_ID( *(cntr) ) == CC_MAP || \ - CC_CNTR_ID( *(cntr) ) == CC_SET \ + CC_CNTR_ID( *(cntr) ) == CC_SET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ CC_POINT_HNDL_TO_ALLOCING_FN_RESULT( \ *(cntr), \ @@ -5070,7 +7097,8 @@ static inline void *cc_oset_next( ( \ CC_CNTR_ID( *(cntr) ) == CC_VEC ? cc_vec_shrink : \ CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_shrink : \ - /* CC_SET */ cc_set_shrink \ + CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_shrink : \ + /* CC_STR */ cc_str_shrink \ ) \ /* Function arguments */ \ ( \ @@ -5095,7 +7123,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP || \ CC_CNTR_ID( *(cntr) ) == CC_SET || \ CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ + CC_CNTR_ID( *(cntr) ) == CC_OSET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ CC_STATIC_ASSERT( CC_IS_SAME_TY( *(cntr), *(src) ) ), \ CC_CAST_MAYBE_UNUSED( \ @@ -5108,7 +7137,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_init_clone : \ CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_init_clone : \ CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_init_clone : \ - /* CC_OSET */ cc_oset_init_clone \ + CC_CNTR_ID( *(cntr) ) == CC_OSET ? cc_oset_init_clone : \ + /* CC_STR */ cc_str_init_clone \ ) \ /* Function arguments */ \ ( \ @@ -5130,7 +7160,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP || \ CC_CNTR_ID( *(cntr) ) == CC_SET || \ CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ + CC_CNTR_ID( *(cntr) ) == CC_OSET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ /* Function select */ \ ( \ @@ -5139,7 +7170,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_clear : \ CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_clear : \ CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_clear : \ - /* CC_OSET */ cc_oset_clear \ + CC_CNTR_ID( *(cntr) ) == CC_OSET ? cc_oset_clear : \ + /* CC_STR */ cc_str_clear \ ) \ /* Function arguments */ \ ( \ @@ -5161,7 +7193,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP || \ CC_CNTR_ID( *(cntr) ) == CC_SET || \ CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ + CC_CNTR_ID( *(cntr) ) == CC_OSET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ /* Function select */ \ ( \ @@ -5170,7 +7203,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_cleanup : \ CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_cleanup : \ CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_cleanup : \ - /* CC_OSET */ cc_oset_cleanup \ + CC_CNTR_ID( *(cntr) ) == CC_OSET ? cc_oset_cleanup : \ + /* CC_STR */ cc_str_cleanup \ ) \ /* Function arguments */ \ ( \ @@ -5184,36 +7218,36 @@ static inline void *cc_oset_next( cc_init( cntr ) \ ) \ -#define cc_r_end( cntr ) \ -( \ - CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ - CC_WARN_DEPRECATED_IF( \ - CC_CNTR_ID( *(cntr) ) == CC_MAP || \ - CC_CNTR_ID( *(cntr) ) == CC_SET \ - ), \ - CC_STATIC_ASSERT( \ - CC_CNTR_ID( *(cntr) ) == CC_LIST || \ - CC_CNTR_ID( *(cntr) ) == CC_MAP || \ - CC_CNTR_ID( *(cntr) ) == CC_SET || \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ - ), \ - CC_CAST_MAYBE_UNUSED( \ - CC_EL_TY( *(cntr) ) *, \ - /* Function select */ \ - ( \ - CC_CNTR_ID( *(cntr) ) == CC_LIST ? cc_list_r_end : \ - CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_r_end : \ - CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_r_end : \ - CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_r_end : \ - /* CC_OSET */ cc_oset_r_end \ - ) \ - /* Function arguments */ \ - ( \ - *(cntr) \ - ) \ - ) \ -) \ +#define cc_r_end( cntr ) \ +( \ + CC_WARN_DUPLICATE_SIDE_EFFECTS( cntr ), \ + CC_WARN_DEPRECATED_IF( \ + CC_CNTR_ID( *(cntr) ) == CC_MAP || \ + CC_CNTR_ID( *(cntr) ) == CC_SET \ + ), \ + CC_STATIC_ASSERT( \ + CC_CNTR_ID( *(cntr) ) == CC_LIST || \ + CC_CNTR_ID( *(cntr) ) == CC_MAP || \ + CC_CNTR_ID( *(cntr) ) == CC_SET || \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ + CC_CNTR_ID( *(cntr) ) == CC_OSET \ + ), \ + CC_CAST_MAYBE_UNUSED( \ + CC_EL_TY( *(cntr) ) *, \ + /* Function select */ \ + ( \ + CC_CNTR_ID( *(cntr) ) == CC_LIST ? cc_list_r_end : \ + CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_r_end : \ + CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_r_end : \ + CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_r_end : \ + /* CC_OSET */ cc_oset_r_end \ + ) \ + /* Function arguments */ \ + ( \ + *(cntr) \ + ) \ + ) \ +) \ #define cc_end( cntr ) \ ( \ @@ -5224,7 +7258,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP || \ CC_CNTR_ID( *(cntr) ) == CC_SET || \ CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ + CC_CNTR_ID( *(cntr) ) == CC_OSET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ CC_CAST_MAYBE_UNUSED( \ CC_EL_TY( *(cntr) ) *, \ @@ -5235,7 +7270,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_end : \ CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_end : \ CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_end : \ - /* CC_OSET */ cc_oset_end \ + CC_CNTR_ID( *(cntr) ) == CC_OSET ? cc_oset_end : \ + /* CC_STR */ cc_str_end \ ) \ /* Function arguments */ \ ( \ @@ -5246,7 +7282,7 @@ static inline void *cc_oset_next( ) \ ) \ -#define cc_first( ... ) CC_SELECT_ON_NUM_ARGS( cc_first, __VA_ARGS__ ) +#define cc_first( ... ) CC_SELECT_ON_NUM_ARGS( cc_first_, __VA_ARGS__ ) #define cc_first_1( cntr ) \ ( \ @@ -5257,7 +7293,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP || \ CC_CNTR_ID( *(cntr) ) == CC_SET || \ CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ + CC_CNTR_ID( *(cntr) ) == CC_OSET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ CC_CAST_MAYBE_UNUSED( \ CC_EL_TY( *(cntr) ) *, \ @@ -5268,7 +7305,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_first : \ CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_first : \ CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_first : \ - /* CC_OSET */ cc_oset_first \ + CC_CNTR_ID( *(cntr) ) == CC_OSET ? cc_oset_first : \ + /* CC_STR */ cc_str_first \ ) \ /* Function arguments */ \ ( \ @@ -5305,7 +7343,7 @@ static inline void *cc_oset_next( ) \ ) \ -#define cc_last( ... ) CC_SELECT_ON_NUM_ARGS( cc_last, __VA_ARGS__ ) +#define cc_last( ... ) CC_SELECT_ON_NUM_ARGS( cc_last_, __VA_ARGS__ ) #define cc_last_1( cntr ) \ ( \ @@ -5320,7 +7358,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP || \ CC_CNTR_ID( *(cntr) ) == CC_SET || \ CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ + CC_CNTR_ID( *(cntr) ) == CC_OSET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ CC_CAST_MAYBE_UNUSED( \ CC_EL_TY( *(cntr) ) *, \ @@ -5331,7 +7370,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_last : \ CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_last : \ CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_last : \ - /* CC_OSET */ cc_oset_last \ + CC_CNTR_ID( *(cntr) ) == CC_OSET ? cc_oset_last : \ + /* CC_STR */ cc_str_last \ ) \ /* Function arguments */ \ ( \ @@ -5377,7 +7417,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP || \ CC_CNTR_ID( *(cntr) ) == CC_SET || \ CC_CNTR_ID( *(cntr) ) == CC_OMAP || \ - CC_CNTR_ID( *(cntr) ) == CC_OSET \ + CC_CNTR_ID( *(cntr) ) == CC_OSET || \ + CC_CNTR_ID( *(cntr) ) == CC_STR \ ), \ CC_CAST_MAYBE_UNUSED( \ CC_EL_TY( *(cntr) ) *, \ @@ -5388,7 +7429,8 @@ static inline void *cc_oset_next( CC_CNTR_ID( *(cntr) ) == CC_MAP ? cc_map_next : \ CC_CNTR_ID( *(cntr) ) == CC_SET ? cc_set_next : \ CC_CNTR_ID( *(cntr) ) == CC_OMAP ? cc_omap_next : \ - /* CC_OSET */ cc_oset_next \ + CC_CNTR_ID( *(cntr) ) == CC_OSET ? cc_oset_next : \ + /* CC_STR */ cc_str_next \ ) \ /* Function arguments */ \ ( \ @@ -5434,7 +7476,7 @@ static inline void *cc_oset_next( ) \ ) \ -#define cc_for_each( ... ) CC_SELECT_ON_NUM_ARGS( cc_for_each, __VA_ARGS__ ) +#define cc_for_each( ... ) CC_SELECT_ON_NUM_ARGS( cc_for_each_, __VA_ARGS__ ) #define cc_for_each_2( cntr, i_name ) \ for( \ @@ -5451,7 +7493,7 @@ static inline void *cc_oset_next( ) \ for( const CC_KEY_TY( *(cntr) ) *key_ptr_name = cc_key_for( (cntr), i_name ); key_ptr_name; key_ptr_name = NULL ) \ -#define cc_r_for_each( ... ) CC_SELECT_ON_NUM_ARGS( cc_r_for_each, __VA_ARGS__ ) +#define cc_r_for_each( ... ) CC_SELECT_ON_NUM_ARGS( cc_r_for_each_, __VA_ARGS__ ) #define cc_r_for_each_2( cntr, i_name ) \ for( \ @@ -5468,6 +7510,665 @@ static inline void *cc_oset_next( ) \ for( const CC_KEY_TY( *(cntr) ) *key_ptr_name = cc_key_for( (cntr), i ); key_ptr_name; key_ptr_name = NULL ) \ +/*--------------------------------------------------------------------------------------------------------------------*/ +/* Heterogeneous string insertion and lookup conversion */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +// This section defines the macros used to support the passing of C strings into API macros that perform insertions, +// lookups, and related operations on containers with cc_str key and/or element types. +// API macros that perform lookups must convert the supplied C string into a temporary string that uses +// the C string as its buffer. +// API macros that perform insertions, on the other hand, must do the following: +// +// * Convert the C string into a real, allocated cc_str. +// * Check that the conversion did not fail due to memory exhaustion. +// * Free the allocated cc_str if the conversion failed or the container operation did not actually insert it because of +// memory exhaustion or some other condition. +// +// The auxiliary macros defined here achieve these things by identifying a heterogeneous insertion or lookup condition +// based on the container's key or element type and the type of the key or element to be inserted or looked up and then +// performing the necessary action. Here, the C++ macros rely on template functions, whereas the C macros use _Generic +// expressions. + +// CC_EL_CONVERTED_FOR_HETEROINSERT and CC_KEY_CONVERTED_FOR_HETEROINSERT for converting a C-string key or element for +// heterogeneous insertion. +// If no heterogeneous insertion condition exists, these macros return the key or element unmodified. + +#ifdef __cplusplus + +template< + typename cntr_key_or_el_ty, + typename arg_key_or_el_ty, + typename std::enable_if< + CC_IF_CPP20( + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) || + ) + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) || + ( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ) || + ( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ), + bool + >::type = true +> +cntr_key_or_el_ty cc_key_or_el_converted_for_heteroinsert( const void *cstring, cc_realloc_fnptr_ty realloc_ ) +{ + return (cntr_key_or_el_ty)cc_cstring_to_str_char8( cstring, realloc_ ); +} + +template< + typename cntr_key_or_el_ty, + typename arg_key_or_el_ty, + typename std::enable_if< + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ), + bool + >::type = true +> +CC_STR_RAW( char16_t ) cc_key_or_el_converted_for_heteroinsert( const char16_t *cstring, cc_realloc_fnptr_ty realloc_ ) +{ + return cc_cstring_to_str_char16( cstring, realloc_ ); +} + +template< + typename cntr_key_or_el_ty, + typename arg_key_or_el_ty, + typename std::enable_if< + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ), + bool + >::type = true +> +CC_STR_RAW( char32_t ) cc_key_or_el_converted_for_heteroinsert( const char32_t *cstring, cc_realloc_fnptr_ty realloc_ ) +{ + return cc_cstring_to_str_char32( cstring, realloc_ ); +} + +template< + typename cntr_key_or_el_ty, + typename arg_key_or_el_ty, + typename std::enable_if< + CC_IF_CPP20( + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + ) + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + !( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ) && + !( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ) && + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) + , + bool + >::type = true +> +arg_key_or_el_ty cc_key_or_el_converted_for_heteroinsert( + arg_key_or_el_ty arg, + CC_UNUSED( cc_realloc_fnptr_ty, realloc_ ) +) +{ + return arg; +} + +#define CC_EL_CONVERTED_FOR_HETEROINSERT( cntr, el ) \ +( cc_key_or_el_converted_for_heteroinsert( el, CC_REALLOC_FN ) ) \ + +#define CC_KEY_CONVERTED_FOR_HETEROINSERT( cntr, key ) \ +( cc_key_or_el_converted_for_heteroinsert( key, CC_REALLOC_FN ) ) \ + +#else + +// In C, to detect a condition that calls for an element conversion, we must perform a generic selection on two types: +// the container's element type, and the type of the element supplied for insertion. +// To do so, we construct a function pointer whose return type is the container's element type and whose argument type +// is the type of the supplied element, and then test that against all possible conversion conditions. + +#define CC_EL_CONVERTED_FOR_HETEROINSERT( cntr, el ) \ +_Generic( ( CC_EL_TY( cntr ) (*)( CC_TYPEOF_XP( el ) ) ){ 0 }, \ + CC_STR_RAW( cc_maybe_char ) (*)( char * ): \ + cc_cstring_to_str_char8( _Generic( el, char *: el, default: NULL ), CC_REALLOC_FN ), \ + CC_STR_RAW( cc_maybe_char ) (*)( const char * ): \ + cc_cstring_to_str_char8( _Generic( el, const char *: el, default: NULL ), CC_REALLOC_FN ), \ + CC_STR_RAW( unsigned char ) (*)( unsigned char * ): \ + cc_cstring_to_str_char8( _Generic( el, unsigned char *: el, default: NULL ), CC_REALLOC_FN ), \ + CC_STR_RAW( unsigned char ) (*)( const unsigned char * ): \ + cc_cstring_to_str_char8( _Generic( el, const unsigned char *: el, default: NULL ), CC_REALLOC_FN ), \ + CC_STR_RAW( signed char ) (*)( signed char * ): \ + cc_cstring_to_str_char8( _Generic( el, signed char *: el, default: NULL ), CC_REALLOC_FN ), \ + CC_STR_RAW( signed char ) (*)( const signed char * ): \ + cc_cstring_to_str_char8( _Generic( el, const signed char *: el, default: NULL ), CC_REALLOC_FN ), \ + CC_STR_RAW( char16_t ) (*)( char16_t * ): \ + cc_cstring_to_str_char16( _Generic( el, char16_t *: el, default: NULL ), CC_REALLOC_FN ), \ + CC_STR_RAW( char16_t ) (*)( const char16_t * ): \ + cc_cstring_to_str_char16( _Generic( el, const char16_t *: el, default: NULL ), CC_REALLOC_FN ), \ + CC_STR_RAW( char32_t ) (*)( char32_t * ): \ + cc_cstring_to_str_char32( _Generic( el, char32_t *: el, default: NULL ), CC_REALLOC_FN ), \ + CC_STR_RAW( char32_t ) (*)( const char32_t * ): \ + cc_cstring_to_str_char32( _Generic( el, const char32_t *: el, default: NULL ), CC_REALLOC_FN ), \ + default: el \ +) \ + +// The same goes for key conversion, except the constructed function pointer's return type is the container's base +// function pointer type rather than its key type specifically. +// This is more efficient, in terms of compile speed, than using CC_KEY_TY to deduce the container's key type here. + +#define CC_KEY_CONVERTED_FOR_HETEROINSERT( cntr, key ) \ +_Generic( ( CC_TYPEOF_XP( **cntr ) (*)( CC_TYPEOF_XP( key ) ) ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ) (*)( char * ): \ + cc_cstring_to_str_char8( _Generic( key, char *: key, default: NULL ), CC_REALLOC_FN ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ) (*)( const char * ): \ + cc_cstring_to_str_char8( _Generic( key, const char *: key, default: NULL ), CC_REALLOC_FN ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ) (*)( unsigned char * ): \ + cc_cstring_to_str_char8( _Generic( key, unsigned char *: key, default: NULL ), CC_REALLOC_FN ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ) (*)( const unsigned char * ): \ + cc_cstring_to_str_char8( _Generic( key, const unsigned char *: key, default: NULL ), CC_REALLOC_FN ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ) (*)( signed char * ): \ + cc_cstring_to_str_char8( _Generic( key, signed char *: key, default: NULL ), CC_REALLOC_FN ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ) (*)( const signed char * ): \ + cc_cstring_to_str_char8( _Generic( key, const signed char *: key, default: NULL ), CC_REALLOC_FN ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ) (*)( char16_t * ): \ + cc_cstring_to_str_char16( _Generic( key, char16_t *: key, default: NULL ), CC_REALLOC_FN ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ) (*)( const char16_t * ): \ + cc_cstring_to_str_char16( _Generic( key, const char16_t *: key, default: NULL ), CC_REALLOC_FN ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ) (*)( char32_t * ): \ + cc_cstring_to_str_char32( _Generic( key, char32_t *: key, default: NULL ), CC_REALLOC_FN ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ) (*)( const char32_t * ): \ + cc_cstring_to_str_char32( _Generic( key, const char32_t *: key, default: NULL ), CC_REALLOC_FN ), \ + default: key \ +) \ + +#endif + +// CC_CHECK_HETEROINSERT_EL_CONVERSION and CC_CHECK_HETEROINSERT_KEY_CONVERSION macros for checking that the above +// conversions succeeded. +// This requires checking that the provided void pointer to the converted key or element is not NULL. +// If no conversion was necessary, these macros simply return true. + +#ifdef __cplusplus + +template< + typename cntr_key_or_el_ty, + typename arg_key_or_el_ty, + typename std::enable_if< + CC_IF_CPP20( + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) || + ) + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) || + ( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ) || + ( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ) || + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) || + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) + , + bool + >::type = true +> +bool cc_check_heteroinsert_key_or_el_conversion( void *converted_key_or_el ) +{ + return *(cntr_key_or_el_ty *)converted_key_or_el; +} + +template< + typename cntr_key_or_el_ty, + typename arg_key_or_el_ty, + typename std::enable_if< + CC_IF_CPP20( + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + ) + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + !( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ) && + !( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ) && + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) + , + bool + >::type = true +> +bool cc_check_heteroinsert_key_or_el_conversion( CC_UNUSED( void *, converted_key_or_el ) ) +{ + return true; +} + +#define CC_CHECK_HETEROINSERT_EL_CONVERSION( cntr, el, converted_el ) \ +cc_check_heteroinsert_key_or_el_conversion( converted_el ) \ + +#define CC_CHECK_HETEROINSERT_KEY_CONVERSION( cntr, key, converted_key ) \ +cc_check_heteroinsert_key_or_el_conversion( converted_key ) \ + +#else + +#define CC_CHECK_HETEROINSERT_EL_CONVERSION( cntr, el, converted_el ) \ +_Generic( ( CC_EL_TY( cntr ) (*)( CC_TYPEOF_XP( el ) ) ){ 0 }, \ + CC_STR_RAW( cc_maybe_char ) (*)( char * ): \ + (bool)*(CC_STR_RAW( char ) *)converted_el, \ + CC_STR_RAW( cc_maybe_char ) (*)( const char * ): \ + (bool)*(CC_STR_RAW( char ) *)converted_el, \ + CC_STR_RAW( unsigned char ) (*)( unsigned char * ): \ + (bool)*(CC_STR_RAW( unsigned char ) *)converted_el, \ + CC_STR_RAW( unsigned char ) (*)( const unsigned char * ): \ + (bool)*(CC_STR_RAW( unsigned char ) *)converted_el, \ + CC_STR_RAW( signed char ) (*)( signed char * ): \ + (bool)*(CC_STR_RAW( signed char ) *)converted_el, \ + CC_STR_RAW( signed char ) (*)( const signed char * ): \ + (bool)*(CC_STR_RAW( signed char ) *)converted_el, \ + CC_STR_RAW( char16_t ) (*)( char16_t * ): \ + (bool)*(CC_STR_RAW( char16_t ) *)converted_el, \ + CC_STR_RAW( char16_t ) (*)( const char16_t * ): \ + (bool)*(CC_STR_RAW( char16_t ) *)converted_el, \ + CC_STR_RAW( char32_t ) (*)( char32_t * ): \ + (bool)*(CC_STR_RAW( char32_t ) *)converted_el, \ + CC_STR_RAW( char32_t ) (*)( const char32_t * ): \ + (bool)*(CC_STR_RAW( char32_t ) *)converted_el, \ + default: true \ +) \ + +#define CC_CHECK_HETEROINSERT_KEY_CONVERSION( cntr, key, converted_key ) \ +_Generic( ( CC_TYPEOF_XP( **cntr ) (*)( CC_TYPEOF_XP( key ) ) ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ) (*)( char * ): \ + (bool)*(CC_STR_RAW( char ) *)converted_key, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ) (*)( const char * ): \ + (bool)*(CC_STR_RAW( char ) *)converted_key, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ) (*)( unsigned char * ): \ + (bool)*(CC_STR_RAW( unsigned char ) *)converted_key, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ) (*)( const unsigned char * ): \ + (bool)*(CC_STR_RAW( unsigned char ) *)converted_key, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ) (*)( signed char * ): \ + (bool)*(CC_STR_RAW( signed char ) *)converted_key, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ) (*)( const signed char * ): \ + (bool)*(CC_STR_RAW( signed char ) *)converted_key, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ) (*)( char16_t * ): \ + (bool)*(CC_STR_RAW( char16_t ) *)converted_key, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ) (*)( const char16_t * ): \ + (bool)*(CC_STR_RAW( char16_t ) *)converted_key, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ) (*)( char32_t * ): \ + (bool)*(CC_STR_RAW( char32_t ) *)converted_key, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ) (*)( const char32_t * ): \ + (bool)*(CC_STR_RAW( char32_t ) *)converted_key, \ + default: true \ +) \ + +#endif + +// CC_CLEANUP_HETEROINSERT_EL and CC_CLEANUP_HETEROINSERT_KEY macros for freeing a converted key or element in the case +// of allocation failure or no need for insertion (a condition that can occur in cc_get_or_insert). +// If no conversion was necessary, these macros are effectively no-ops. + +#ifdef __cplusplus + +template< + typename cntr_key_or_el_ty, + typename arg_key_or_el_ty, + typename std::enable_if< + CC_IF_CPP20( + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) || + ) + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) || + ( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ) || + ( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ) || + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) || + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) + , + bool + >::type = true +> +void cc_cleanup_heteroinsert_key_or_el( void *converted_key_or_el, cc_free_fnptr_ty free_ ) +{ + free_( *(cntr_key_or_el_ty *)converted_key_or_el ); +} + +template< + typename cntr_key_or_el_ty, + typename arg_key_or_el_ty, + typename std::enable_if< + CC_IF_CPP20( + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + ) + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + !( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ) && + !( + std::is_same::value && + ( + std::is_same::value || + std::is_same::value + ) + ) && + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) + , + bool + >::type = true +> +void cc_cleanup_heteroinsert_key_or_el( CC_UNUSED( void *, converted_key_or_el ), CC_UNUSED( cc_free_fnptr_ty, free_ ) ) +{ + // Do nothing. +} + +#define CC_CLEANUP_HETEROINSERT_EL( cntr, el, converted_el ) \ +cc_cleanup_heteroinsert_key_or_el( converted_el, CC_FREE_FN ) \ + +#define CC_CLEANUP_HETEROINSERT_KEY( cntr, key, converted_key ) \ +cc_cleanup_heteroinsert_key_or_el( converted_key, CC_FREE_FN ) \ + +#else + +#define CC_CLEANUP_HETEROINSERT_EL( cntr, el, converted_el ) \ +_Generic( ( CC_EL_TY( cntr ) (*)( CC_TYPEOF_XP( el ) ) ){ 0 }, \ + CC_STR_RAW( cc_maybe_char ) (*)( char * ): \ + CC_FREE_FN( *(CC_STR_RAW( char ) *)converted_el ), \ + CC_STR_RAW( cc_maybe_char ) (*)( const char * ): \ + CC_FREE_FN( *(CC_STR_RAW( char ) *)converted_el ), \ + CC_STR_RAW( unsigned char ) (*)( unsigned char * ): \ + CC_FREE_FN( *(CC_STR_RAW( unsigned char ) *)converted_el ), \ + CC_STR_RAW( unsigned char ) (*)( const unsigned char * ): \ + CC_FREE_FN( *(CC_STR_RAW( unsigned char ) *)converted_el ), \ + CC_STR_RAW( signed char ) (*)( signed char * ): \ + CC_FREE_FN( *(CC_STR_RAW( signed char ) *)converted_el ), \ + CC_STR_RAW( signed char ) (*)( const signed char * ): \ + CC_FREE_FN( *(CC_STR_RAW( signed char ) *)converted_el ), \ + CC_STR_RAW( char16_t ) (*)( char16_t * ): \ + CC_FREE_FN( *(CC_STR_RAW( char16_t ) *)converted_el ), \ + CC_STR_RAW( char16_t ) (*)( const char16_t * ): \ + CC_FREE_FN( *(CC_STR_RAW( char16_t ) *)converted_el ), \ + CC_STR_RAW( char32_t ) (*)( char32_t * ): \ + CC_FREE_FN( *(CC_STR_RAW( char32_t ) *)converted_el ), \ + CC_STR_RAW( char32_t ) (*)( const char32_t * ): \ + CC_FREE_FN( *(CC_STR_RAW( char32_t ) *)converted_el ), \ + default: (void)0 \ +) \ + +#define CC_CLEANUP_HETEROINSERT_KEY( cntr, key, converted_key ) \ +_Generic( ( CC_TYPEOF_XP( **cntr ) (*)( CC_TYPEOF_XP( key ) ) ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ) (*)( char * ): \ + CC_FREE_FN( *(CC_STR_RAW( char ) *)converted_key ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ) (*)( const char * ): \ + CC_FREE_FN( *(CC_STR_RAW( char ) *)converted_key ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ) (*)( unsigned char * ): \ + CC_FREE_FN( *(CC_STR_RAW( unsigned char ) *)converted_key ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ) (*)( const unsigned char * ): \ + CC_FREE_FN( *(CC_STR_RAW( unsigned char ) *)converted_key ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ) (*)( signed char * ): \ + CC_FREE_FN( *(CC_STR_RAW( signed char ) *)converted_key ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ) (*)( const signed char * ): \ + CC_FREE_FN( *(CC_STR_RAW( signed char ) *)converted_key ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ) (*)( char16_t * ): \ + CC_FREE_FN( *(CC_STR_RAW( char16_t ) *)converted_key ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ) (*)( const char16_t * ): \ + CC_FREE_FN( *(CC_STR_RAW( char16_t ) *)converted_key ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ) (*)( char32_t * ): \ + CC_FREE_FN( *(CC_STR_RAW( char32_t ) *)converted_key ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ) (*)( const char32_t * ): \ + CC_FREE_FN( *(CC_STR_RAW( char32_t ) *)converted_key ), \ + default: (void)0 \ +) \ + +#endif + +// CC_KEY_CONVERTED_FOR_HETEROLOOKUP macro for wrapping a supplied C-string key (or element, in the case of set and +// oset) inside a temporary cc_str for heterogeneous lookup. +// If no heterogeneous lookup is being performed, these macros return the key unmodified. + +#ifdef __cplusplus + +template< + typename cntr_key_ty, + typename arg_key_ty, + typename std::enable_if< + CC_IF_CPP20( + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) || + ) + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) || + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) || + ( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ), + bool + >::type = true +> +cntr_key_ty cc_key_converted_for_heterolookup( const void *cstring, cc_str_hdr_ty *str_hdr ) +{ + return (cntr_key_ty)cc_cstring_to_temp_str_char8( cstring, str_hdr ); +} + +template< + typename key_ty, + typename arg_key_ty, + typename std::enable_if< + std::is_same::value && + ( std::is_same::value || std::is_same::value ), + bool + >::type = true +> +CC_STR_RAW( char16_t ) cc_key_converted_for_heterolookup( const char16_t *cstring, cc_str_hdr_ty *str_hdr ) +{ + return cc_cstring_to_temp_str_char16( cstring, str_hdr ); +} + +template< + typename cntr_key_ty, + typename arg_key_ty, + typename std::enable_if< + std::is_same::value + && ( std::is_same::value || std::is_same::value ), + bool + >::type = true +> +CC_STR_RAW( char32_t ) cc_key_converted_for_heterolookup( const char32_t *cstring, cc_str_hdr_ty *str_hdr ) +{ + return cc_cstring_to_temp_str_char32( cstring, str_hdr ); +} + +template< + typename cntr_key_ty, + typename arg_key_ty, + typename std::enable_if< + CC_IF_CPP20( + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + ) + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) && + !( + std::is_same::value && + ( std::is_same::value || std::is_same::value ) + ) + , + bool + >::type = true +> +arg_key_ty cc_key_converted_for_heterolookup( arg_key_ty arg, CC_UNUSED( cc_str_hdr_ty *, str_hdr ) ) +{ + return arg; +} + +#define CC_KEY_CONVERTED_FOR_HETEROLOOKUP( cntr, key ) \ +cc_key_converted_for_heterolookup( key, &cc_unmove( cc_str_hdr_ty() ) ) \ + +#else + +#define CC_KEY_CONVERTED_FOR_HETEROLOOKUP( cntr, key ) \ +_Generic( ( CC_TYPEOF_XP( **cntr ) (*)( CC_TYPEOF_XP( key ) ) ){ 0 }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ) (*)( char * ): \ + cc_cstring_to_temp_str_char8( _Generic( key, char *: key, default: NULL ), &(cc_str_hdr_ty){ 0 } ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ) (*)( const char * ): \ + cc_cstring_to_temp_str_char8( _Generic( key, const char *: key, default: NULL ), &(cc_str_hdr_ty){ 0 } ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ) (*)( unsigned char * ): \ + cc_cstring_to_temp_str_char8( _Generic( key, unsigned char *: key, default: NULL ), &(cc_str_hdr_ty){ 0 } ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ) (*)( const unsigned char * ): \ + cc_cstring_to_temp_str_char8( _Generic( key, const unsigned char *: key, default: NULL ), &(cc_str_hdr_ty){ 0 } ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ) (*)( signed char * ): \ + cc_cstring_to_temp_str_char8( _Generic( key, signed char *: key, default: NULL ), &(cc_str_hdr_ty){ 0 } ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ) (*)( const signed char * ): \ + cc_cstring_to_temp_str_char8( _Generic( key, const signed char *: key, default: NULL ), &(cc_str_hdr_ty){ 0 } ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ) (*)( char16_t * ): \ + cc_cstring_to_temp_str_char16( _Generic( key, char16_t *: key, default: NULL ), &(cc_str_hdr_ty){ 0 } ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ) (*)( const char16_t * ): \ + cc_cstring_to_temp_str_char16( _Generic( key, const char16_t *: key, default: NULL ), &(cc_str_hdr_ty){ 0 } ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ) (*)( char32_t * ): \ + cc_cstring_to_temp_str_char32( _Generic( key, char32_t *: key, default: NULL ), &(cc_str_hdr_ty){ 0 } ), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ) (*)( const char32_t * ): \ + cc_cstring_to_temp_str_char32( _Generic( key, const char32_t *: key, default: NULL ), &(cc_str_hdr_ty){ 0 } ), \ + default: key \ +) \ + +#endif + /*--------------------------------------------------------------------------------------------------------------------*/ /* Destructor, comparison, and hash functions and custom load factors */ /*--------------------------------------------------------------------------------------------------------------------*/ @@ -5557,137 +8258,199 @@ CC_CAT_2( CC_R3_, d3 )( m, arg ) \ #ifdef __cplusplus #define CC_EL_DTOR_SLOT( n, arg ) std::is_same::value ? cc_dtor_##n##_fn : -#define CC_EL_DTOR( cntr ) \ -( \ - CC_FOR_EACH_DTOR( CC_EL_DTOR_SLOT, CC_EL_TY( cntr ) ) \ - (cc_dtor_fnptr_ty)NULL \ -) \ +#define CC_EL_DTOR( cntr ) \ +( \ + CC_FOR_EACH_DTOR( CC_EL_DTOR_SLOT, CC_EL_TY( cntr ) ) \ + std::is_same::value ? cc_destroy_str_char : \ + std::is_same::value ? cc_destroy_str_unsigned_char : \ + std::is_same::value ? cc_destroy_str_signed_char : \ + CC_IF_CPP20( std::is_same::value ? cc_destroy_str_char8 : ) \ + std::is_same::value ? cc_destroy_str_char16 : \ + std::is_same::value ? cc_destroy_str_char32 : \ + (cc_dtor_fnptr_ty)NULL \ +) \ -#define CC_KEY_DTOR_SLOT( n, arg ) \ -std::is_same< \ - CC_TYPEOF_XP(**arg), \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( arg ), cc_dtor_##n##_ty ) \ ->::value ? cc_dtor_##n##_fn : \ +#define CC_KEY_DTOR_SLOT( n, arg ) \ +std::is_same::value ? \ + cc_dtor_##n##_fn : \ -#define CC_KEY_DTOR( cntr ) \ -( \ - CC_FOR_EACH_DTOR( CC_KEY_DTOR_SLOT, cntr ) \ - (cc_dtor_fnptr_ty)NULL \ -) \ +#define CC_KEY_DTOR( cntr ) \ +( \ + CC_FOR_EACH_DTOR( CC_KEY_DTOR_SLOT, cntr ) \ + std::is_same::value ? \ + cc_destroy_str_char : \ + std::is_same::value ? \ + cc_destroy_str_unsigned_char : \ + std::is_same::value ? \ + cc_destroy_str_signed_char : \ + CC_IF_CPP20( \ + std::is_same::value ? \ + cc_destroy_str_char8 : \ + ) \ + std::is_same::value ? \ + cc_destroy_str_char16 : \ + std::is_same::value ? \ + cc_destroy_str_char32 : \ + (cc_dtor_fnptr_ty)NULL \ +) \ #define CC_KEY_CMPR_SLOT( n, arg ) \ std::is_same::value ? \ cc_cmpr_##n##_fn_select : \ -#define CC_KEY_CMPR( cntr ) \ -( \ - CC_FOR_EACH_CMPR( CC_KEY_CMPR_SLOT, cntr ) \ - std::is_same::value ? \ - cc_cmpr_char_select : \ - std::is_same::value ? \ - cc_cmpr_unsigned_char_select : \ - std::is_same::value ? \ - cc_cmpr_signed_char_select : \ - std::is_same::value ? \ - cc_cmpr_unsigned_short_select : \ - std::is_same::value ? \ - cc_cmpr_short_select : \ - std::is_same::value ? \ - cc_cmpr_unsigned_int_select : \ - std::is_same::value ? \ - cc_cmpr_int_select : \ - std::is_same::value ? \ - cc_cmpr_unsigned_long_select : \ - std::is_same::value ? \ - cc_cmpr_long_select : \ - std::is_same::value ? \ - cc_cmpr_unsigned_long_long_select : \ - std::is_same::value ? \ - cc_cmpr_long_long_select : \ - std::is_same::value ? \ - cc_cmpr_size_t_select : \ - std::is_same::value ? \ - cc_cmpr_c_string_select : \ - cc_cmpr_dummy_select \ -)( CC_CNTR_ID( cntr ) ) \ +#define CC_KEY_CMPR( cntr ) \ +( \ + CC_FOR_EACH_CMPR( CC_KEY_CMPR_SLOT, cntr ) \ + std::is_same::value ? \ + cc_cmpr_char_select : \ + std::is_same::value ? \ + cc_cmpr_unsigned_char_select : \ + std::is_same::value ? \ + cc_cmpr_signed_char_select : \ + std::is_same::value ? \ + cc_cmpr_unsigned_short_select : \ + std::is_same::value ? \ + cc_cmpr_short_select : \ + std::is_same::value ? \ + cc_cmpr_unsigned_int_select : \ + std::is_same::value ? \ + cc_cmpr_int_select : \ + std::is_same::value ? \ + cc_cmpr_unsigned_long_select : \ + std::is_same::value ? \ + cc_cmpr_long_select : \ + std::is_same::value ? \ + cc_cmpr_unsigned_long_long_select : \ + std::is_same::value ? \ + cc_cmpr_long_long_select : \ + std::is_same::value ? \ + cc_cmpr_size_t_select : \ + std::is_same::value ? \ + cc_cmpr_cstring_select : \ + std::is_same::value ? \ + cc_cmpr_cstring_select : \ + std::is_same::value ? \ + cc_cmpr_str_char_select : \ + std::is_same::value ? \ + cc_cmpr_str_unsigned_char_select : \ + std::is_same::value ? \ + cc_cmpr_str_signed_char_select : \ + CC_IF_CPP20( \ + std::is_same::value ? \ + cc_cmpr_str_char8_select : \ + ) \ + std::is_same::value ? \ + cc_cmpr_str_char16_select : \ + std::is_same::value ? \ + cc_cmpr_str_char32_select : \ + cc_cmpr_dummy_select \ +)( CC_CNTR_ID( cntr ) ) \ -#define CC_KEY_HASH_SLOT( n, arg ) \ -std::is_same< \ - CC_TYPEOF_XP(**arg), \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( arg ), cc_hash_##n##_ty ) \ ->::value ? cc_hash_##n##_fn : \ +#define CC_KEY_HASH_SLOT( n, arg ) \ +std::is_same::value ? \ + cc_hash_##n##_fn : \ -#define CC_KEY_HASH( cntr ) \ -( \ - CC_FOR_EACH_HASH( CC_KEY_HASH_SLOT, cntr ) \ - std::is_same::value ? \ - cc_hash_char : \ - std::is_same::value ? \ - cc_hash_unsigned_char : \ - std::is_same::value ? \ - cc_hash_signed_char : \ - std::is_same::value ? \ - cc_hash_unsigned_short : \ - std::is_same::value ? \ - cc_hash_short : \ - std::is_same::value ? \ - cc_hash_unsigned_int : \ - std::is_same::value ? \ - cc_hash_int : \ - std::is_same::value ? \ - cc_hash_unsigned_long : \ - std::is_same::value ? \ - cc_hash_long : \ - std::is_same::value ? \ - cc_hash_unsigned_long_long : \ - std::is_same::value ? \ - cc_hash_long_long : \ - std::is_same::value ? \ - cc_hash_size_t : \ - std::is_same::value ? \ - cc_hash_c_string : \ - (cc_hash_fnptr_ty)NULL \ -) \ +#define CC_KEY_HASH( cntr ) \ +( \ + CC_FOR_EACH_HASH( CC_KEY_HASH_SLOT, cntr ) \ + std::is_same::value ? \ + cc_hash_char : \ + std::is_same::value ? \ + cc_hash_unsigned_char : \ + std::is_same::value ? \ + cc_hash_signed_char : \ + std::is_same::value ? \ + cc_hash_unsigned_short : \ + std::is_same::value ? \ + cc_hash_short : \ + std::is_same::value ? \ + cc_hash_unsigned_int : \ + std::is_same::value ? \ + cc_hash_int : \ + std::is_same::value ? \ + cc_hash_unsigned_long : \ + std::is_same::value ? \ + cc_hash_long : \ + std::is_same::value ? \ + cc_hash_unsigned_long_long : \ + std::is_same::value ? \ + cc_hash_long_long : \ + std::is_same::value ? \ + cc_hash_size_t : \ + std::is_same::value ? \ + cc_hash_cstring : \ + std::is_same::value ? \ + cc_hash_cstring : \ + std::is_same::value ? \ + cc_hash_str_char : \ + std::is_same::value ? \ + cc_hash_str_unsigned_char : \ + std::is_same::value ? \ + cc_hash_str_signed_char : \ + CC_IF_CPP20( \ + std::is_same::value ? \ + cc_hash_str_char8 : \ + ) \ + std::is_same::value ? \ + cc_hash_str_char16 : \ + std::is_same::value ? \ + cc_hash_str_char32 : \ + (cc_hash_fnptr_ty)NULL \ +) \ #define CC_HAS_CMPR_SLOT( n, arg ) std::is_same::value ? true : -#define CC_HAS_CMPR( ty ) \ -( \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - CC_FOR_EACH_CMPR( CC_HAS_CMPR_SLOT, ty ) \ - false \ -) \ +#define CC_HAS_CMPR( ty ) \ +( \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + CC_IF_CPP20( std::is_same::value ? true : ) \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + CC_FOR_EACH_CMPR( CC_HAS_CMPR_SLOT, ty ) \ + false \ +) \ #define CC_HAS_HASH_SLOT( n, arg ) std::is_same::value ? true : -#define CC_HAS_HASH( ty ) \ -( \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - std::is_same::value ? true : \ - CC_FOR_EACH_HASH( CC_HAS_HASH_SLOT, ty ) \ - false \ -) \ +#define CC_HAS_HASH( ty ) \ +( \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + CC_IF_CPP20( std::is_same::value ? true : ) \ + std::is_same::value ? true : \ + std::is_same::value ? true : \ + CC_FOR_EACH_HASH( CC_HAS_HASH_SLOT, ty ) \ + false \ +) \ #define CC_KEY_LOAD_SLOT( n, arg ) \ std::is_same< \ @@ -5712,82 +8475,114 @@ cc_layout( #else #define CC_EL_DTOR_SLOT( n, arg ) cc_dtor_##n##_ty: cc_dtor_##n##_fn, -#define CC_EL_DTOR( cntr ) \ -_Generic( (CC_EL_TY( cntr )){ 0 }, \ - CC_FOR_EACH_DTOR( CC_EL_DTOR_SLOT, ) \ - default: (cc_dtor_fnptr_ty)NULL \ -) \ +#define CC_EL_DTOR( cntr ) \ +_Generic( (CC_EL_TY( cntr )){ 0 }, \ + CC_FOR_EACH_DTOR( CC_EL_DTOR_SLOT, ) \ + default: _Generic( (CC_EL_TY( cntr )){ 0 }, \ + CC_STR_RAW( cc_maybe_char ): cc_destroy_str_char, \ + CC_STR_RAW( unsigned char ): cc_destroy_str_unsigned_char, \ + CC_STR_RAW( signed char ): cc_destroy_str_signed_char, \ + CC_STR_RAW( char16_t ): cc_destroy_str_char16, \ + CC_STR_RAW( char32_t ): cc_destroy_str_char32, \ + default: (cc_dtor_fnptr_ty)NULL \ + ) \ +) \ #define CC_KEY_DTOR_SLOT( n, arg ) CC_MAKE_BASE_FNPTR_TY( arg, cc_dtor_##n##_ty ): cc_dtor_##n##_fn, -#define CC_KEY_DTOR( cntr ) \ -_Generic( (**cntr), \ - CC_FOR_EACH_DTOR( CC_KEY_DTOR_SLOT, CC_EL_TY( cntr ) ) \ - default: (cc_dtor_fnptr_ty)NULL \ -) \ +#define CC_KEY_DTOR( cntr ) \ +_Generic( (**cntr), \ + CC_FOR_EACH_DTOR( CC_KEY_DTOR_SLOT, CC_EL_TY( cntr ) ) \ + default: _Generic( (**cntr), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ): cc_destroy_str_char, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ): cc_destroy_str_unsigned_char, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ): cc_destroy_str_signed_char, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ): cc_destroy_str_char16, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ): cc_destroy_str_char32, \ + default: (cc_dtor_fnptr_ty)NULL \ + ) \ +) \ #define CC_KEY_CMPR_SLOT( n, arg ) CC_MAKE_BASE_FNPTR_TY( arg, cc_cmpr_##n##_ty ): cc_cmpr_##n##_fn_select, -#define CC_KEY_CMPR( cntr ) \ -_Generic( (**cntr), \ - CC_FOR_EACH_CMPR( CC_KEY_CMPR_SLOT, CC_EL_TY( cntr ) ) \ - default: _Generic( (**cntr), \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_char ): cc_cmpr_char_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned char ): cc_cmpr_unsigned_char_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), signed char ): cc_cmpr_signed_char_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned short ): cc_cmpr_unsigned_short_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), short ): cc_cmpr_short_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned int ): cc_cmpr_unsigned_int_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), int ): cc_cmpr_int_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long ): cc_cmpr_unsigned_long_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long ): cc_cmpr_long_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long long ): cc_cmpr_unsigned_long_long_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long long ): cc_cmpr_long_long_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_size_t ): cc_cmpr_size_t_select, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), char * ): cc_cmpr_c_string_select, \ - default: cc_cmpr_dummy_select \ - ) \ -)( CC_CNTR_ID( cntr ) ) \ +#define CC_KEY_CMPR( cntr ) \ +_Generic( (**cntr), \ + CC_FOR_EACH_CMPR( CC_KEY_CMPR_SLOT, CC_EL_TY( cntr ) ) \ + default: _Generic( (**cntr), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_char ): cc_cmpr_char_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned char ): cc_cmpr_unsigned_char_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), signed char ): cc_cmpr_signed_char_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned short ): cc_cmpr_unsigned_short_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), short ): cc_cmpr_short_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned int ): cc_cmpr_unsigned_int_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), int ): cc_cmpr_int_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long ): cc_cmpr_unsigned_long_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long ): cc_cmpr_long_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long long ): cc_cmpr_unsigned_long_long_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long long ): cc_cmpr_long_long_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_size_t ): cc_cmpr_size_t_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), char * ): cc_cmpr_cstring_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), const char * ): cc_cmpr_cstring_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ): cc_cmpr_str_char_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ): cc_cmpr_str_unsigned_char_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ): cc_cmpr_str_signed_char_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ): cc_cmpr_str_char16_select, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ): cc_cmpr_str_char32_select, \ + default: cc_cmpr_dummy_select \ + ) \ +)( CC_CNTR_ID( cntr ) ) \ #define CC_KEY_HASH_SLOT( n, arg ) CC_MAKE_BASE_FNPTR_TY( arg, cc_hash_##n##_ty ): cc_hash_##n##_fn, -#define CC_KEY_HASH( cntr ) \ -_Generic( (**cntr), \ - CC_FOR_EACH_HASH( CC_KEY_HASH_SLOT, CC_EL_TY( cntr ) ) \ - default: _Generic( (**cntr), \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_char ): cc_hash_char, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned char ): cc_hash_unsigned_char, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), signed char ): cc_hash_signed_char, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned short ): cc_hash_unsigned_short, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), short ): cc_hash_short, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned int ): cc_hash_unsigned_int, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), int ): cc_hash_int, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long ): cc_hash_unsigned_long, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long ): cc_hash_long, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long long ): cc_hash_unsigned_long_long, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long long ): cc_hash_long_long, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_size_t ): cc_hash_size_t, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), char * ): cc_hash_c_string, \ - default: (cc_hash_fnptr_ty)NULL \ - ) \ -) \ +#define CC_KEY_HASH( cntr ) \ +_Generic( (**cntr), \ + CC_FOR_EACH_HASH( CC_KEY_HASH_SLOT, CC_EL_TY( cntr ) ) \ + default: _Generic( (**cntr), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_char ): cc_hash_char, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned char ): cc_hash_unsigned_char, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), signed char ): cc_hash_signed_char, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned short ): cc_hash_unsigned_short, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), short ): cc_hash_short, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned int ): cc_hash_unsigned_int, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), int ): cc_hash_int, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long ): cc_hash_unsigned_long, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long ): cc_hash_long, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long long ): cc_hash_unsigned_long_long, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long long ): cc_hash_long_long, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_size_t ): cc_hash_size_t, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), char * ): cc_hash_cstring, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), const char * ): cc_hash_cstring, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ): cc_hash_str_char, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ): cc_hash_str_unsigned_char, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ): cc_hash_str_signed_char, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ): cc_hash_str_char16, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ): cc_hash_str_char32, \ + default: (cc_hash_fnptr_ty)NULL \ + ) \ +) \ #define CC_HAS_CMPR_SLOT( n, arg ) cc_cmpr_##n##_ty: true, #define CC_HAS_CMPR( ty ) \ _Generic( (ty){ 0 }, \ CC_FOR_EACH_CMPR( CC_HAS_CMPR_SLOT, ) \ default: _Generic( (ty){ 0 }, \ - cc_maybe_char: true, \ - unsigned char: true, \ - signed char: true, \ - unsigned short: true, \ - short: true, \ - unsigned int: true, \ - int: true, \ - unsigned long: true, \ - long: true, \ - unsigned long long: true, \ - long long: true, \ - cc_maybe_size_t: true, \ - char *: true, \ - default: false \ + cc_maybe_char: true, \ + unsigned char: true, \ + signed char: true, \ + unsigned short: true, \ + short: true, \ + unsigned int: true, \ + int: true, \ + unsigned long: true, \ + long: true, \ + unsigned long long: true, \ + long long: true, \ + cc_maybe_size_t: true, \ + char *: true, \ + const char *: true, \ + CC_STR_RAW( cc_maybe_char ): true, \ + CC_STR_RAW( unsigned char ): true, \ + CC_STR_RAW( signed char ): true, \ + CC_STR_RAW( char16_t ): true, \ + CC_STR_RAW( char32_t ): true, \ + default: false \ ) \ ) \ @@ -5796,20 +8591,26 @@ _Generic( (ty){ 0 }, \ _Generic( (ty){ 0 }, \ CC_FOR_EACH_HASH( CC_HAS_HASH_SLOT, ) \ default: _Generic( (ty){ 0 }, \ - cc_maybe_char: true, \ - unsigned char: true, \ - signed char: true, \ - unsigned short: true, \ - short: true, \ - unsigned int: true, \ - int: true, \ - unsigned long: true, \ - long: true, \ - unsigned long long: true, \ - long long: true, \ - cc_maybe_size_t: true, \ - char *: true, \ - default: false \ + cc_maybe_char: true, \ + unsigned char: true, \ + signed char: true, \ + unsigned short: true, \ + short: true, \ + unsigned int: true, \ + int: true, \ + unsigned long: true, \ + long: true, \ + unsigned long long: true, \ + long long: true, \ + cc_maybe_size_t: true, \ + char *: true, \ + const char *: true, \ + CC_STR_RAW( cc_maybe_char ): true, \ + CC_STR_RAW( unsigned char ): true, \ + CC_STR_RAW( signed char ): true, \ + CC_STR_RAW( char16_t ): true, \ + CC_STR_RAW( char32_t ): true, \ + default: false \ ) \ ) \ @@ -5824,39 +8625,51 @@ _Generic( (**cntr), \ CC_MAKE_BASE_FNPTR_TY( arg, cc_cmpr_##n##_ty ): \ ( cc_key_details_ty ){ sizeof( cc_cmpr_##n##_ty ), alignof( cc_cmpr_##n##_ty ) }, \ -#define CC_KEY_DETAILS( cntr ) \ -_Generic( (**cntr), \ - CC_FOR_EACH_CMPR( CC_KEY_DETAILS_SLOT, CC_EL_TY( cntr ) ) \ - default: _Generic( (**cntr), \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_char ): \ - ( cc_key_details_ty ){ sizeof( char ), alignof( char ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned char ) : \ - ( cc_key_details_ty ){ sizeof( unsigned char ), alignof( unsigned char ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), signed char ) : \ - ( cc_key_details_ty ){ sizeof( signed char ), alignof( signed char ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned short ) : \ - ( cc_key_details_ty ){ sizeof( unsigned short ), alignof( unsigned short ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), short ) : \ - ( cc_key_details_ty ){ sizeof( short ), alignof( short ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned int ) : \ - ( cc_key_details_ty ){ sizeof( unsigned int ), alignof( unsigned int ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), int ) : \ - ( cc_key_details_ty ){ sizeof( int ), alignof( int ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long ): \ - ( cc_key_details_ty ){ sizeof( unsigned long ), alignof( unsigned long ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long ): \ - ( cc_key_details_ty ){ sizeof( long ), alignof( long ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long long ): \ - ( cc_key_details_ty ){ sizeof( unsigned long long ), alignof( unsigned long long ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long long ): \ - ( cc_key_details_ty ){ sizeof( long long ), alignof( long long ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_size_t ): \ - ( cc_key_details_ty ){ sizeof( size_t ), alignof( size_t ) }, \ - CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), char * ): \ - ( cc_key_details_ty ){ sizeof( char * ), alignof( char * ) }, \ - default: ( cc_key_details_ty ){ 0 } \ - ) \ -) \ +#define CC_KEY_DETAILS( cntr ) \ +_Generic( (**cntr), \ + CC_FOR_EACH_CMPR( CC_KEY_DETAILS_SLOT, CC_EL_TY( cntr ) ) \ + default: _Generic( (**cntr), \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_char ): \ + ( cc_key_details_ty ){ sizeof( char ), alignof( char ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned char ) : \ + ( cc_key_details_ty ){ sizeof( unsigned char ), alignof( unsigned char ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), signed char ) : \ + ( cc_key_details_ty ){ sizeof( signed char ), alignof( signed char ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned short ) : \ + ( cc_key_details_ty ){ sizeof( unsigned short ), alignof( unsigned short ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), short ) : \ + ( cc_key_details_ty ){ sizeof( short ), alignof( short ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned int ) : \ + ( cc_key_details_ty ){ sizeof( unsigned int ), alignof( unsigned int ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), int ) : \ + ( cc_key_details_ty ){ sizeof( int ), alignof( int ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long ): \ + ( cc_key_details_ty ){ sizeof( unsigned long ), alignof( unsigned long ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long ): \ + ( cc_key_details_ty ){ sizeof( long ), alignof( long ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), unsigned long long ): \ + ( cc_key_details_ty ){ sizeof( unsigned long long ), alignof( unsigned long long ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), long long ): \ + ( cc_key_details_ty ){ sizeof( long long ), alignof( long long ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), cc_maybe_size_t ): \ + ( cc_key_details_ty ){ sizeof( size_t ), alignof( size_t ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), char * ): \ + ( cc_key_details_ty ){ sizeof( char * ), alignof( char * ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), const char * ): \ + ( cc_key_details_ty ){ sizeof( const char * ), alignof( const char * ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( cc_maybe_char ) ): \ + ( cc_key_details_ty ){ sizeof( CC_STR_RAW( char ) ), alignof( CC_STR_RAW( char ) ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( unsigned char ) ): \ + ( cc_key_details_ty ){ sizeof( CC_STR_RAW( unsigned char ) ), alignof( CC_STR_RAW( unsigned char ) ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( signed char ) ): \ + ( cc_key_details_ty ){ sizeof( CC_STR_RAW( signed char ) ), alignof( CC_STR_RAW( signed char ) ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char16_t ) ): \ + ( cc_key_details_ty ){ sizeof( CC_STR_RAW( char16_t ) ), alignof( CC_STR_RAW( char16_t ) ) }, \ + CC_MAKE_BASE_FNPTR_TY( CC_EL_TY( cntr ), CC_STR_RAW( char32_t ) ): \ + ( cc_key_details_ty ){ sizeof( CC_STR_RAW( char32_t ) ), alignof( CC_STR_RAW( char32_t ) ) }, \ + default: ( cc_key_details_ty ){ 0 } \ + ) \ +) \ #define CC_LAYOUT( cntr ) \ cc_layout( CC_CNTR_ID( cntr ), CC_EL_SIZE( cntr ), alignof( CC_EL_TY( cntr ) ), CC_KEY_DETAILS( cntr ) ) \ @@ -5933,109 +8746,469 @@ CC_DEFAULT_INTEGER_CMPR_HASH_FUNCTIONS( unsigned long long, unsigned_long_long ) CC_DEFAULT_INTEGER_CMPR_HASH_FUNCTIONS( long long, long_long ) CC_DEFAULT_INTEGER_CMPR_HASH_FUNCTIONS( size_t, size_t ) -// In MSVC under C, char is an alias for unsigned char or signed char, contrary to the C Standard, which requires all -// three to be distinct types. -// To accommodate this bug, we have to ensure that char doesn't clash with either of the other two types in _Generic -// statements. -// If char is an alias, cc_maybe_char will be a dummy type used in no other context. -// Otherwise, it will be an alias for char. - -// size_t needs to be handled in a similar way because it could be an alias for a fundamental integer type or a distinct -// builtin type. - -#ifndef __cplusplus - -typedef struct { char nothing; } cc_char_dummy; - -typedef CC_TYPEOF_XP( - _Generic( (char){ 0 }, - unsigned char: (cc_char_dummy){ 0 }, - signed char: (cc_char_dummy){ 0 }, - default: (char){ 0 } - ) -) cc_maybe_char; - -typedef struct { char nothing; } cc_size_t_dummy; - -typedef CC_TYPEOF_XP( - _Generic( (size_t){ 0 }, - unsigned short: (cc_size_t_dummy){ 0 }, - short: (cc_size_t_dummy){ 0 }, - unsigned int: (cc_size_t_dummy){ 0 }, - int: (cc_size_t_dummy){ 0 }, - unsigned long: (cc_size_t_dummy){ 0 }, - long: (cc_size_t_dummy){ 0 }, - unsigned long long: (cc_size_t_dummy){ 0 }, - long long: (cc_size_t_dummy){ 0 }, - default: (size_t){ 0 } - ) -) cc_maybe_size_t; +// For hashing both CC strings and regular C strings, we use the public-domain Wyhash +// (https://github.com/wangyi-fudan/wyhash) with the following modifications: +// * We use a fixed seed and secret (the defaults suggested in the Wyhash repository). +// * We do not handle endianness, so the result will differ depending on the platform. +// * We omit the code optimized for 32-bit platforms. +static inline void cc_wymum( uint64_t *a, uint64_t *b ) +{ +#if defined( __SIZEOF_INT128__ ) + __uint128_t r = *a; + r *= *b; + *a = (uint64_t)r; + *b = (uint64_t)( r >> 64 ); +#elif defined( _MSC_VER ) && defined( _M_X64 ) + *a = _umul128( *a, *b, b ); +#else + uint64_t ha = *a >> 32; + uint64_t hb = *b >> 32; + uint64_t la = (uint32_t)*a; + uint64_t lb = (uint32_t)*b; + uint64_t rh = ha * hb; + uint64_t rm0 = ha * lb; + uint64_t rm1 = hb * la; + uint64_t rl = la * lb; + uint64_t t = rl + ( rm0 << 32 ); + uint64_t c = t < rl; + uint64_t lo = t + ( rm1 << 32 ); + c += lo < t; + uint64_t hi = rh + ( rm0 >> 32 ) + ( rm1 >> 32 ) + c; + *a = lo; + *b = hi; #endif +} + +static inline uint64_t cc_wymix( uint64_t a, uint64_t b ) +{ + cc_wymum( &a, &b ); + return a ^ b; +} + +static inline uint64_t cc_wyr8( const unsigned char *p ) +{ + uint64_t v; + memcpy( &v, p, 8 ); + return v; +} + +static inline uint64_t cc_wyr4( const unsigned char *p ) +{ + uint32_t v; + memcpy( &v, p, 4 ); + return v; +} + +static inline uint64_t cc_wyr3( const unsigned char *p, size_t k ) +{ + return ( ( (uint64_t)p[ 0 ] ) << 16 ) | ( ( (uint64_t)p[ k >> 1 ] ) << 8 ) | p[ k - 1 ]; +} + +static inline size_t cc_wyhash( const void *key, size_t len ) +{ + const unsigned char *p = (const unsigned char *)key; + uint64_t seed = 0xCA813BF4C7ABF0A9ULL; + uint64_t a; + uint64_t b; + if( CC_LIKELY( len <= 16 ) ) + { + if( CC_LIKELY( len >= 4 ) ) + { + a = ( cc_wyr4( p ) << 32 ) | cc_wyr4( p + ( ( len >> 3 ) << 2 ) ); + b = ( cc_wyr4( p + len - 4 ) << 32 ) | cc_wyr4( p + len - 4 - ( ( len >> 3 ) << 2 ) ); + } + else if( CC_LIKELY( len > 0 ) ) + { + a = cc_wyr3( p, len ); + b = 0; + } + else + { + a = 0; + b = 0; + } + } + else + { + size_t i = len; + if( CC_UNLIKELY( i >= 48 ) ) + { + uint64_t see1 = seed; + uint64_t see2 = seed; + do{ + seed = cc_wymix( cc_wyr8( p ) ^ 0x8BB84B93962EACC9ULL, cc_wyr8( p + 8 ) ^ seed ); + see1 = cc_wymix( cc_wyr8( p + 16 ) ^ 0x4B33A62ED433D4A3ULL, cc_wyr8( p + 24 ) ^ see1 ); + see2 = cc_wymix( cc_wyr8( p + 32 ) ^ 0x4D5A2DA51DE1AA47ULL, cc_wyr8( p + 40 ) ^ see2 ); + p += 48; + i -= 48; + } + while( CC_LIKELY( i >= 48 ) ); + seed ^= see1 ^ see2; + } + + while( CC_UNLIKELY( i > 16 ) ) + { + seed = cc_wymix( cc_wyr8( p ) ^ 0x8BB84B93962EACC9ULL, cc_wyr8( p + 8 ) ^ seed ); + i -= 16; + p += 16; + } + + a = cc_wyr8( p + i - 16 ); + b = cc_wyr8( p + i - 8 ); + } + + a ^= 0x8BB84B93962EACC9ULL; + b ^= seed; + cc_wymum( &a, &b ); + return (size_t)cc_wymix( a ^ 0x2D358DCCAA6C78A5ULL ^ len, b ^ 0x8BB84B93962EACC9ULL ); +} // Null-terminated C strings. -// We use FNV-1a because newer, faster alternatives that process word-sized chunks require prior knowledge of the -// string's length. -static inline int cc_cmpr_c_string_three_way( void *void_val_1, void *void_val_2 ) +static inline int cc_cmpr_cstring_three_way( void *void_val_1, void *void_val_2 ) { return strcmp( *(char **)void_val_1, *(char **)void_val_2 ); } -static inline int cc_cmpr_c_string_equal( void *void_val_1, void *void_val_2 ) +static inline int cc_cmpr_cstring_equal( void *void_val_1, void *void_val_2 ) { return strcmp( *(char **)void_val_1, *(char **)void_val_2 ) == 0; } -static inline cc_cmpr_fnptr_ty cc_cmpr_c_string_select( size_t cntr_id ) \ -{ \ - return cntr_id == CC_MAP || cntr_id == CC_SET ? cc_cmpr_c_string_equal : cc_cmpr_c_string_three_way; \ -} \ - -#if SIZE_MAX == 0xFFFFFFFF // 32-bit size_t. - -static inline size_t cc_hash_c_string( void *void_val ) +static inline cc_cmpr_fnptr_ty cc_cmpr_cstring_select( size_t cntr_id ) { - char *val = *(char **)void_val; - size_t hash = 0x01000193; - while( *val ) - hash = ( (unsigned char)*val++ ^ hash ) * 0x811C9DC5; - - return hash; + return cntr_id == CC_MAP || cntr_id == CC_SET ? cc_cmpr_cstring_equal : cc_cmpr_cstring_three_way; } -#elif SIZE_MAX == 0xFFFFFFFFFFFFFFFF // 64-bit size_t. - -static inline size_t cc_hash_c_string( void *void_val ) +static inline size_t cc_hash_cstring( void *void_val ) { - char *val = *(char **)void_val; - size_t hash = 0xCBF29CE484222325; - while( *val ) - hash = ( (unsigned char)*val++ ^ hash ) * 0x100000001B3; - - return hash; + const char *val = *(const char **)void_val; + return cc_wyhash( val, strlen( val ) ); } -#else // Strange size_t. +// CC strings. -static inline size_t cc_hash_c_string( void *void_val ) +static inline int cc_cmpr_str_char_three_way( void *void_val_1, void *void_val_2 ) { - char *val = *(char **)void_val; - size_t hash = 0; - while( *val ) - hash = hash * 131 + (unsigned char)*val++; - - return hash; + return strcmp( + (const char *)cc_str_first( *(CC_STR_RAW( char ) *)void_val_1, 0 /* Dummy */, 0 /* Dummy */ ), + (const char *)cc_str_first( *(CC_STR_RAW( char ) *)void_val_2, 0 /* Dummy */, 0 /* Dummy */ ) + ); } +static inline int cc_cmpr_str_unsigned_char_three_way( void *void_val_1, void *void_val_2 ) +{ + return strcmp( + (const char *)cc_str_first( *(CC_STR_RAW( unsigned char ) *)void_val_1, 0 /* Dummy */, 0 /* Dummy */ ), + (const char *)cc_str_first( *(CC_STR_RAW( unsigned char ) *)void_val_2, 0 /* Dummy */, 0 /* Dummy */ ) + ); +} + +static inline int cc_cmpr_str_signed_char_three_way( void *void_val_1, void *void_val_2 ) +{ + return strcmp( + (const char *)cc_str_first( *(CC_STR_RAW( signed char ) *)void_val_1, 0 /* Dummy */, 0 /* Dummy */ ), + (const char *)cc_str_first( *(CC_STR_RAW( signed char ) *)void_val_2, 0 /* Dummy */, 0 /* Dummy */ ) + ); +} + +#if defined( __cplusplus ) && __cplusplus >= 202101L +static inline int cc_cmpr_str_char8_three_way( void *void_val_1, void *void_val_2 ) +{ + return strcmp( + (const char *)cc_str_first( *(CC_STR_RAW( char8_t ) *)void_val_1, 0 /* Dummy */, 0 /* Dummy */ ), + (const char *)cc_str_first( *(CC_STR_RAW( char8_t ) *)void_val_2, 0 /* Dummy */, 0 /* Dummy */ ) + ); +} #endif +static inline int cc_cmpr_str_char16_three_way( void *void_val_1, void *void_val_2 ) +{ + char16_t *els_1 = (char16_t *)cc_str_first( *(CC_STR_RAW( char16_t ) *)void_val_1, 0 /* Dummy */, 0 /* Dummy */ ); + char16_t *els_2 = (char16_t *)cc_str_first( *(CC_STR_RAW( char16_t ) *)void_val_2, 0 /* Dummy */, 0 /* Dummy */ ); + + while( true ) + { + if( *els_1 < *els_2 ) + return -1; + + if( *els_1 > *els_2 ) + return 1; + + if( !*els_1 ) + return 0; + + ++els_1; + ++els_2; + } +} + +static inline int cc_cmpr_str_char32_three_way( void *void_val_1, void *void_val_2 ) +{ + char32_t *els_1 = (char32_t *)cc_str_first( *(CC_STR_RAW( char32_t ) *)void_val_1, 0 /* Dummy */, 0 /* Dummy */ ); + char32_t *els_2 = (char32_t *)cc_str_first( *(CC_STR_RAW( char32_t ) *)void_val_2, 0 /* Dummy */, 0 /* Dummy */ ); + + while( true ) + { + if( *els_1 < *els_2 ) + return -1; + + if( *els_1 > *els_2 ) + return 1; + + if( !*els_1 ) + return 0; + + ++els_1; + ++els_2; + } +} + +static inline int cc_cmpr_str_char_equal( void *void_val_1, void *void_val_2 ) +{ + CC_STR_RAW( char ) val_1 = *(CC_STR_RAW( char ) *)void_val_1; + CC_STR_RAW( char ) val_2 = *(CC_STR_RAW( char ) *)void_val_2; + + if( cc_str_size( val_1 ) != cc_str_size( val_2 ) ) + return false; + + return memcmp( + cc_str_first( val_1, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_first( val_2, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_size( val_1 ) + ) == 0; +} + +static inline int cc_cmpr_str_unsigned_char_equal( void *void_val_1, void *void_val_2 ) +{ + CC_STR_RAW( unsigned char ) val_1 = *(CC_STR_RAW( unsigned char ) *)void_val_1; + CC_STR_RAW( unsigned char ) val_2 = *(CC_STR_RAW( unsigned char ) *)void_val_2; + + if( cc_str_size( val_1 ) != cc_str_size( val_2 ) ) + return false; + + return memcmp( + cc_str_first( val_1, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_first( val_2, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_size( val_1 ) + ) == 0; +} + +static inline int cc_cmpr_str_signed_char_equal( void *void_val_1, void *void_val_2 ) +{ + CC_STR_RAW( signed char ) val_1 = *(CC_STR_RAW( signed char ) *)void_val_1; + CC_STR_RAW( signed char ) val_2 = *(CC_STR_RAW( signed char ) *)void_val_2; + + if( cc_str_size( val_1 ) != cc_str_size( val_2 ) ) + return false; + + return memcmp( + cc_str_first( val_1, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_first( val_2, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_size( val_1 ) + ) == 0; +} + +#if defined( __cplusplus ) && __cplusplus >= 202101L +static inline int cc_cmpr_str_char8_equal( void *void_val_1, void *void_val_2 ) +{ + CC_STR_RAW( char8_t ) val_1 = *(CC_STR_RAW( char8_t ) *)void_val_1; + CC_STR_RAW( char8_t ) val_2 = *(CC_STR_RAW( char8_t ) *)void_val_2; + + if( cc_str_size( val_1 ) != cc_str_size( val_2 ) ) + return false; + + return memcmp( + cc_str_first( val_1, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_first( val_2, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_size( val_1 ) + ) == 0; +} +#endif + +static inline int cc_cmpr_str_char16_equal( void *void_val_1, void *void_val_2 ) +{ + CC_STR_RAW( char16_t ) val_1 = *(CC_STR_RAW( char16_t ) *)void_val_1; + CC_STR_RAW( char16_t ) val_2 = *(CC_STR_RAW( char16_t ) *)void_val_2; + + if( cc_str_size( val_1 ) != cc_str_size( val_2 ) ) + return false; + + return memcmp( + cc_str_first( val_1, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_first( val_2, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_size( val_1 ) + ) == 0; +} + +static inline int cc_cmpr_str_char32_equal( void *void_val_1, void *void_val_2 ) +{ + CC_STR_RAW( char32_t ) val_1 = *(CC_STR_RAW( char32_t ) *)void_val_1; + CC_STR_RAW( char32_t ) val_2 = *(CC_STR_RAW( char32_t ) *)void_val_2; + + if( cc_str_size( val_1 ) != cc_str_size( val_2 ) ) + return false; + + return memcmp( + cc_str_first( val_1, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_first( val_2, 0 /* Dummy */, 0 /* Dummy */ ), + cc_str_size( val_1 ) + ) == 0; +} + +static inline cc_cmpr_fnptr_ty cc_cmpr_str_char_select( size_t cntr_id ) +{ + return cntr_id == CC_MAP || cntr_id == CC_SET ? cc_cmpr_str_char_equal : cc_cmpr_str_char_three_way; +} + +static inline cc_cmpr_fnptr_ty cc_cmpr_str_unsigned_char_select( size_t cntr_id ) +{ + return cntr_id == CC_MAP || cntr_id == CC_SET ? cc_cmpr_str_unsigned_char_equal : cc_cmpr_str_unsigned_char_three_way; +} + +static inline cc_cmpr_fnptr_ty cc_cmpr_str_signed_char_select( size_t cntr_id ) +{ + return cntr_id == CC_MAP || cntr_id == CC_SET ? cc_cmpr_str_signed_char_equal : cc_cmpr_str_signed_char_three_way; +} + +#if defined( __cplusplus ) && __cplusplus >= 202101L +static inline cc_cmpr_fnptr_ty cc_cmpr_str_char8_select( size_t cntr_id ) +{ + return cntr_id == CC_MAP || cntr_id == CC_SET ? cc_cmpr_str_char8_equal : cc_cmpr_str_char8_three_way; +} +#endif + +static inline cc_cmpr_fnptr_ty cc_cmpr_str_char16_select( size_t cntr_id ) +{ + return cntr_id == CC_MAP || cntr_id == CC_SET ? cc_cmpr_str_char16_equal : cc_cmpr_str_char16_three_way; +} + +static inline cc_cmpr_fnptr_ty cc_cmpr_str_char32_select( size_t cntr_id ) +{ + return cntr_id == CC_MAP || cntr_id == CC_SET ? cc_cmpr_str_char32_equal : cc_cmpr_str_char32_three_way; +} + +static inline size_t cc_hash_str_char( void *void_val ) +{ + CC_STR_RAW( char ) val = *(CC_STR_RAW( char ) *)void_val; + return cc_wyhash( cc_str_first( val, 0 /* Dummy */, 0 /* Dummy */ ), cc_str_size( val ) ); +} + +static inline size_t cc_hash_str_unsigned_char( void *void_val ) +{ + CC_STR_RAW( unsigned char ) val = *(CC_STR_RAW( unsigned char ) *)void_val; + return cc_wyhash( cc_str_first( val, 0 /* Dummy */, 0 /* Dummy */ ), cc_str_size( val ) ); +} + +static inline size_t cc_hash_str_signed_char( void *void_val ) +{ + CC_STR_RAW( signed char ) val = *(CC_STR_RAW( signed char ) *)void_val; + return cc_wyhash( cc_str_first( val, 0 /* Dummy */, 0 /* Dummy */ ), cc_str_size( val ) ); +} + +#if defined( __cplusplus ) && __cplusplus >= 202101L +static inline size_t cc_hash_str_char8( void *void_val ) +{ + CC_STR_RAW( char8_t ) val = *(CC_STR_RAW( char8_t ) *)void_val; + return cc_wyhash( cc_str_first( val, 0 /* Dummy */, 0 /* Dummy */ ), cc_str_size( val ) ); +} +#endif + +static inline size_t cc_hash_str_char16( void *void_val ) +{ + CC_STR_RAW( char16_t ) val = *(CC_STR_RAW( char16_t ) *)void_val; + return cc_wyhash( cc_str_first( val, 0 /* Dummy */, 0 /* Dummy */ ), cc_str_size( val ) * sizeof( char16_t ) ); +} + +static inline size_t cc_hash_str_char32( void *void_val ) +{ + CC_STR_RAW( char32_t ) val = *(CC_STR_RAW( char32_t ) *)void_val; + return cc_wyhash( cc_str_first( val, 0 /* Dummy */, 0 /* Dummy */ ), cc_str_size( val ) * sizeof( char32_t ) ); +} + // Dummy for containers with no comparison function. static inline CC_ALWAYS_INLINE cc_cmpr_fnptr_ty cc_cmpr_dummy_select( CC_UNUSED( size_t, cntr_id ) ) { return NULL; } +// Default destructors for CC strings. + +static inline void cc_destroy_str_char( void *void_val, cc_free_fnptr_ty free_ ) +{ + cc_str_cleanup( + *(CC_STR_RAW( char ) *)void_val, + sizeof( char ), + 0, // Dummy. + NULL, // Dummy. + NULL, // Dummy. + free_ + ); +} + +static inline void cc_destroy_str_unsigned_char( void *void_val, cc_free_fnptr_ty free_ ) +{ + cc_str_cleanup( + *(CC_STR_RAW( unsigned char ) *)void_val, + sizeof( char ), + 0, // Dummy. + NULL, // Dummy. + NULL, // Dummy. + free_ + ); +} + +static inline void cc_destroy_str_signed_char( void *void_val, cc_free_fnptr_ty free_ ) +{ + cc_str_cleanup( + *(CC_STR_RAW( signed char ) *)void_val, + sizeof( char ), + 0, // Dummy. + NULL, // Dummy. + NULL, // Dummy. + free_ + ); +} + +#if defined( __cplusplus ) && __cplusplus >= 202101L +static inline void cc_destroy_str_char8( void *void_val, cc_free_fnptr_ty free_ ) +{ + cc_str_cleanup( + *(CC_STR_RAW( char8_t ) *)void_val, + sizeof( char ), + 0, // Dummy. + NULL, // Dummy. + NULL, // Dummy. + free_ + ); +} +#endif + +static inline void cc_destroy_str_char16( void *void_val, cc_free_fnptr_ty free_ ) +{ + cc_str_cleanup( + *(CC_STR_RAW( char16_t ) *)void_val, + sizeof( char ), + 0, // Dummy. + NULL, // Dummy. + NULL, // Dummy. + free_ + ); +} + +static inline void cc_destroy_str_char32( void *void_val, cc_free_fnptr_ty free_ ) +{ + cc_str_cleanup( + *(CC_STR_RAW( char32_t ) *)void_val, + sizeof( char ), + 0, // Dummy. + NULL, // Dummy. + NULL, // Dummy. + free_ + ); +} + #endif #else/*---------------------------------------------------------------------------------------------------------------*/ @@ -6051,7 +9224,7 @@ static inline CC_ALWAYS_INLINE cc_cmpr_fnptr_ty cc_cmpr_dummy_select( CC_UNUSED( typedef CC_TYPEOF_TY( CC_1ST_ARG( CC_DTOR ) ) CC_CAT_3( cc_dtor_, CC_N_DTORS, _ty ); -static inline void CC_CAT_3( cc_dtor_, CC_N_DTORS, _fn )( void *void_val ) +static inline void CC_CAT_3( cc_dtor_, CC_N_DTORS, _fn )( void *void_val, CC_UNUSED( cc_free_fnptr_ty, free_ ) ) { CC_CAT_3( cc_dtor_, CC_N_DTORS, _ty ) val = *(CC_CAT_3( cc_dtor_, CC_N_DTORS, _ty ) *)void_val; CC_OTHER_ARGS( CC_DTOR ) @@ -6145,7 +9318,7 @@ typedef CC_TYPEOF_TY( CC_1ST_ARG( CC_CMPR ) ) CC_CAT_3( cc_cmpr_, CC_N_CMPRS, _t // This allows the compiler to optimize the comparison for best performance in both cases without burdening the user // with having to define two separate comparison functions for a single type. -static inline CC_ALWAYS_INLINE int CC_CAT_3( cc_cmpr_, CC_N_CMPRS, _fn_three_way )( void *void_val_1, void *void_val_2 ) +static inline int CC_CAT_3( cc_cmpr_, CC_N_CMPRS, _fn_three_way )( void *void_val_1, void *void_val_2 ) { CC_CAT_3( cc_cmpr_, CC_N_CMPRS, _ty ) val_1 = *(CC_CAT_3( cc_cmpr_, CC_N_CMPRS, _ty ) *)void_val_1; CC_CAT_3( cc_cmpr_, CC_N_CMPRS, _ty ) val_2 = *(CC_CAT_3( cc_cmpr_, CC_N_CMPRS, _ty ) *)void_val_2; From fe84daecfe7598c0001d20c40a05f911f7467b42 Mon Sep 17 00:00:00 2001 From: fedir Date: Thu, 1 May 2025 20:54:39 +0200 Subject: [PATCH 10/33] Removed `const` where it was simply wrong. --- src/fuse_operations.c | 2 +- src/process_info.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fuse_operations.c b/src/fuse_operations.c index 8c6077e..269a6b7 100644 --- a/src/fuse_operations.c +++ b/src/fuse_operations.c @@ -42,7 +42,7 @@ #include "sourcefs.h" #include "ui-socket.h" -const char *get_process_name_by_pid(const int pid) { +char *get_process_name_by_pid(const int pid) { char path[1024]; sprintf(path, "/proc/%d/exe", pid); diff --git a/src/process_info.h b/src/process_info.h index 9645524..200feb9 100644 --- a/src/process_info.h +++ b/src/process_info.h @@ -12,7 +12,7 @@ #include struct process_info { pid_t PID; - const char *name; + char *name; }; #endif // PROCESS_INFO_H From 8cb7721e392b1673ad0c5aef37a44a42f63ccfa0 Mon Sep 17 00:00:00 2001 From: fedir Date: Thu, 1 May 2025 20:55:05 +0200 Subject: [PATCH 11/33] Updated ui-socket to use the new dialogue --- src/ui-socket.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/ui-socket.c b/src/ui-socket.c index ad2549e..f383b77 100644 --- a/src/ui-socket.c +++ b/src/ui-socket.c @@ -11,6 +11,7 @@ #include #include #define _GNU_SOURCE +#include "cc.h" #include "perm_permissions_table.h" #include "real_filename.h" #include "temp_permissions_table.h" @@ -23,7 +24,9 @@ #include #include -#define ZENITY_TEMP_ALLOW_MESSAGE "Allow this time\n" +#define ZENITY_YES 0 +#define ZENITY_NO 1 +#define ZENITY_PERM 2 int init_ui_socket(const char *perm_permissions_db_filename) { FILE *fp = NULL; @@ -66,12 +69,8 @@ void destroy_ui_socket(void) { access_t ask_access(const char *filename, struct process_info proc_info) { FILE *fp = NULL; char *command = NULL; - int ret = - asprintf(&command, - "zenity --question --extra-button \"Allow this time\" --title " - "\"Allow Access?\" --text \"Allow process " - "%s with PID %d to access %s\"", - proc_info.name, proc_info.PID, filename, get_mountpoint()); + int ret = asprintf(&command, "zenity \"%d\" \"%s\" \"%s\" \"%s\"", + proc_info.PID, proc_info.name, filename, get_mountpoint()); if (ret < 0) { // If asprintf fails, the contents of command are undefined (see man @@ -92,24 +91,30 @@ access_t ask_access(const char *filename, struct process_info proc_info) { return DENY; } - // if the user clicks the "Allow this time" button, `zenity` will only - // write it to `stdout`, but the exit code will still be `1`. So, we need - // to manually check the output. - char buffer[sizeof(ZENITY_TEMP_ALLOW_MESSAGE) + 1]; - while (fgets(buffer, sizeof(buffer), fp)) { - printf("%s", buffer); - if (strcmp(buffer, ZENITY_TEMP_ALLOW_MESSAGE) == 0) { - pclose(fp); - return ALLOW_TEMP; - } + str(char) zenity_output; + init(&zenity_output); + + size_t total_read = 0; + char line[1024]; // Buffer to read individual lines + + // Read the command output line by line + while (fgets(line, sizeof(line), fp)) { + size_t line_len = strlen(line); + push_fmt(&zenity_output, line); } int zenity_exit_code = WEXITSTATUS(pclose(fp)); + fprintf(stderr, "zenity wrote out %s\n", first(&zenity_output)); fprintf(stderr, "zenity returned %d\n", zenity_exit_code); - // zenity returns 1 on "No" >:( - if (zenity_exit_code == 0) { + + cleanup(&zenity_output); + + if (zenity_exit_code == (ZENITY_YES | ZENITY_PERM)) { return ALLOW; } + if (zenity_exit_code == ZENITY_YES) { + return ALLOW_TEMP; + } return DENY; } From 82f66a1df3e8429e25f439adf423b7f5a81f8d15 Mon Sep 17 00:00:00 2001 From: fedir Date: Sat, 3 May 2025 10:30:13 +0200 Subject: [PATCH 12/33] Fixed inverted responses --- src/gui/zenity-clone.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/zenity-clone.c b/src/gui/zenity-clone.c index 4efa120..6bd6335 100644 --- a/src/gui/zenity-clone.c +++ b/src/gui/zenity-clone.c @@ -16,7 +16,7 @@ gboolean is_permanent = false; GtkEntryBuffer *entry_buffer = NULL; GtkWidget *checkbox = NULL; -static void negative_response(GtkWindow *window) { +static void positive_response(GtkWindow *window) { fprintf(stdout, "%s", gtk_entry_buffer_get_text(entry_buffer)); exit_code = (gtk_check_button_get_active(GTK_CHECK_BUTTON(checkbox))) ? YES | PERM @@ -24,7 +24,7 @@ static void negative_response(GtkWindow *window) { gtk_window_close(window); } -static void positive_response(GtkWindow *window) { +static void negative_response(GtkWindow *window) { fprintf(stdout, "%s", gtk_entry_buffer_get_text(entry_buffer)); exit_code = (gtk_check_button_get_active(GTK_CHECK_BUTTON(checkbox))) ? NO | PERM From d367d6ffe72c01d9f1c89677d3f4210a3e4ce47c Mon Sep 17 00:00:00 2001 From: fedir Date: Sat, 3 May 2025 10:31:42 +0200 Subject: [PATCH 13/33] Adapted the ui-socket to the new dialogue --- src/ui-socket.c | 107 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 92 insertions(+), 15 deletions(-) diff --git a/src/ui-socket.c b/src/ui-socket.c index f383b77..1daa9dd 100644 --- a/src/ui-socket.c +++ b/src/ui-socket.c @@ -14,8 +14,10 @@ #include "cc.h" #include "perm_permissions_table.h" #include "real_filename.h" +#include "sourcefs.h" #include "temp_permissions_table.h" #include "ui-socket.h" +#include #include #include #include @@ -28,6 +30,11 @@ #define ZENITY_NO 1 #define ZENITY_PERM 2 +struct dialogue_response { + access_t decision; + char *filename; +}; + int init_ui_socket(const char *perm_permissions_db_filename) { FILE *fp = NULL; @@ -66,11 +73,16 @@ void destroy_ui_socket(void) { * @return: access status - ALLOW, DENY or ALLOW_TEMP * allowed for the runtime of the process */ -access_t ask_access(const char *filename, struct process_info proc_info) { +struct dialogue_response ask_access(const char *filename, + struct process_info proc_info) { FILE *fp = NULL; char *command = NULL; int ret = asprintf(&command, "zenity \"%d\" \"%s\" \"%s\" \"%s\"", - proc_info.PID, proc_info.name, filename, get_mountpoint()); + proc_info.PID, proc_info.name, get_mountpoint(), filename); + + struct dialogue_response response; + response.decision = DENY; + response.filename = NULL; if (ret < 0) { // If asprintf fails, the contents of command are undefined (see man @@ -79,7 +91,11 @@ access_t ask_access(const char *filename, struct process_info proc_info) { // justify preparing for this. fprintf(stderr, "Could not create query on rule insertion"); perror(""); - return 1; + response.decision = DENY; + response.filename = malloc(2); + response.filename[0] = '.'; + response.filename[1] = 0; + return response; } // Zenity Question Message Popup @@ -88,18 +104,20 @@ access_t ask_access(const char *filename, struct process_info proc_info) { if (fp == NULL) { perror("Pipe returned a error"); - return DENY; + response.decision = DENY; + response.filename = malloc(2); + response.filename[0] = '.'; + response.filename[1] = 0; + return response; } str(char) zenity_output; init(&zenity_output); - size_t total_read = 0; char line[1024]; // Buffer to read individual lines // Read the command output line by line while (fgets(line, sizeof(line), fp)) { - size_t line_len = strlen(line); push_fmt(&zenity_output, line); } @@ -107,16 +125,28 @@ access_t ask_access(const char *filename, struct process_info proc_info) { fprintf(stderr, "zenity wrote out %s\n", first(&zenity_output)); fprintf(stderr, "zenity returned %d\n", zenity_exit_code); + // if (size(&zenity_output) == 0) { + // push(&zenity_output, '.'); + // } + + assert(strlen(first(&zenity_output)) == size(&zenity_output)); + + response.filename = malloc(size(&zenity_output) + 1); + strcpy(response.filename, first(&zenity_output)); + // response.filename[size(&zenity_output)] = 0; + + // assert(0 == strcmp(response.filename, first(&zenity_output))); cleanup(&zenity_output); if (zenity_exit_code == (ZENITY_YES | ZENITY_PERM)) { - return ALLOW; - } - if (zenity_exit_code == ZENITY_YES) { - return ALLOW_TEMP; + response.decision = ALLOW; + } else if (zenity_exit_code == ZENITY_YES) { + response.decision = ALLOW_TEMP; + } else { + response.decision = DENY; } - return DENY; + return response; } /** @@ -136,20 +166,36 @@ int interactive_access(const char *filename, struct process_info proc_info, access_t access = check_temp_access(real_path, proc_info); if (access == ALLOW) { + fprintf(stderr, + "Permission allowed to %s based on a rule present in the temp " + "permission table.\n", + proc_info.name); free(real_path); return 1; } if (access == DENY) { + fprintf(stderr, + "Permission denied to %s based on a rule present in the temp " + "permission table.\n", + proc_info.name); free(real_path); return 0; } access = check_perm_access(real_path, proc_info); if (access == ALLOW) { + fprintf(stderr, + "Permission allowed to %s based on a rule present in the perm " + "permission table.\n", + proc_info.name); free(real_path); return 1; } if (access == DENY) { + fprintf(stderr, + "Permission denied to %s based on a rule present in the perm " + "permission table.\n", + proc_info.name); free(real_path); return 0; } @@ -158,30 +204,61 @@ int interactive_access(const char *filename, struct process_info proc_info, // permissions are granted if (opts & GRANT_PERM) { + fprintf(stderr, "Permission granted permanently to %s.\n", proc_info.name); give_perm_access(real_path, proc_info); free(real_path); return 1; } if (opts & GRANT_TEMP) { + fprintf(stderr, "Permission granted temporarily to %s.\n", proc_info.name); set_temp_access(real_path, proc_info, SET_ALLOW); free(real_path); return 1; } - access_t user_response = ask_access(real_path, proc_info); - if (user_response == ALLOW) { + struct dialogue_response response = ask_access(filename, proc_info); + // fprintf(stderr, "%s", response.filename); + // assert(0 != strlen(response.filename)); + + // the user might specify a different file in the dialogue, so we need to + // check if it is valid + + /* + while (source_access(response.filename, F_OK)) { + // if it is invalid, just ask again. + fprintf(stderr, "Filename returned by zenty wasn't correct: %s\n", + response.filename); + free(response.filename); + response = ask_access(filename, proc_info); + } + */ + free(real_path); + + real_path = real_filename(response.filename); + free(response.filename); + + if (response.decision == ALLOW) { + fprintf(stderr, + "Permission granted permanently to %s based on zenty response.\n", + proc_info.name); give_perm_access(real_path, proc_info); free(real_path); return 1; } - if (user_response == ALLOW_TEMP) { + if (response.decision == ALLOW_TEMP) { + fprintf(stderr, + "Permission granted temporarily to %s based on zenty response.\n", + proc_info.name); set_temp_access(real_path, proc_info, SET_ALLOW); free(real_path); return 1; } - if (user_response == DENY) { + if (response.decision == DENY) { + fprintf(stderr, + "Permission denied temporarily to %s based on zenty response.\n", + proc_info.name); set_temp_access(real_path, proc_info, SET_DENY); free(real_path); return 0; From 112d514f59f61d02b19f48653ee97e7cf4e740f1 Mon Sep 17 00:00:00 2001 From: fedir Date: Sat, 3 May 2025 10:32:10 +0200 Subject: [PATCH 14/33] Adapted mock zenity to the new dialogue --- test/mock/zenity | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/mock/zenity b/test/mock/zenity index 769de25..35270b4 100755 --- a/test/mock/zenity +++ b/test/mock/zenity @@ -2,20 +2,24 @@ # fake-zenity: script that mocks the behavior of zenity based on the ./.fake-zenity-response file +ZENITY_YES=0 +ZENITY_NO=1 +ZENITY_PERM=2 + if [[ $1 == "--set-fake-response" ]]; then #someone knows we are fake :) - echo $2 >~/.fake_zenity_response + echo "$2" >~/.fake_zenity_response else if [ -f ~/.fake_zenity_response ]; then FAKE_ZENITY_RESPONSE=$(cat ~/.fake_zenity_response) + printf "%s" "$4" if [[ $FAKE_ZENITY_RESPONSE == "yes_tmp" ]]; then - printf "Allow this time\n" - exit 1 + exit "$ZENITY_YES" elif [[ $FAKE_ZENITY_RESPONSE == "no" ]]; then - exit 1 + exit "$ZENITY_NO" elif [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then - exit 0 + exit "$((ZENITY_YES | ZENITY_PERM))" fi fi fi From ac1d7c1535d2b2f82f0f6ac5323bca61f35811cd Mon Sep 17 00:00:00 2001 From: fedir Date: Sat, 3 May 2025 12:03:54 +0200 Subject: [PATCH 15/33] Renamed zenity-clone to icfs-dialogue and improved makefile --- Makefile | 36 +++++++++++++++++---- src/gui/Makefile | 34 +++++++++++-------- src/gui/{zenity-clone.c => icfs_dialogue.c} | 0 test/test.bash | 2 +- 4 files changed, 52 insertions(+), 20 deletions(-) rename src/gui/{zenity-clone.c => icfs_dialogue.c} (100%) diff --git a/Makefile b/Makefile index 81d59b5..0c8386f 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,17 @@ SHELL=/bin/bash # configurable options -SOURCES_DIR := ./src -TESTS_DIR := ./tests -BUILD_DIR := ./build +ifndef ($(SOURCES_DIR)) + SOURCES_DIR := ./src +endif + +ifndef ($(TESTS_DIR)) + TESTS_DIR := ./tests +endif + +ifndef ($(BUILD_DIR)) + BUILD_DIR := ./build +endif CC := gcc CXX := g++ @@ -49,12 +57,18 @@ ifeq ($(TEST), 1) TARGETS += icfs_test endif +ifneq ($(DIALOGUE), 0) + TARGETS += $(BUILD_DIR)/icfs_dialogue +endif # build! default: $(TARGETS) -.PHONY: clean +.PHONY: clean icfs_test clean-icfs clean-icfs_dialogue + +$(BUILD_DIR)/icfs_dialogue: + make -C $(SOURCES_DIR)/gui TEST=$(TEST) DEBUG=$(shell realpath $(DEBUG)) SOURCES_DIR=$(shell realpath $(SOURCES_DIR)/gui) BUILD_DIR=$(shell realpath $(BUILD_DIR)) TESTS_DIR=$(shell realpath $(TESTS_DIR)) $(BUILD_DIR)/icfs: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DIR)/sourcefs.o $(BUILD_DIR)/ui-socket.o $(BUILD_DIR)/temp_permissions_table.o $(BUILD_DIR)/perm_permissions_table.o $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs @@ -83,6 +97,16 @@ $(BUILD_DIR)/temp_permissions_table.o: $(SOURCES_DIR)/temp_permissions_table.c $ $(BUILD_DIR)/perm_permissions_table.o: $(SOURCES_DIR)/perm_permissions_table.c $(SOURCES_DIR)/perm_permissions_table.h $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@ +CLEAN_TARGETS=clean-icfs -clean: - rm $(BUILD_DIR)/*.o $(BUILD_DIR)/icfs* +ifneq ($(DIALOGUE), 0) + CLEAN_TARGETS += clean-icfs_dialogue +endif + +clean: $(CLEAN_TARGETS) + +clean-icfs: + rm $(BUILD_DIR)/*.o $(BUILD_DIR)/icfs + +clean-icfs_dialogue: + make -C $(SOURCES_DIR)/gui clean SOURCES_DIR=$(shell realpath $(SOURCES_DIR)/gui) BUILD_DIR=$(shell realpath $(BUILD_DIR)) TESTS_DIR=$(shell realpath $(TESTS_DIR)) diff --git a/src/gui/Makefile b/src/gui/Makefile index 107b266..953b125 100644 --- a/src/gui/Makefile +++ b/src/gui/Makefile @@ -2,9 +2,17 @@ SHELL=/bin/bash # configurable options -SOURCES_DIR := . -TESTS_DIR := . -BUILD_DIR := . +ifndef ($(SOURCES_DIR)) + SOURCES_DIR := . +endif + +ifndef ($(TESTS_DIR)) + TESTS_DIR := . +endif + +ifndef ($(BUILD_DIR)) + BUILD_DIR := . +endif CC := gcc CXX := g++ @@ -43,10 +51,10 @@ endif # set up targets -TARGETS := $(BUILD_DIR)/zenity +TARGETS := $(BUILD_DIR)/icfs_dialogue ifeq ($(TEST), 1) - TARGETS += zenity_test + TARGETS += icfs_dialogue_test endif @@ -54,16 +62,16 @@ endif default: $(TARGETS) -.PHONY: clean zenity_test +.PHONY: clean icfs_dialogue_test -zenity_test: $(BUILD_DIR)/zenity - ./zenity 666 cat /home/fedir Downloads +icfs_dialogue_test: $(BUILD_DIR)/icfs_dialogue + $(BUILD_DIR)/icfs_dialogue 666 cat /home/fedir /Downloads -$(BUILD_DIR)/zenity: $(BUILD_DIR)/zenity.o - $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/zenity +$(BUILD_DIR)/icfs_dialogue: $(BUILD_DIR)/icfs_dialogue.o + $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs_dialogue -$(BUILD_DIR)/zenity.o: $(SOURCES_DIR)/zenity-clone.c - $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $(BUILD_DIR)/zenity.o +$(BUILD_DIR)/icfs_dialogue.o: $(SOURCES_DIR)/icfs_dialogue.c + $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $(BUILD_DIR)/icfs_dialogue.o clean: - rm $(BUILD_DIR)/*.o $(BUILD_DIR)/zenity + rm $(BUILD_DIR)/*.o $(BUILD_DIR)/icfs_dialogue diff --git a/src/gui/zenity-clone.c b/src/gui/icfs_dialogue.c similarity index 100% rename from src/gui/zenity-clone.c rename to src/gui/icfs_dialogue.c diff --git a/test/test.bash b/test/test.bash index eeb9782..14c20f1 100755 --- a/test/test.bash +++ b/test/test.bash @@ -28,7 +28,7 @@ if [[ $1 == "--setuid" ]]; then else echo "Database protection will not be tested due to the lack of setuid capabilites." echo "To test it, run this script with '--setuid'." - valgrind -s ../build/icfs -o default_permissions ./protected ./.pt.db & + valgrind --leak-check=full -s ../build/icfs -o default_permissions ./protected ./.pt.db & sleep 5 fi From 3a89449c3214809f3c17cb3ac5ed2c3577ca4f67 Mon Sep 17 00:00:00 2001 From: fedir Date: Sat, 3 May 2025 12:05:11 +0200 Subject: [PATCH 16/33] Added gitignore to the dialogue --- src/gui/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/gui/.gitignore diff --git a/src/gui/.gitignore b/src/gui/.gitignore new file mode 100644 index 0000000..e2148f9 --- /dev/null +++ b/src/gui/.gitignore @@ -0,0 +1,4 @@ +./ui/* +./compile_commands.json +./*.c +./icfs_dialogue From a2eeb81feddb39e1461d217d45f9c1fccfd20d05 Mon Sep 17 00:00:00 2001 From: fedir Date: Sat, 3 May 2025 12:09:10 +0200 Subject: [PATCH 17/33] Updated gitignore --- .gitignore | 6 +++++- src/gui/.gitignore | 4 ---- 2 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 src/gui/.gitignore diff --git a/.gitignore b/.gitignore index c35c131..0293b27 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ build/* .cache test/protected/* test/.pt.db -compile_commands.json +*compile_commands.json test/perf* test/callgraph* +src/gui/ui/* + +src/gui/*.c +src/gui/icfs_dialogue diff --git a/src/gui/.gitignore b/src/gui/.gitignore deleted file mode 100644 index e2148f9..0000000 --- a/src/gui/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -./ui/* -./compile_commands.json -./*.c -./icfs_dialogue From f4576cf7ea3315627ceb58c07c205e13d4ea410d Mon Sep 17 00:00:00 2001 From: fedir Date: Sat, 3 May 2025 12:09:55 +0200 Subject: [PATCH 18/33] Updated gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 0293b27..f92c2ae 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,5 @@ test/.pt.db test/perf* test/callgraph* src/gui/ui/* - -src/gui/*.c +src/gui/*.o src/gui/icfs_dialogue From 10d29887617b8c28e0bb79014fce14d62ef4cadc Mon Sep 17 00:00:00 2001 From: fedir Date: Sun, 4 May 2025 17:05:07 +0200 Subject: [PATCH 19/33] Added a version check for the icfs-dialogue --- src/gui/icfs_dialogue.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gui/icfs_dialogue.c b/src/gui/icfs_dialogue.c index 6bd6335..d6be0cc 100644 --- a/src/gui/icfs_dialogue.c +++ b/src/gui/icfs_dialogue.c @@ -138,8 +138,13 @@ static int on_command_line(GApplication *app, GApplicationCommandLine *cmdline, } int main(int argc, char **argv) { + + if (argc == 2 && strcmp(argv[1], "--version") == 0) { + fprintf(stdout, "icfs_dialogue 1.0.0"); + } + // Create a new application - AdwApplication *app = adw_application_new("com.example.zenityclone", + AdwApplication *app = adw_application_new("de.umbrasolis.icfs_dialogue", G_APPLICATION_HANDLES_COMMAND_LINE); g_signal_connect(app, "command-line", G_CALLBACK(on_command_line), NULL); From ecedbbb4ce78255270ea6b15a4c94931425ff2da Mon Sep 17 00:00:00 2001 From: fedir Date: Sun, 4 May 2025 17:05:44 +0200 Subject: [PATCH 20/33] Added DENY_TEMP access type --- src/access_t.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/access_t.h b/src/access_t.h index 915f4ad..a1bda37 100644 --- a/src/access_t.h +++ b/src/access_t.h @@ -1,6 +1,6 @@ #ifndef ACCESS_T_H #define ACCESS_T_H -typedef enum { DENY, ALLOW, ALLOW_TEMP, NDEF } access_t; +typedef enum { DENY, ALLOW, ALLOW_TEMP, DENY_TEMP, NDEF } access_t; #endif // !ACCESS_T_H From c4ae40c7bd18d845620ade669a81828784029315 Mon Sep 17 00:00:00 2001 From: fedir Date: Sun, 4 May 2025 17:09:28 +0200 Subject: [PATCH 21/33] Finished the new dialogue functionality --- src/perm_permissions_table.c | 42 ++++++++++++++++++++++------- src/perm_permissions_table.h | 6 ++++- src/set_mode_t.h | 5 ++++ src/temp_permissions_table.h | 3 +-- src/ui-socket.c | 23 +++++++++++----- test/mock/{zenity => icfs_dialogue} | 6 +++-- 6 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 src/set_mode_t.h rename test/mock/{zenity => icfs_dialogue} (74%) diff --git a/src/perm_permissions_table.c b/src/perm_permissions_table.c index 04bcb37..58dfec7 100644 --- a/src/perm_permissions_table.c +++ b/src/perm_permissions_table.c @@ -9,6 +9,7 @@ #include "perm_permissions_table.h" #include "access_t.h" #include "process_info.h" +#include "set_mode_t.h" #include #include #include @@ -83,10 +84,21 @@ static int check_table_col_schema(void *notused, int argc, char **argv, } static int set_flag(void *flag, int argc, char **argv, char **colname) { - (void)argc; - (void)argv; (void)colname; - *(int *)flag = 1; + + if (argc < 3) { + fprintf(stderr, + "Unexpected amount of arguments given to the callback: %d.\n", + argc); + return 1; + } + + if (atoi(argv[2])) { + fprintf(stderr, "Third column was: %s\n", argv[2]); + *(int *)flag = 1; + } else { + *(int *)flag = -1; + } return 0; } @@ -182,7 +194,7 @@ int init_perm_permissions_table(const char *db_filename) { /** * Destroys the permanent permissions table. */ -void destroy_perm_permissions_table() { sqlite3_close(perm_database); } +void destroy_perm_permissions_table(void) { sqlite3_close(perm_database); } /** * Checks if the process has a permanent access to the file. @@ -197,7 +209,7 @@ access_t check_perm_access(const char *filename, struct process_info pi) { char *query = NULL; int ret = asprintf(&query, "SELECT * FROM %s WHERE executable = \'%s\' " - "AND filename = \'%s\' AND mode = TRUE;", + "AND filename = \'%s\';", table_name, pi.name, filename); if (ret < 0) { @@ -220,9 +232,12 @@ access_t check_perm_access(const char *filename, struct process_info pi) { return NDEF; } - if (flag) { + if (flag == 1) { return ALLOW; } + if (flag == -1) { + return DENY; + } return NDEF; } @@ -233,10 +248,19 @@ access_t check_perm_access(const char *filename, struct process_info pi) { * @param pi: The process information * @return: 0 on success, 1 on failure */ -int give_perm_access(const char *filename, struct process_info pi) { +int set_perm_access(const char *filename, struct process_info pi, + set_mode_t mode) { char *query = NULL; - int ret = asprintf(&query, "INSERT INTO %s VALUES (\'%s\', \'%s\', TRUE);", - table_name, pi.name, filename); + int ret = -1; + if (mode == SET_ALLOW) { + ret = asprintf(&query, "INSERT INTO %s VALUES (\'%s\', \'%s\', TRUE);", + table_name, pi.name, filename); + } else if (mode == SET_DENY) { + ret = asprintf(&query, "INSERT INTO %s VALUES (\'%s\', \'%s\', FALSE);", + table_name, pi.name, filename); + } else { + return 1; + } if (ret < 0) { // If asprintf fails, the contents of query are undefined (see man diff --git a/src/perm_permissions_table.h b/src/perm_permissions_table.h index f42b4fe..1dee36d 100644 --- a/src/perm_permissions_table.h +++ b/src/perm_permissions_table.h @@ -11,6 +11,7 @@ #include "access_t.h" #include "process_info.h" +#include "set_mode_t.h" /** * Initializes the permanent permissions table. @@ -40,8 +41,11 @@ access_t check_perm_access(const char *filename, struct process_info pi); * * @param filename: The file that the process is trying to access * @param pi: The process information + * @param mode: Kind of access rule to be set - SET_DENY to deny access, and + * SET_ALLOW to allow access. * @return: 0 on success, -1 on failure */ -int give_perm_access(const char *filename, struct process_info pi); +int set_perm_access(const char *filename, struct process_info pi, + set_mode_t mode); #endif // #ifdef PERM_PERMISSION_TABLE_H diff --git a/src/set_mode_t.h b/src/set_mode_t.h new file mode 100644 index 0000000..f0e7582 --- /dev/null +++ b/src/set_mode_t.h @@ -0,0 +1,5 @@ + +#ifndef SET_MODE_T_H +#define SET_MODE_T_H +typedef enum { SET_DENY, SET_ALLOW } set_mode_t; +#endif // !SET_MODE_T_H diff --git a/src/temp_permissions_table.h b/src/temp_permissions_table.h index 9320e27..1d62965 100644 --- a/src/temp_permissions_table.h +++ b/src/temp_permissions_table.h @@ -4,6 +4,7 @@ #include "access_t.h" #include "process_info.h" +#include "set_mode_t.h" /** * Initializes the temporary permissions table. @@ -35,8 +36,6 @@ void destroy_temp_permissions_table(void); */ access_t check_temp_access(const char *filename, struct process_info pi); -typedef enum { SET_DENY, SET_ALLOW } set_mode_t; - /** * Sets temporary access mode of the process to the file. * diff --git a/src/ui-socket.c b/src/ui-socket.c index 1daa9dd..95ab12c 100644 --- a/src/ui-socket.c +++ b/src/ui-socket.c @@ -49,7 +49,7 @@ int init_ui_socket(const char *perm_permissions_db_filename) { } // Test if Zenity is installed (get version) - fp = popen("zenity --version", "r"); + fp = popen("icfs_dialogue --version", "r"); if (fp == NULL) { perror("Pipe returned an error"); return 1; @@ -77,7 +77,7 @@ struct dialogue_response ask_access(const char *filename, struct process_info proc_info) { FILE *fp = NULL; char *command = NULL; - int ret = asprintf(&command, "zenity \"%d\" \"%s\" \"%s\" \"%s\"", + int ret = asprintf(&command, "icfs_dialogue \"%d\" \"%s\" \"%s\" \"%s\"", proc_info.PID, proc_info.name, get_mountpoint(), filename); struct dialogue_response response; @@ -142,8 +142,10 @@ struct dialogue_response ask_access(const char *filename, response.decision = ALLOW; } else if (zenity_exit_code == ZENITY_YES) { response.decision = ALLOW_TEMP; - } else { + } else if (zenity_exit_code == (ZENITY_NO | ZENITY_PERM)) { response.decision = DENY; + } else { + response.decision = DENY_TEMP; } return response; @@ -205,7 +207,7 @@ int interactive_access(const char *filename, struct process_info proc_info, if (opts & GRANT_PERM) { fprintf(stderr, "Permission granted permanently to %s.\n", proc_info.name); - give_perm_access(real_path, proc_info); + set_perm_access(real_path, proc_info, SET_ALLOW); free(real_path); return 1; } @@ -241,7 +243,7 @@ int interactive_access(const char *filename, struct process_info proc_info, fprintf(stderr, "Permission granted permanently to %s based on zenty response.\n", proc_info.name); - give_perm_access(real_path, proc_info); + set_perm_access(real_path, proc_info, SET_ALLOW); free(real_path); return 1; } @@ -255,7 +257,7 @@ int interactive_access(const char *filename, struct process_info proc_info, return 1; } - if (response.decision == DENY) { + if (response.decision == DENY_TEMP) { fprintf(stderr, "Permission denied temporarily to %s based on zenty response.\n", proc_info.name); @@ -264,6 +266,15 @@ int interactive_access(const char *filename, struct process_info proc_info, return 0; } + if (response.decision == DENY) { + fprintf(stderr, + "Permission denied permanently to %s based on zenty response.\n", + proc_info.name); + set_perm_access(real_path, proc_info, SET_DENY); + free(real_path); + return 0; + } + free(real_path); // deny on unknown options. return 0; diff --git a/test/mock/zenity b/test/mock/icfs_dialogue similarity index 74% rename from test/mock/zenity rename to test/mock/icfs_dialogue index 35270b4..92f8aff 100755 --- a/test/mock/zenity +++ b/test/mock/icfs_dialogue @@ -14,12 +14,14 @@ else FAKE_ZENITY_RESPONSE=$(cat ~/.fake_zenity_response) printf "%s" "$4" - if [[ $FAKE_ZENITY_RESPONSE == "yes_tmp" ]]; then + if [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then exit "$ZENITY_YES" elif [[ $FAKE_ZENITY_RESPONSE == "no" ]]; then exit "$ZENITY_NO" - elif [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then + elif [[ $FAKE_ZENITY_RESPONSE == "yes_perm" ]]; then exit "$((ZENITY_YES | ZENITY_PERM))" + elif [[ $FAKE_ZENITY_RESPONSE == "no_perm" ]]; then + exit "$((ZENITY_NO | ZENITY_PERM))" fi fi fi From 8a530b493c67cf4d0ffdc4fedac7f99ec8ca63dc Mon Sep 17 00:00:00 2001 From: fedir Date: Sun, 4 May 2025 17:10:19 +0200 Subject: [PATCH 22/33] Added new tests for the new dialogue --- .gitignore | 1 + test/opener/Makefile | 81 ++++++++++++++++++++++++++++++++++++++++++++ test/opener/opener.c | 20 +++++++++++ test/test.bash | 68 +++++++++++++++++++++++++++---------- 4 files changed, 152 insertions(+), 18 deletions(-) create mode 100644 test/opener/Makefile create mode 100644 test/opener/opener.c diff --git a/.gitignore b/.gitignore index f92c2ae..fd117b0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ test/.pt.db *compile_commands.json test/perf* test/callgraph* +test/openers src/gui/ui/* src/gui/*.o src/gui/icfs_dialogue diff --git a/test/opener/Makefile b/test/opener/Makefile new file mode 100644 index 0000000..1254625 --- /dev/null +++ b/test/opener/Makefile @@ -0,0 +1,81 @@ +SHELL=/bin/bash + +# configurable options + +ifndef ($(SOURCES_DIR)) + SOURCES_DIR := . +endif + +ifndef ($(TESTS_DIR)) + TESTS_DIR := . +endif + +ifndef ($(BUILD_DIR)) + BUILD_DIR := . +endif + +CC := gcc +CXX := g++ + +NAME := opener + +# dependencies + +PACKAGE_NAMES := + +ifeq ($(TEST), 1) + # PACKAGE_NAMES += check # TODO: use check? +endif + + +# set up cflags and libs + +CFLAGS := +LDFLAGS := + +ifneq ($(PACKAGE_NAMES),) + CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES)) + LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES)) +endif + +ifeq ($(DEBUG),1) + CFLAGS += -O0 -pedantic -g -Wall -Wextra -Wcast-align \ + -Wcast-qual -Wdisabled-optimization -Wformat=2 \ + -Winit-self -Wlogical-op -Wmissing-declarations \ + -Wmissing-include-dirs -Wredundant-decls -Wshadow \ + -Wsign-conversion -Wstrict-overflow=5 \ + -Wswitch-default -Wundef -Wno-unused + LDFLAGS += +else + CFLAGS += -O3 + LDFLAGS += +endif + + +# set up targets + +TARGETS := $(BUILD_DIR)/$(NAME) + +ifeq ($(TEST), 1) + TARGETS += $(NAME)_test +endif + + +# build! + +default: $(TARGETS) + +.PHONY: clean $(NAME)_test + +$(NAME)_test: $(BUILD_DIR)/$(NAME) + echo "No tests defined." + #$(BUILD_DIR)/$(NAME) + +$(BUILD_DIR)/$(NAME): $(BUILD_DIR)/$(NAME).o + $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/$(NAME) + +$(BUILD_DIR)/$(NAME).o: $(SOURCES_DIR)/$(NAME).c + $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $(BUILD_DIR)/$(NAME).o + +clean: + rm $(BUILD_DIR)/*.o $(BUILD_DIR)/$(NAME) diff --git a/test/opener/opener.c b/test/opener/opener.c new file mode 100644 index 0000000..1cd4fb5 --- /dev/null +++ b/test/opener/opener.c @@ -0,0 +1,20 @@ + + +#include +#include +#include +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: ./opener [FILENAME]"); + return 1; + } + + int fd = open(argv[1], O_RDWR | O_CREAT); + if (fd == -1) { + perror("Cannot open file"); + return 1; + } + close(fd); + + return 0; +} diff --git a/test/test.bash b/test/test.bash index 14c20f1..698530f 100755 --- a/test/test.bash +++ b/test/test.bash @@ -9,6 +9,17 @@ touch ./protected/do-not-remove ./protected/should-be-removed ./protected/truth chmod 777 ./protected/perm777 ./protected/perm000 echo "Free code, free world." >./protected/motto +rm -rf ./openers +mkdir openers +make -C ./opener || ( + echo "Could not make the opener program." + exit 1 +) +for i in {1..10}; do + cp ./opener/opener "./openers/opener$i" + ln --symbolic "$(realpath "./openers/opener$i")" "./openers/symlinked_opener$i" +done + # set up the fake-zenity PATH="$(realpath ./mock/):$PATH" @@ -28,6 +39,7 @@ if [[ $1 == "--setuid" ]]; then else echo "Database protection will not be tested due to the lack of setuid capabilites." echo "To test it, run this script with '--setuid'." + #valgrind --leak-check=full -s ../build/icfs -o default_permissions -o debug ./protected ./.pt.db 2>&1 | grep "==\|zenity\|Permission\|column\|callback" & valgrind --leak-check=full -s ../build/icfs -o default_permissions ./protected ./.pt.db & sleep 5 fi @@ -39,85 +51,105 @@ fi # create files -zenity --set-fake-response no +icfs_dialogue --set-fake-response no truncate -s 0 ./protected/should-exist-anyway 2>/dev/null && echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: truncate cannot create protected/should-exist despite access being permitted!" # OK -zenity --set-fake-response yes_tmp +icfs_dialogue --set-fake-response yes truncate -s 0 ./protected/should-exist 2>/dev/null && echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: truncate cannot create protected/should-exist despite access being permitted!" # OK # write to files -zenity --set-fake-response no +icfs_dialogue --set-fake-response no sed -e 'a\'"Linux is a cancer that attaches itself in an intellectual property sense to everything it touches." "./protected/truth" 2>/dev/null && echo "[ICFS-TEST]: echo can write to protected/lie despite access being denied!" || echo "[ICFS-TEST]: OK" # EACCESS -zenity --set-fake-response yes_tmp +icfs_dialogue --set-fake-response yes sed -e 'a\'"Sharing knowledge is the most fundamental act of friendship. Because it is a way you can give something without loosing something." "./protected/truth" 2>/dev/null && echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: echo cannot write to protected/truth despite access being permitted!" # OK # Read files -zenity --set-fake-response no +icfs_dialogue --set-fake-response no cat ./protected/motto >/dev/null 2>/dev/null && echo "[ICFS-TEST]: cat can read protected/this-only despite access being denied!" || echo "[ICFS-TEST]: OK" # EACCESS -zenity --set-fake-response yes_tmp +icfs_dialogue --set-fake-response yes cat ./protected/motto >/dev/null 2>/dev/null && echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: echo cannot create protected/this-only despite access being permitted!" # "Free code, free world." # remove files -zenity --set-fake-response no +icfs_dialogue --set-fake-response no rm ./protected/do-not-remove >/dev/null 2>/dev/null && echo "[ICFS-TEST]: rm can unlink protected/do-not-remove despite access being denied!" || echo "[ICFS-TEST]: OK" # EACCESS -zenity --set-fake-response yes_tmp +icfs_dialogue --set-fake-response yes rm ./protected/should-be-removed >/dev/null 2>/dev/null && echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: rm cannot unlink protected/should-be-removed despite access being permitted!" # OK # rename files -zenity --set-fake-response no +icfs_dialogue --set-fake-response no mv ./protected/do-not-rename ./protected/terrible-name 2>/dev/null && echo "[ICFS-TEST]: mv can rename protected/truth despite access being denied!" || echo "[ICFS-TEST]: OK" # EACCESS -zenity --set-fake-response yes_tmp +icfs_dialogue --set-fake-response yes mv ./protected/should-be-renamed ./protected/great-name 2>/dev/null && echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: mv cannot rename should-be-removed to renamed-file despite access being permitted!" # OK # change permissions -zenity --set-fake-response no +icfs_dialogue --set-fake-response no chmod 000 ./protected/perm777 2>/dev/null && echo "[ICFS-TEST]: chmod can change permissions of protected/perm777 despite access being denied!" || echo "[ICFS-TEST]: OK" # EACCESS -zenity --set-fake-response yes_tmp +icfs_dialogue --set-fake-response yes chmod 000 ./protected/perm000 2>/dev/null && echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: chmod cannot change permissions of protected/perm000 despite access being permitted!" # OK # test permanent permissions -zenity --set-fake-response yes -cat ./protected/motto >/dev/null 2>/dev/null && +icfs_dialogue --set-fake-response yes_perm +openers/opener1 ./protected/motto >/dev/null 2>/dev/null && echo "[ICFS-TEST]: OK" || - echo "[ICFS-TEST]: echo cannot read protected/motto despite access being permitted!" # OK + echo "[ICFS-TEST]: openers/opener1 cannot read protected/motto despite access being permitted!" # OK -zenity --set-fake-response no # this should be ignored -cat ./protected/motto >/dev/null 2>/dev/null && +icfs_dialogue --set-fake-response no # this should be ignored +openers/opener1 ./protected/motto >/dev/null 2>/dev/null && echo "[ICFS-TEST]: OK" || - echo "[ICFS-TEST]: echo cannot read protected/motto despite access being permitted!" # OK + echo "[ICFS-TEST]: openers/opener1 cannot read protected/motto despite access being permitted!" # OK + +icfs_dialogue --set-fake-response no # this should be ignored +openers/symlinked_opener1 ./protected/motto >/dev/null 2>/dev/null && + echo "[ICFS-TEST]: OK" || + echo "[ICFS-TEST]: openers/symlinked_opener1 cannot read protected/motto despite access being permitted!" # OK + +icfs_dialogue --set-fake-response no_perm +openers/opener2 ./protected/motto >/dev/null 2>/dev/null && + echo "[ICFS-TEST]: openers/opener2 can read protected/motto despite access being denied!" || + echo "[ICFS-TEST]: OK" # EACCESS + +icfs_dialogue --set-fake-response yes # this should be ignored +openers/opener2 ./protected/motto >/dev/null 2>/dev/null && + echo "[ICFS-TEST]: openers/opener2 can read protected/motto despite access being denied!" || + echo "[ICFS-TEST]: OK" # EACCESS + +icfs_dialogue --set-fake-response yes # this should be ignored +openers/symlinked_opener2 ./protected/motto >/dev/null 2>/dev/null && + echo "[ICFS-TEST]: openers/symlinked_opener2 can read protected/motto despite access being denied!" || + echo "[ICFS-TEST]: OK" # EACCESS # test database access if [[ -r "./.pt.db" || -w "./.pt.db" ]]; then From 4f98a4834ebffe1c5f1f45961ffb1551257799bc Mon Sep 17 00:00:00 2001 From: fedir Date: Sun, 4 May 2025 17:10:58 +0200 Subject: [PATCH 23/33] Updated gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index fd117b0..c328f02 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ test/.pt.db test/perf* test/callgraph* test/openers +test/opener/opener +test/opener/opener.o src/gui/ui/* src/gui/*.o src/gui/icfs_dialogue From e32ce5add592cf27638ef9b75ad317d83adceb2f Mon Sep 17 00:00:00 2001 From: fedir Date: Sun, 4 May 2025 17:25:47 +0200 Subject: [PATCH 24/33] Renamed all zenity mentions to dialogue --- src/ui-socket.c | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ui-socket.c b/src/ui-socket.c index 95ab12c..0e6f3c7 100644 --- a/src/ui-socket.c +++ b/src/ui-socket.c @@ -26,9 +26,9 @@ #include #include -#define ZENITY_YES 0 -#define ZENITY_NO 1 -#define ZENITY_PERM 2 +#define DIALOGUE_YES 0 +#define DIALOGUE_NO 1 +#define DIALOGUE_PERM 2 struct dialogue_response { access_t decision; @@ -48,7 +48,7 @@ int init_ui_socket(const char *perm_permissions_db_filename) { return 1; } - // Test if Zenity is installed (get version) + // Test if dialogue is installed (get version) fp = popen("icfs_dialogue --version", "r"); if (fp == NULL) { perror("Pipe returned an error"); @@ -98,7 +98,7 @@ struct dialogue_response ask_access(const char *filename, return response; } - // Zenity Question Message Popup + // dialogue Question Message Popup fp = popen(command, "r"); free(command); @@ -111,38 +111,38 @@ struct dialogue_response ask_access(const char *filename, return response; } - str(char) zenity_output; - init(&zenity_output); + str(char) dialogue_output; + init(&dialogue_output); char line[1024]; // Buffer to read individual lines // Read the command output line by line while (fgets(line, sizeof(line), fp)) { - push_fmt(&zenity_output, line); + push_fmt(&dialogue_output, line); } - int zenity_exit_code = WEXITSTATUS(pclose(fp)); - fprintf(stderr, "zenity wrote out %s\n", first(&zenity_output)); - fprintf(stderr, "zenity returned %d\n", zenity_exit_code); + int dialogue_exit_code = WEXITSTATUS(pclose(fp)); + fprintf(stderr, "dialogue wrote out %s\n", first(&dialogue_output)); + fprintf(stderr, "dialogue returned %d\n", dialogue_exit_code); - // if (size(&zenity_output) == 0) { - // push(&zenity_output, '.'); + // if (size(&dialogue_output) == 0) { + // push(&dialogue_output, '.'); // } - assert(strlen(first(&zenity_output)) == size(&zenity_output)); + assert(strlen(first(&dialogue_output)) == size(&dialogue_output)); - response.filename = malloc(size(&zenity_output) + 1); - strcpy(response.filename, first(&zenity_output)); - // response.filename[size(&zenity_output)] = 0; + response.filename = malloc(size(&dialogue_output) + 1); + strcpy(response.filename, first(&dialogue_output)); + // response.filename[size(&dialogue_output)] = 0; - // assert(0 == strcmp(response.filename, first(&zenity_output))); - cleanup(&zenity_output); + // assert(0 == strcmp(response.filename, first(&dialogue_output))); + cleanup(&dialogue_output); - if (zenity_exit_code == (ZENITY_YES | ZENITY_PERM)) { + if (dialogue_exit_code == (DIALOGUE_YES | DIALOGUE_PERM)) { response.decision = ALLOW; - } else if (zenity_exit_code == ZENITY_YES) { + } else if (dialogue_exit_code == DIALOGUE_YES) { response.decision = ALLOW_TEMP; - } else if (zenity_exit_code == (ZENITY_NO | ZENITY_PERM)) { + } else if (dialogue_exit_code == (DIALOGUE_NO | DIALOGUE_PERM)) { response.decision = DENY; } else { response.decision = DENY_TEMP; From 4539df98429823ae8f6487ec8c138ad1b157be1a Mon Sep 17 00:00:00 2001 From: fedir Date: Sun, 4 May 2025 17:39:39 +0200 Subject: [PATCH 25/33] Fixed wrong fallback filename bug --- src/ui-socket.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui-socket.c b/src/ui-socket.c index 0e6f3c7..ef72430 100644 --- a/src/ui-socket.c +++ b/src/ui-socket.c @@ -93,7 +93,7 @@ struct dialogue_response ask_access(const char *filename, perror(""); response.decision = DENY; response.filename = malloc(2); - response.filename[0] = '.'; + response.filename[0] = '/'; response.filename[1] = 0; return response; } @@ -106,7 +106,7 @@ struct dialogue_response ask_access(const char *filename, perror("Pipe returned a error"); response.decision = DENY; response.filename = malloc(2); - response.filename[0] = '.'; + response.filename[0] = '/'; response.filename[1] = 0; return response; } From 420f34a7f32cc88d6ad2fdfedced8035cbc5561d Mon Sep 17 00:00:00 2001 From: fedir Date: Mon, 5 May 2025 18:53:05 +0200 Subject: [PATCH 26/33] Added folder globbing for permanent permissions --- Makefile | 5 +++- src/fuse_operations.c | 55 +----------------------------------- src/perm_permissions_table.c | 49 ++++++++++++++++++++++++++++++-- src/proc_operations.c | 45 +++++++++++++++++++++++++++++ src/proc_operations.h | 17 +++++++++++ src/temp_permissions_table.c | 31 +------------------- 6 files changed, 114 insertions(+), 88 deletions(-) create mode 100644 src/proc_operations.c create mode 100644 src/proc_operations.h diff --git a/Makefile b/Makefile index 0c8386f..b934d76 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,7 @@ default: $(TARGETS) $(BUILD_DIR)/icfs_dialogue: make -C $(SOURCES_DIR)/gui TEST=$(TEST) DEBUG=$(shell realpath $(DEBUG)) SOURCES_DIR=$(shell realpath $(SOURCES_DIR)/gui) BUILD_DIR=$(shell realpath $(BUILD_DIR)) TESTS_DIR=$(shell realpath $(TESTS_DIR)) -$(BUILD_DIR)/icfs: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DIR)/sourcefs.o $(BUILD_DIR)/ui-socket.o $(BUILD_DIR)/temp_permissions_table.o $(BUILD_DIR)/perm_permissions_table.o +$(BUILD_DIR)/icfs: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DIR)/sourcefs.o $(BUILD_DIR)/ui-socket.o $(BUILD_DIR)/temp_permissions_table.o $(BUILD_DIR)/perm_permissions_table.o $(BUILD_DIR)/proc_operations.o $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs icfs_test: $(BUILD_DIR)/icfs @@ -97,6 +97,9 @@ $(BUILD_DIR)/temp_permissions_table.o: $(SOURCES_DIR)/temp_permissions_table.c $ $(BUILD_DIR)/perm_permissions_table.o: $(SOURCES_DIR)/perm_permissions_table.c $(SOURCES_DIR)/perm_permissions_table.h $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@ +$(BUILD_DIR)/proc_operations.o: $(SOURCES_DIR)/proc_operations.c $(SOURCES_DIR)/proc_operations.h + $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@ + CLEAN_TARGETS=clean-icfs ifneq ($(DIALOGUE), 0) diff --git a/src/fuse_operations.c b/src/fuse_operations.c index 269a6b7..02a16f0 100644 --- a/src/fuse_operations.c +++ b/src/fuse_operations.c @@ -39,63 +39,10 @@ #include /* flock(2) */ #include "fuse_operations.h" +#include "proc_operations.h" #include "sourcefs.h" #include "ui-socket.h" -char *get_process_name_by_pid(const int pid) { - char path[1024]; - sprintf(path, "/proc/%d/exe", pid); - - char *name = realpath(path, NULL); - if (name == NULL) { - fprintf(stderr, "Could not get process name by pid %d", pid); - perror(""); - } - - /* - size_t namelen = 32; - ssize_t readret = 0; - char *name = NULL; - while (namelen >= (size_t)readret && readret > 0) { - namelen *= 2; - name = calloc(namelen, sizeof(char)); - if (name == NULL) { - free(path); - fprintf(stderr, "Could not get get process name by pid %d", pid); - perror(""); - return NULL; - } - readret = readlink(path, name, namelen); - if (readret < 0) { - free(name); - free(path); - fprintf(stderr, "Couldn't get process name by pid %d", pid); - perror(""); - return NULL; - } - if (namelen >= (size_t)readret) { - free(name); - } - } - */ - - return name; - - /* - FILE *file = fopen(path, "r"); - if (file) { - size_t size = 0; - size = fread(path, sizeof(char), 1024, file); - if (size > 0) { - if ('\n' == path[size - 1]) { - path[size - 1] = '\0'; - } - } - fclose(file); - } - */ -} - static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { (void)conn; cfg->use_ino = 1; diff --git a/src/perm_permissions_table.c b/src/perm_permissions_table.c index 58dfec7..d6b4b5c 100644 --- a/src/perm_permissions_table.c +++ b/src/perm_permissions_table.c @@ -8,6 +8,7 @@ #include "perm_permissions_table.h" #include "access_t.h" +#include "proc_operations.h" #include "process_info.h" #include "set_mode_t.h" #include @@ -204,13 +205,16 @@ void destroy_perm_permissions_table(void) { sqlite3_close(perm_database); } * @return: access status - ALLOW, DENY or NDEF in case if no information was * found */ -access_t check_perm_access(const char *filename, struct process_info pi) { +access_t check_perm_access_noparent(const char *filename, + struct process_info pi) { char *query = NULL; int ret = asprintf(&query, "SELECT * FROM %s WHERE executable = \'%s\' " - "AND filename = \'%s\';", - table_name, pi.name, filename); + "AND ((\'%s\' LIKE CONCAT(filename, \'%%\') AND filename " + "GLOB \'*/\') OR filename = \'%s\');", + table_name, pi.name, filename, filename); + fprintf(stderr, "query: %s\n", query); if (ret < 0) { // If asprintf fails, the contents of query are undefined (see man @@ -241,6 +245,45 @@ access_t check_perm_access(const char *filename, struct process_info pi) { return NDEF; } +/** + * Checks if the process or any of it's parents have permanent access to the + * file. + * + * @param filename: The file that the process is trying to access + * @pram pi: The process information + * @return: access status - ALLOW, DENY or NDEF in case if no information was + * found. Does not return ALLOW_TEMP or DENY_TEMP. + * @note: In case one of the parent processes is killed while this function + * execution the result is not guranteed to be correct. It should only lead to + * false negatives, though. + */ +access_t check_perm_access(const char *filename, struct process_info pi) { + if (pi.PID == 0) { + return NDEF; + } + + struct process_info current_pi = pi; + current_pi.name = strdup(current_pi.name); + while (current_pi.PID != 0) { + access_t access = check_perm_access_noparent(filename, current_pi); + free(current_pi.name); + if (access != NDEF) { + return access; + } + current_pi.name = NULL; + while (current_pi.name == NULL) { + current_pi.PID = get_parent_pid(current_pi.PID); + if (current_pi.PID != 0) { + current_pi.name = get_process_name_by_pid(current_pi.PID); + } else { + break; + } + } + } + + return NDEF; +} + /** * Gives permanent access to the process to the file. * diff --git a/src/proc_operations.c b/src/proc_operations.c new file mode 100644 index 0000000..90bbb80 --- /dev/null +++ b/src/proc_operations.c @@ -0,0 +1,45 @@ +#include "proc_operations.h" +#include +#include + +char *get_process_name_by_pid(const int pid) { + char path[1024]; + sprintf(path, "/proc/%d/exe", pid); + + char *name = realpath(path, NULL); + if (name == NULL) { + fprintf(stderr, "Could not get process name by pid %d", pid); + perror(""); + } + return name; +} + +/** + * Finds the parent process ID of a given process. + * + * @param pid: The process ID of the process to find the parent of + * @return: The parent process ID, or 0 if the parent process ID could not be + * found + */ +pid_t get_parent_pid(pid_t pid) { + pid_t ppid = 0; + char path[256]; + snprintf(path, sizeof(path), "/proc/%u/status", pid); + + FILE *file = fopen(path, "r"); + if (file == NULL) { + perror("Failed to open /proc//status"); + return 0; + } + + char line[256]; + while (fgets(line, sizeof(line), file)) { + if (sscanf(line, "PPid:\t%d", &ppid) == 1) { + fclose(file); + return ppid; + } + } + + fclose(file); + return 0; // Parent PID not found +} diff --git a/src/proc_operations.h b/src/proc_operations.h new file mode 100644 index 0000000..e39b7c0 --- /dev/null +++ b/src/proc_operations.h @@ -0,0 +1,17 @@ +#ifndef PROC_OPERATIONS +#define PROC_OPERATIONS + +#include + +char *get_process_name_by_pid(const int pid); + +/** + * Finds the parent process ID of a given process. + * + * @param pid: The process ID of the process to find the parent of + * @return: The parent process ID, or 0 if the parent process ID could not be + * found + */ +pid_t get_parent_pid(pid_t pid); + +#endif // !PROC_OPERATIONS diff --git a/src/temp_permissions_table.c b/src/temp_permissions_table.c index e3c8e47..efe894c 100644 --- a/src/temp_permissions_table.c +++ b/src/temp_permissions_table.c @@ -9,6 +9,7 @@ #include "temp_permissions_table.h" #include "access_t.h" #include "cc.h" +#include "proc_operations.h" #include "process_info.h" #include #include @@ -144,36 +145,6 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) { return NDEF; } -/** - * Finds the parent process ID of a given process. - * - * @param pid: The process ID of the process to find the parent of - * @return: The parent process ID, or 0 if the parent process ID could not be - * found - */ -pid_t get_parent_pid(pid_t pid) { - pid_t ppid = 0; - char path[256]; - snprintf(path, sizeof(path), "/proc/%u/status", pid); - - FILE *file = fopen(path, "r"); - if (file == NULL) { - perror("Failed to open /proc//status"); - return 0; - } - - char line[256]; - while (fgets(line, sizeof(line), file)) { - if (sscanf(line, "PPid:\t%d", &ppid) == 1) { - fclose(file); - return ppid; - } - } - - fclose(file); - return 0; // Parent PID not found -} - /** * Checks if the process or any of it's parents have temporary access to the * file. From fd2144a1f95ac6358db0289add1a0cefa7fb2c5a Mon Sep 17 00:00:00 2001 From: fedir Date: Mon, 5 May 2025 18:59:57 +0200 Subject: [PATCH 27/33] Added a filename check --- src/ui-socket.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ui-socket.c b/src/ui-socket.c index ef72430..59e54d1 100644 --- a/src/ui-socket.c +++ b/src/ui-socket.c @@ -225,7 +225,6 @@ int interactive_access(const char *filename, struct process_info proc_info, // the user might specify a different file in the dialogue, so we need to // check if it is valid - /* while (source_access(response.filename, F_OK)) { // if it is invalid, just ask again. fprintf(stderr, "Filename returned by zenty wasn't correct: %s\n", @@ -233,7 +232,7 @@ int interactive_access(const char *filename, struct process_info proc_info, free(response.filename); response = ask_access(filename, proc_info); } - */ + free(real_path); real_path = real_filename(response.filename); From 22b091f01725b5e27f61190b0868f8131b9fcae1 Mon Sep 17 00:00:00 2001 From: fedir Date: Tue, 6 May 2025 12:17:26 +0200 Subject: [PATCH 28/33] Fixed empty filename bug. --- src/ui-socket.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ui-socket.c b/src/ui-socket.c index 59e54d1..fbc4066 100644 --- a/src/ui-socket.c +++ b/src/ui-socket.c @@ -125,9 +125,9 @@ struct dialogue_response ask_access(const char *filename, fprintf(stderr, "dialogue wrote out %s\n", first(&dialogue_output)); fprintf(stderr, "dialogue returned %d\n", dialogue_exit_code); - // if (size(&dialogue_output) == 0) { - // push(&dialogue_output, '.'); - // } + if (size(&dialogue_output) == 0) { + push(&dialogue_output, '/'); + } assert(strlen(first(&dialogue_output)) == size(&dialogue_output)); From 801a7cdb39dd0c618543f41eba912c7f1cb344e5 Mon Sep 17 00:00:00 2001 From: fedir Date: Tue, 6 May 2025 12:17:50 +0200 Subject: [PATCH 29/33] Added temp permission globbing --- src/temp_permissions_table.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/temp_permissions_table.c b/src/temp_permissions_table.c index efe894c..8fe4966 100644 --- a/src/temp_permissions_table.c +++ b/src/temp_permissions_table.c @@ -12,7 +12,9 @@ #include "proc_operations.h" #include "process_info.h" #include +#include #include +#include #include struct temp_process_permissions { @@ -127,14 +129,23 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) { if (process_creation_time == permission_entry->creation_time) { // the process is the same as the one that was granted temporary access // to the file + size_t filename_len = strlen(filename); for_each(&permission_entry->denied_files, denied_file) { - if (strncmp(*denied_file, filename, strlen(filename)) == 0) { + size_t denied_file_len = strlen(*denied_file); + if (strncmp(*denied_file, filename, denied_file_len) == 0 && + ((denied_file_len < filename_len && + (*denied_file)[denied_file_len - 1] == '/') || + (denied_file_len == filename_len))) { pthread_mutex_unlock(&temp_permissions_table_lock); return DENY; } } for_each(&permission_entry->allowed_files, allowed_file) { - if (strncmp(*allowed_file, filename, strlen(filename)) == 0) { + size_t allowed_file_len = strlen(*allowed_file); + if (strncmp(*allowed_file, filename, allowed_file_len) == 0 && + ((allowed_file_len < filename_len && + (*allowed_file)[allowed_file_len - 1] == '/') || + (allowed_file_len == filename_len))) { pthread_mutex_unlock(&temp_permissions_table_lock); return ALLOW; } From 15fa0fe193e364d9ea7052c3dfc400f454ae67c2 Mon Sep 17 00:00:00 2001 From: fedir Date: Tue, 6 May 2025 12:18:21 +0200 Subject: [PATCH 30/33] Added filename return to the dialogue mockup --- test/mock/icfs_dialogue | 46 ++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/test/mock/icfs_dialogue b/test/mock/icfs_dialogue index 92f8aff..99ef036 100755 --- a/test/mock/icfs_dialogue +++ b/test/mock/icfs_dialogue @@ -1,29 +1,41 @@ #!/bin/bash -# fake-zenity: script that mocks the behavior of zenity based on the ./.fake-zenity-response file +# fake-icfs_dialogue: script that mocks the behavior of icfs_dialogue based on the ./.fake-icfs_dialogue-response file -ZENITY_YES=0 -ZENITY_NO=1 -ZENITY_PERM=2 +ICFS_DIALOGUE_YES=0 +ICFS_DIALOGUE_NO=1 +ICFS_DIALOGUE_PERM=2 if [[ $1 == "--set-fake-response" ]]; then #someone knows we are fake :) - echo "$2" >~/.fake_zenity_response + echo "$2" >~/.fake_icfs_dialogue_response +elif [[ $1 == "--set-fake-response-filename" ]]; then + echo "$2" >~/.fake_icfs_dialogue_response_filename +elif [[ $1 == "--reset-fake-response" ]]; then + rm ~/.fake_icfs_dialogue_response ~/.fake_icfs_dialogue_response_filename else - if [ -f ~/.fake_zenity_response ]; then - FAKE_ZENITY_RESPONSE=$(cat ~/.fake_zenity_response) + if [ -f ~/.fake_icfs_dialogue_response ]; then + FAKE_ICFS_DIALOGUE_RESPONSE=$(cat ~/.fake_icfs_dialogue_response) - printf "%s" "$4" - if [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then - exit "$ZENITY_YES" - elif [[ $FAKE_ZENITY_RESPONSE == "no" ]]; then - exit "$ZENITY_NO" - elif [[ $FAKE_ZENITY_RESPONSE == "yes_perm" ]]; then - exit "$((ZENITY_YES | ZENITY_PERM))" - elif [[ $FAKE_ZENITY_RESPONSE == "no_perm" ]]; then - exit "$((ZENITY_NO | ZENITY_PERM))" + if [[ -f ~/.fake_icfs_dialogue_response_filename ]]; then + FAKE_ICFS_DIALOGUE_RESPONSE_FILENAME=$(cat ~/.fake_icfs_dialogue_response_filename) + if [[ $FAKE_ICFS_DIALOGUE_RESPONSE_FILENAME == "" ]]; then + printf "%s" "$4" + else + printf "%s" "$(cat ~/.fake_icfs_dialogue_response_filename)" + fi + fi + + if [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "yes" ]]; then + exit "$ICFS_DIALOGUE_YES" + elif [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "no" ]]; then + exit "$ICFS_DIALOGUE_NO" + elif [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "yes_perm" ]]; then + exit "$((ICFS_DIALOGUE_YES | ICFS_DIALOGUE_PERM))" + elif [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "no_perm" ]]; then + exit "$((ICFS_DIALOGUE_NO | ICFS_DIALOGUE_PERM))" fi fi fi -exit 255 # TODO: call actual zenity here +exit 255 # TODO: call actual icfs_dialogue here From 6065a0c20ac34a032d2e50108a440c284a06be5b Mon Sep 17 00:00:00 2001 From: fedir Date: Tue, 6 May 2025 12:18:45 +0200 Subject: [PATCH 31/33] Added permissions globbing tests --- test/test.bash | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test/test.bash b/test/test.bash index 698530f..ba0d9c8 100755 --- a/test/test.bash +++ b/test/test.bash @@ -9,6 +9,13 @@ touch ./protected/do-not-remove ./protected/should-be-removed ./protected/truth chmod 777 ./protected/perm777 ./protected/perm000 echo "Free code, free world." >./protected/motto +mkdir protected/haystack +for i in {1..10}; do + touch "./protected/haystack/hay$i" +done +touch ./protected/haystack/needle +echo "Liberty in every line." >./protected/haystack/needle + rm -rf ./openers mkdir openers make -C ./opener || ( @@ -151,9 +158,22 @@ openers/symlinked_opener2 ./protected/motto >/dev/null 2>/dev/null && echo "[ICFS-TEST]: openers/symlinked_opener2 can read protected/motto despite access being denied!" || echo "[ICFS-TEST]: OK" # EACCESS +# test permission globbing + +icfs_dialogue --set-fake-response yes_perm +icfs_dialogue --set-fake-response-filename "/" +grep 'Liberty' ./protected/haystack/needle >/dev/null && + echo "[ICFS-TEST]: OK" || + echo "[ICFS-TEST]: grep cannot read protected/motto despite access being permitted!" # OK + +icfs_dialogue --set-fake-response no # this should be ignored +grep "Liberty" ./protected/haystack/* >/dev/null && + echo "[ICFS-TEST]: OK" || + echo "[ICFS-TEST]: grep cannot read protected/motto despite access being permitted!" # OK + # test database access if [[ -r "./.pt.db" || -w "./.pt.db" ]]; then - echo "[ICFS-TEST]: permanent permissions is accessible!" + echo "[ICFS-TEST]: permanent permissions database is accessible!" else echo "[ICFS-TEST]: OK" fi From b4149ac425c3b59b0604f9852f6772954ccccd46 Mon Sep 17 00:00:00 2001 From: fedir Date: Tue, 6 May 2025 12:19:39 +0200 Subject: [PATCH 32/33] Updated gitignore --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c328f02..70ab0e0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,13 +2,13 @@ build/* .clang-tidy .cache test/protected/* -test/.pt.db -*compile_commands.json test/perf* test/callgraph* test/openers test/opener/opener test/opener/opener.o +test/.* +*compile_commands.json src/gui/ui/* src/gui/*.o src/gui/icfs_dialogue From 78e108d0d467fc54a2715e1708b792a59e1ea8c0 Mon Sep 17 00:00:00 2001 From: fedir Date: Tue, 6 May 2025 17:56:55 +0200 Subject: [PATCH 33/33] Added more tests --- test/opener/opener.c | 82 +++++++++++++++++++++++++++++++++++++++----- test/test.bash | 43 ++++++++++++++++++----- 2 files changed, 109 insertions(+), 16 deletions(-) diff --git a/test/opener/opener.c b/test/opener/opener.c index 1cd4fb5..51ce934 100644 --- a/test/opener/opener.c +++ b/test/opener/opener.c @@ -1,20 +1,86 @@ - - +#include +#include #include #include +#include +#include +#include +#include #include + +#define PATH_MAX 4096 + int main(int argc, char *argv[]) { + // Check for correct usage if (argc != 2) { - fprintf(stderr, "Usage: ./opener [FILENAME]"); + fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } - int fd = open(argv[1], O_RDWR | O_CREAT); - if (fd == -1) { - perror("Cannot open file"); + const char *path = argv[1]; + struct stat statbuf; + + // Stat the given path to determine if it's a directory + if (lstat(path, &statbuf) == -1) { + perror("lstat"); return 1; } - close(fd); - return 0; + // Case 1: The path is not a directory + if (!S_ISDIR(statbuf.st_mode)) { + int fd = open(path, O_RDONLY); + if (fd == -1) { + perror("open"); + return 1; + } + close(fd); + return 0; + } + + // Case 2: The path is a directory + DIR *dirp = opendir(path); + if (dirp == NULL) { + perror("opendir"); + return 1; + } + + struct dirent *entry; + int success = 1; + + while ((entry = readdir(dirp)) != NULL) { + // Skip . and .. + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + // Construct the full path + char fullpath[PATH_MAX]; + snprintf(fullpath, PATH_MAX, "%s/%s", path, entry->d_name); + + // Stat the entry to check if it's a regular file + struct stat entry_stat; + if (lstat(fullpath, &entry_stat) == -1) { + perror("lstat"); + success = 0; + break; + } + + // Only process regular files + if (!S_ISREG(entry_stat.st_mode)) { + continue; + } + + // Try to open and immediately close the file + int fd = open(fullpath, O_RDONLY); + if (fd == -1) { + perror("open"); + success = 0; + break; + } + close(fd); + } + + closedir(dirp); + + return (success ? 0 : 1); } diff --git a/test/test.bash b/test/test.bash index ba0d9c8..e287240 100755 --- a/test/test.bash +++ b/test/test.bash @@ -160,27 +160,54 @@ openers/symlinked_opener2 ./protected/motto >/dev/null 2>/dev/null && # test permission globbing +icfs_dialogue --set-fake-response yes +icfs_dialogue --set-fake-response-filename "/" +openers/opener3 ./protected/haystack >/dev/null 2>/dev/null && + echo "[ICFS-TEST]: OK" || + echo "[ICFS-TEST]: openers/opener3 cannot read protected/haystack/needle despite access being permitted!" # OK + +icfs_dialogue --set-fake-response no +icfs_dialogue --set-fake-response-filename "/" +openers/opener4 ./protected/haystack >/dev/null 2>/dev/null && + echo "[ICFS-TEST]: openers/opener4 can read files in protected/haystack despite access being denied!" || + echo "[ICFS-TEST]: OK" # EACCESS + icfs_dialogue --set-fake-response yes_perm icfs_dialogue --set-fake-response-filename "/" -grep 'Liberty' ./protected/haystack/needle >/dev/null && +openers/opener5 ./protected/haystack/needle >/dev/null 2>/dev/null && echo "[ICFS-TEST]: OK" || - echo "[ICFS-TEST]: grep cannot read protected/motto despite access being permitted!" # OK + echo "[ICFS-TEST]: openers/opener5 cannot read protected/haystack/needle despite access being permitted!" # OK icfs_dialogue --set-fake-response no # this should be ignored -grep "Liberty" ./protected/haystack/* >/dev/null && +openers/opener5 ./protected/haystack >/dev/null 2>/dev/null && echo "[ICFS-TEST]: OK" || - echo "[ICFS-TEST]: grep cannot read protected/motto despite access being permitted!" # OK + echo "[ICFS-TEST]: openers/opener5 cannot read files in protected/haystack despite access being permitted!" # OK + +icfs_dialogue --set-fake-response no_perm +icfs_dialogue --set-fake-response-filename "/" +openers/opener6 ./protected/haystack/needle >/dev/null 2>/dev/null && + echo "[ICFS-TEST]: openers/opener6 can read protected/haystack/needle despite access being denied!" || + echo "[ICFS-TEST]: OK" # EACCESS + +icfs_dialogue --set-fake-response yes # this should be ignored +openers/opener6 ./protected/haystack >/dev/null 2>/dev/null && + echo "[ICFS-TEST]: openers/opener6 can read files in protected/haystack despite access being denied!" || + echo "[ICFS-TEST]: OK" # EACCESS # test database access -if [[ -r "./.pt.db" || -w "./.pt.db" ]]; then - echo "[ICFS-TEST]: permanent permissions database is accessible!" +if [[ $1 == '--setuid' ]]; then + if [[ -r "./.pt.db" || -w "./.pt.db" ]]; then + echo "[ICFS-TEST]: permanent permissions database is accessible!" + else + echo "[ICFS-TEST]: OK" + fi else - echo "[ICFS-TEST]: OK" + echo "[ICFS-TEST]: permanent permissions database access was not tested due to the lack of seuid bit setting capabilites. To test this, run the script with '--setuid' flag" fi # unmount sleep 0.5 #lsof +f -- $(realpath ./protected) -umount $(realpath ./protected) +umount "$(realpath ./protected)" sleep 0.5