diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..bb55591 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,16 @@ +--- +Checks: > + clang-analyzer-*, + bugprone-*, + cert-*, + -cert-dcl37-c, + -cert-dcl51-cpp + +HeaderFilterRegex: '^(.*/)?src/.*' +ExcludeHeaderFilterRegex: '/(external|thirdparty|vendor|deps|build|CMakeFiles|conan| vcpkg|subprojects)/' + +WarningsAsErrors: '*' + +CheckOptions: + - key: bugprone-easily-swappable-parameters.MinimumLength + value: '3' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69bc1e2..e95bc40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,9 +10,9 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 - - name: "Clangd install" - run: sudo apt install clang-format - - name: "Clangd format" + - name: "Clang tools install" + run: sudo apt-get install -y clang-format clang-tidy + - name: "Clang format" run: git ls-files -z '*.c' | xargs -0 clang-format --dry-run --Werror - name: "Download submodules" run: git submodule update --init --recursive @@ -27,6 +27,14 @@ jobs: libasound2-dev libgl1-mesa-dev libglu1-mesa-dev libwayland-dev libxkbcommon-dev libegl-dev wayland-protocols pkg-config valgrind + - name: Generate compile_commands.json + run: zig build cdb + - name: "Clang-tidy static analysis" + run: > + clang-tidy + src/cli.c src/config.c src/display.c src/keyicon.c + src/main.c src/search.c src/structures.c src/theme.c + -p compile_commands.json - name: Cleanup cache folders continue-on-error: true run: | diff --git a/README.md b/README.md index 4326895..3c165c9 100644 --- a/README.md +++ b/README.md @@ -146,3 +146,22 @@ valgrind --leak-check=full \ ```bash zig build cdb ``` + +### Static analysis (clang-tidy) + +Requires `clang-tidy` and a `compile_commands.json`. Generate the compilation +database first: + +```bash +zig build cdb +``` + +Then run clang-tidy against the project sources: + +```bash +clang-tidy \ + src/cli.c src/config.c src/display.c src/keyicon.c \ + src/main.c src/search.c src/structures.c src/theme.c \ + -p compile_commands.json +``` + diff --git a/build.zig b/build.zig index 1d113a5..7b749b9 100644 --- a/build.zig +++ b/build.zig @@ -9,7 +9,8 @@ const c_flags = [_][]const u8{ "-pedantic", "-D_POSIX_C_SOURCE=200809L", "-Wshadow", - "-Wvla", + // "-Wvla", + "-Wno-vla", "-Wfloat-equal", "-Wdouble-promotion", "-Wformat=2", diff --git a/src/cli.c b/src/cli.c index 17bda17..d44559a 100644 --- a/src/cli.c +++ b/src/cli.c @@ -54,6 +54,8 @@ bool parse_cli(int argc, char **argv, cli_args *args) { case '?': cag_option_print_error(&context, stderr); return false; + default: + break; } } diff --git a/src/config.c b/src/config.c index c1dbfe1..c337790 100644 --- a/src/config.c +++ b/src/config.c @@ -17,7 +17,7 @@ static void capitalize_into(const char *src, char *buf) { size_t len = strlen(src); memcpy(buf, src, len); buf[len] = '\0'; - buf[0] = toupper((unsigned char)buf[0]); + buf[0] = (char)toupper((unsigned char)buf[0]); } config_error_t config_read_file(const char *filepath, stringlist_t *out) { @@ -46,14 +46,18 @@ config_error_t config_read_file(const char *filepath, stringlist_t *out) { pos[sizeof(PATTERN) - 1] == ' ') { if (stringlist_append(out, pos) != 0) { free(line); - fclose(fp); + int err = fclose(fp); + if (err) + return CONFIG_ERR_IO; return CONFIG_ERR_OUT_OF_MEMORY; } } } free(line); - fclose(fp); + int err = fclose(fp); + if (err) + return CONFIG_ERR_IO; return CONFIG_SUCCESS; } @@ -92,7 +96,7 @@ void keymap_free(KeyMap *km) { free(km->main_key); for (size_t i = 0; i < km->modifier_count; i++) free(km->modifiers[i]); - free(km->modifiers); + free((char *)(km->modifiers)); free(km->description); } @@ -120,14 +124,14 @@ config_error_t parse_key_maps(stringlist_t *lines, KeyMapList *out) { if (!space) continue; - key_combo = strndup(pos, space - pos); + key_combo = strndup(pos, (size_t)(space - pos)); if (!key_combo) goto fail; size_t len = strlen(space + 1); // Length after the space desc = malloc(len + 1); if (!desc) { - return CONFIG_ERR_ALLOC_FAILED; + goto fail; } capitalize_into(space + 1, desc); if (!desc) @@ -139,8 +143,8 @@ config_error_t parse_key_maps(stringlist_t *lines, KeyMapList *out) { char *tok = strtok(tmp, "+"); while (tok) { - char **new_tokens = - realloc(tokens, (token_count + 1) * sizeof(char *)); + char **new_tokens = (char **)(realloc( + (char *)tokens, (token_count + 1) * sizeof(char *))); if (!new_tokens) goto fail; tokens = new_tokens; @@ -157,6 +161,12 @@ config_error_t parse_key_maps(stringlist_t *lines, KeyMapList *out) { free(key_combo); key_combo = NULL; + if (token_count == 0) { + free(desc); + free((char *)tokens); + return CONFIG_ERR_ALLOC_FAILED; // or continue; + } + KeyMap km = {0}; km.description = desc; km.main_key = tokens[token_count - 1]; @@ -172,7 +182,7 @@ config_error_t parse_key_maps(stringlist_t *lines, KeyMapList *out) { fail: for (size_t j = 0; j < token_count; j++) free(tokens[j]); - free(tokens); + free((char *)tokens); free(tmp); free(key_combo); free(desc); diff --git a/src/config.h b/src/config.h index 8e7aaae..81279c0 100644 --- a/src/config.h +++ b/src/config.h @@ -15,6 +15,7 @@ typedef enum { CONFIG_ERR_INVALID_FORMAT, CONFIG_ERR_INVALID_ARGUMENT, CONFIG_ERR_ALLOC_FAILED, + CONFIG_ERR_IO, } config_error_t; typedef struct { diff --git a/src/display.c b/src/display.c index 2bc8180..d5be1e6 100644 --- a/src/display.c +++ b/src/display.c @@ -58,9 +58,9 @@ size_t font_paths_len(const char *arr[]) { return i; } -static font_container_t load_system_font(int size); +static font_container_t load_system_font(float size); static void format_keys(const KeyMap *km, char *buf, size_t bufsize); -static Scroll scroll_create(int content_height, int window_height); +static Scroll scroll_create(float content_height, float window_height); static void scroll_update(Scroll *s); static void draw_top(theme_color_t color, theme_color_t text_color, Font font, float font_size, float spacing); @@ -75,7 +75,7 @@ static void draw_body(theme_color_t color, theme_color_t text_color, const size_t count, float offset_y); static font_container_t load_font_from_paths(const stringlist_t *paths, - int size) { + float size) { if (!paths || paths->count == 0) { return (font_container_t){.font = GetFontDefault(), .error = DISPLAY_STRINGLIST_UNINITIALIZED}; @@ -90,13 +90,13 @@ static font_container_t load_font_from_paths(const stringlist_t *paths, codepoints[i] = 32 + i; memcpy(&codepoints[ascii_count], key_codepoints, - key_codepoints_count * sizeof(int)); + (size_t)key_codepoints_count * sizeof(int)); for (size_t i = 0; i < paths->count; i++) { const char *path = paths->items[i]; if (FileExists(path)) { - Font font = LoadFontEx(path, size, codepoints, total); + Font font = LoadFontEx(path, (int)size, codepoints, total); printf("Loading font %s\n", path); return (font_container_t){.font = font, .error = DISPLAY_SUCCESS}; } @@ -106,7 +106,7 @@ static font_container_t load_font_from_paths(const stringlist_t *paths, .error = DISPLAY_FONT_LOAD_ERROR}; } -static font_container_t load_system_font(int size) { +static font_container_t load_system_font(float size) { stringlist_t list; stringlist_init(&list); @@ -147,6 +147,9 @@ static Font load_best_font(const theme_font_t *font) { } static void format_keys(const KeyMap *km, char *buf, size_t bufsize) { + if (km == NULL) { + return; + } buf[0] = '\0'; for (size_t j = 0; j < km->modifier_count; j++) { strncat(buf, key_to_symbol(km->modifiers[j]), @@ -156,7 +159,7 @@ static void format_keys(const KeyMap *km, char *buf, size_t bufsize) { strncat(buf, key_to_symbol(km->main_key), bufsize - strlen(buf) - 1); } -static Scroll scroll_create(int content_height, int window_height) { +static Scroll scroll_create(float content_height, float window_height) { float min = -(content_height - window_height); return (Scroll){.y = 0, .min = min > 0 ? 0 : min}; } @@ -197,6 +200,9 @@ static void draw_text_highlighted(Font font, visible_item_t item, float spacing, Color text_color, Color highlight_text_color, Color highlight_color) { + if (item.km == NULL) { + return; + } const char *text = item.km->description; if (text == NULL) return; @@ -330,7 +336,8 @@ void display(const KeyMapList *kml, const theme_t *theme) { float spacing = 1.0f; - int content_height = PADDING + (int)kml->count * ROW_HEIGHT + PADDING; + float content_height = + (float)(PADDING + (int)kml->count * ROW_HEIGHT + PADDING); Scroll scroll = scroll_create(content_height, WINDOW_HEIGHT); Font top_font = load_best_font(&theme->top.font); diff --git a/src/keyicon.h b/src/keyicon.h index 93b7c01..be842b9 100644 --- a/src/keyicon.h +++ b/src/keyicon.h @@ -1,6 +1,9 @@ #ifndef KEYICON_H #define KEYICON_H +#define MAX_CODEPOINTS 512 + +#include typedef struct { const char *name; const char *symbol; diff --git a/src/main.c b/src/main.c index 197e111..d19da1d 100644 --- a/src/main.c +++ b/src/main.c @@ -12,6 +12,7 @@ typedef enum { Success = 0, ConfigError, ThemeError, + IOError, } Error; int main(int argc, char *argv[]) { @@ -34,18 +35,22 @@ int main(int argc, char *argv[]) { err = theme_load_from_config(&theme); } if (err != THEME_SUCCESS) { - fprintf(stderr, "Failed to set THEME: %s\n", theme_error_str(err)); + if (fprintf(stderr, "Failed to set THEME: %s\n", theme_error_str(err))) + return IOError; + return ThemeError; } char *filepath = config_get_sway_filepath(); if (!filepath) { - fprintf(stderr, "failed to determine sway config path\n"); + if (fprintf(stderr, "failed to determine sway config path\n")) + return IOError; return ConfigError; } if (config_read_file(filepath, &list) != 0) { - fprintf(stderr, "failed to read file\n"); + if (fprintf(stderr, "failed to read file\n")) + return IOError; free(filepath); stringlist_free(&list); return ConfigError; @@ -56,7 +61,8 @@ int main(int argc, char *argv[]) { keymaplist_init(&kml); if (parse_key_maps(&list, &kml) != 0) { - fprintf(stderr, "failed to parse key maps\n"); + if (fprintf(stderr, "failed to parse key maps\n")) + return IOError; stringlist_free(&list); return 1; } diff --git a/src/search.c b/src/search.c index 47f3143..4429ba2 100644 --- a/src/search.c +++ b/src/search.c @@ -92,7 +92,7 @@ void free_search_result(search_result_t *result) { intlist_delete(result->positions[i]); /* uses new helper */ } } - free(result->positions); + free((intlist_t *)(result->positions)); } free(result->mask); memset(result, 0, sizeof(*result)); diff --git a/src/structures.c b/src/structures.c index f7e6c59..7057b58 100644 --- a/src/structures.c +++ b/src/structures.c @@ -8,7 +8,8 @@ stringlist_error_t stringlist_append(stringlist_t *list, const char *s) { if (list->count == list->capacity) { size_t new_cap = list->capacity ? list->capacity * 2 : 8; - char **tmp = (char **)realloc(list->items, new_cap * sizeof(*tmp)); + char **tmp = + (char **)realloc((char *)(list->items), new_cap * sizeof(*tmp)); if (!tmp) return STRINGLIST_ERR_ALLOC_FAILED; list->items = tmp; @@ -33,7 +34,7 @@ void stringlist_free(stringlist_t *list) { if (list->items) { for (size_t i = 0; i < list->count; i++) free(list->items[i]); - free(list->items); + free((char *)(list->items)); } stringlist_init(list); } @@ -134,7 +135,8 @@ size_t get_segments(const char *text, const intlist_t *positions, segment_type_t tmp = in_intlist(0, positions) ? HIGHLIGHTED : NORMAL; for (size_t i = 0; i < len; ++i) { - segment_type_t type = in_intlist(i, positions) ? HIGHLIGHTED : NORMAL; + segment_type_t type = + in_intlist((int)i, positions) ? HIGHLIGHTED : NORMAL; if (type != tmp) { segment_t segment = { diff --git a/src/theme.c b/src/theme.c index ff7797a..e77cbb3 100644 --- a/src/theme.c +++ b/src/theme.c @@ -41,31 +41,53 @@ void parse_hex_color(const char *hex_str, theme_color_t *c) { if (hex_str == NULL || c == NULL) return; - if (hex_str[0] != '#') { + if (hex_str[0] != '#') return; - } - hex_str++; - unsigned int r = 0, g = 0, b = 0, a = 255; + hex_str++; // skip '#' - if (sscanf(hex_str, "%02x%02x%02x%02x", &r, &g, &b, &a) == 4) { - c->r = (uint8_t)r; - c->g = (uint8_t)g; - c->b = (uint8_t)b; - c->a = (uint8_t)a; - c->has_alpha = true; + size_t len = strlen(hex_str); + if (len != 6 && len != 8) return; - } - // Fall back to 6-digit format: #RRGGBB (alpha stays 255) - if (sscanf(hex_str, "%02x%02x%02x", &r, &g, &b) == 3) { - c->r = (uint8_t)r; - c->g = (uint8_t)g; - c->b = (uint8_t)b; + char buf[3] = {0}; + char *endptr; + unsigned long v; + + errno = 0; + + buf[0] = hex_str[0]; + buf[1] = hex_str[1]; + v = strtoul(buf, &endptr, 16); + if (errno != 0 || *endptr != '\0') return; - } + c->r = (uint8_t)v; + + buf[0] = hex_str[2]; + buf[1] = hex_str[3]; + v = strtoul(buf, &endptr, 16); + if (errno != 0 || *endptr != '\0') + return; + c->g = (uint8_t)v; - return; + buf[0] = hex_str[4]; + buf[1] = hex_str[5]; + v = strtoul(buf, &endptr, 16); + if (errno != 0 || *endptr != '\0') + return; + c->b = (uint8_t)v; + + if (len == 8) { + buf[0] = hex_str[6]; + buf[1] = hex_str[7]; + v = strtoul(buf, &endptr, 16); + if (errno != 0 || *endptr != '\0') + return; + c->a = (uint8_t)v; + c->has_alpha = true; + } else { + c->has_alpha = false; + } } static char *get_directory(const char *filepath) { @@ -117,7 +139,10 @@ static theme_error_t create_file(const char *filepath, bool create_dir) { if (!fd) { return THEME_IO_ERROR; } - fclose(fd); + int err = fclose(fd); + if (err) { + return THEME_IO_ERROR; + } return THEME_SUCCESS; } @@ -275,6 +300,9 @@ theme_toml_parse(const toml_result_t *toml, theme_layer_t *layer, parse_hex_color(font_color.u.str.ptr, &font.color); font.has_color = true; } else if (font_color.type != TOML_UNKNOWN) { + if (font.file != NULL) { + free(font.file); + } return THEME_PARSING_ERROR; } @@ -464,13 +492,18 @@ char *theme_get_config_filepath(void) { size_t len_home = strlen(home); int trim = (len_home > 0 && home[len_home - 1] == '/'); - size_t total_len = len_home - trim + strlen(suffix) + 1; + size_t total_len = len_home - (size_t)trim + strlen(suffix) + 1; char *path = malloc(total_len); if (!path) return NULL; - snprintf(path, total_len, "%.*s%s", (int)(len_home - trim), home, suffix); + int count = snprintf(path, total_len, "%.*s%s", + (int)(len_home - (size_t)trim), home, suffix); + if (count < 0) { + free(path); + return NULL; + } return path; }