From 9889ddf563c0add78b7e4b421b6c685688b7d366 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 7 Mar 2026 20:43:57 +0800 Subject: [PATCH] CI: Add Clang Static Analyzer This integrates scan-build into GitHub Actions: - Standard checkers as a blocking gate (--status-bugs) - Alpha checkers as non-blocking informational reporting - Extract setup_llvm_repo() in install-deps.sh to share LLVM 20 APT repository setup between format and analysis modes - Upload HTML scan-build reports as artifacts on failure --- .ci/install-deps.sh | 58 +++++++++++++++++++++++--------------- .github/workflows/main.yml | 37 ++++++++++++++++++++++++ include/iui.h | 20 +++++++------ ports/headless.c | 4 +-- ports/headless.h | 2 +- src/basic.c | 6 ++-- src/container.c | 4 +-- src/core.c | 8 +++--- src/fab.c | 4 +-- src/input.c | 6 ++-- src/internal.h | 15 +++++++++- src/layout.c | 12 ++++---- src/navigation.c | 23 ++++++++------- src/pickers.c | 2 +- tests/example.c | 26 ++++++++--------- tests/forthsalon-demo.c | 5 +--- tests/pixelwall-demo.c | 18 +++++++----- tests/test-focus.c | 2 +- tests/test-init.c | 8 +++--- tests/test-layout.c | 6 ++-- tests/test-modal.c | 2 +- tests/test-slider.c | 2 +- tests/test-spec.c | 2 +- tests/test-tracking.c | 4 +-- 24 files changed, 170 insertions(+), 106 deletions(-) diff --git a/.ci/install-deps.sh b/.ci/install-deps.sh index b2aba96..fe3a2e8 100755 --- a/.ci/install-deps.sh +++ b/.ci/install-deps.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Install build dependencies for CI -# Usage: .ci/install-deps.sh [sdl2|headless|format] +# Usage: .ci/install-deps.sh [sdl2|headless|format|analysis] set -euo pipefail @@ -9,6 +9,28 @@ source "$(dirname "$0")/common.sh" MODE="${1:-sdl2}" +# Setup LLVM 20 APT repository (shared by format and analysis modes) +setup_llvm_repo() { + LLVM_KEYRING=/usr/share/keyrings/llvm-archive-keyring.gpg + LLVM_KEY_FP="6084F3CF814B57C1CF12EFD515CF4D18AF4F7421" + TMPKEY=$(mktemp) + download_to_file https://apt.llvm.org/llvm-snapshot.gpg.key "$TMPKEY" + ACTUAL_FP=$(gpg --with-fingerprint --with-colons "$TMPKEY" 2>/dev/null | grep fpr | head -1 | cut -d: -f10) + if [ "$ACTUAL_FP" != "$LLVM_KEY_FP" ]; then + print_error "LLVM key fingerprint mismatch!" + print_error "Expected: $LLVM_KEY_FP" + print_error "Got: $ACTUAL_FP" + rm -f "$TMPKEY" + exit 1 + fi + sudo gpg --dearmor -o "$LLVM_KEYRING" <"$TMPKEY" + rm -f "$TMPKEY" + + CODENAME=$(lsb_release -cs) + echo "deb [signed-by=${LLVM_KEYRING}] https://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-20 main" | sudo tee /etc/apt/sources.list.d/llvm-20.list + sudo apt-get update -q=2 +} + case "$MODE" in sdl2) if [ "$OS_TYPE" = "Linux" ]; then @@ -33,35 +55,25 @@ format) fi sudo apt-get update -q=2 sudo apt-get install -y -q=2 --no-install-recommends shfmt python3-pip gnupg ca-certificates lsb-release - - # Install clang-format-20 from LLVM repository - # LLVM signing key fingerprint: 6084F3CF814B57C1CF12EFD515CF4D18AF4F7421 - LLVM_KEYRING=/usr/share/keyrings/llvm-archive-keyring.gpg - LLVM_KEY_FP="6084F3CF814B57C1CF12EFD515CF4D18AF4F7421" - TMPKEY=$(mktemp) - download_to_file https://apt.llvm.org/llvm-snapshot.gpg.key "$TMPKEY" - ACTUAL_FP=$(gpg --with-fingerprint --with-colons "$TMPKEY" 2>/dev/null | grep fpr | head -1 | cut -d: -f10) - if [ "$ACTUAL_FP" != "$LLVM_KEY_FP" ]; then - print_error "LLVM key fingerprint mismatch!" - print_error "Expected: $LLVM_KEY_FP" - print_error "Got: $ACTUAL_FP" - rm -f "$TMPKEY" - exit 1 - fi - sudo gpg --dearmor -o "$LLVM_KEYRING" <"$TMPKEY" - rm -f "$TMPKEY" - - CODENAME=$(lsb_release -cs) - echo "deb [signed-by=${LLVM_KEYRING}] https://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-20 main" | sudo tee /etc/apt/sources.list.d/llvm-20.list - sudo apt-get update -q=2 + setup_llvm_repo sudo apt-get install -y -q=2 --no-install-recommends clang-format-20 # Install Python formatter (version-pinned for reproducibility) pip3 install --break-system-packages --only-binary=:all: black==25.1.0 ;; +analysis) + if [ "$OS_TYPE" != "Linux" ]; then + print_error "Static analysis only supported on Linux" + exit 1 + fi + sudo apt-get update -q=2 + sudo apt-get install -y -q=2 --no-install-recommends gnupg ca-certificates lsb-release libsdl2-dev + setup_llvm_repo + sudo apt-get install -y -q=2 --no-install-recommends clang-20 clang-tools-20 + ;; *) print_error "Unknown mode: $MODE" - echo "Usage: $0 [sdl2|headless|format]" + echo "Usage: $0 [sdl2|headless|format|analysis]" exit 1 ;; esac diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e9af38..84a1b9a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -60,6 +60,43 @@ jobs: - name: Check code formatting run: .ci/check-format.sh + static-analysis: + needs: [detect-code-related-file-changes] + if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true' + timeout-minutes: 15 + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - name: Install dependencies + run: .ci/install-deps.sh analysis + - name: scan-build + run: | + make defconfig + make clean + scan-build-20 --status-bugs \ + -o /tmp/scan-build-report \ + make -j$(nproc) + - name: Upload scan-build report + if: failure() + uses: actions/upload-artifact@v7 + with: + name: scan-build-report + path: /tmp/scan-build-report + retention-days: 7 + if-no-files-found: ignore + - name: scan-build (alpha checkers, informational) + if: always() + continue-on-error: true + run: | + make clean + scan-build-20 \ + -enable-checker alpha.deadcode.UnreachableCode \ + -enable-checker alpha.unix.cstring.OutOfBounds \ + -o /tmp/scan-build-alpha-report \ + make -j$(nproc) 2>&1 | tee /tmp/alpha-output.txt + WARNINGS=$(grep -c "warning:" /tmp/alpha-output.txt || true) + echo "::notice::Alpha checkers found $WARNINGS warnings (informational only)" + unit-tests: needs: [detect-code-related-file-changes] if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true' diff --git a/include/iui.h b/include/iui.h index afda0d3..6a95710 100644 --- a/include/iui.h +++ b/include/iui.h @@ -913,8 +913,8 @@ void iui_segmented(iui_context *ctx, */ void iui_slider(iui_context *ctx, const char *label, - float min, - float max, + float min_value, + float max_value, float step, float *value, const char *fmt); @@ -2031,7 +2031,7 @@ void iui_table_row_end(iui_context *ctx, iui_table_state *state); * @ctx: current UI context * @state: table state from iui_table_begin */ -void iui_table_end(iui_context *ctx, iui_table_state *state); +void iui_table_end(iui_context *ctx, const iui_table_state *state); /* Scrollable Container * Creates a scrollable viewport using the existing clip stack. @@ -2189,7 +2189,7 @@ bool iui_nav_rail_item(iui_context *ctx, int index); /* End navigation rail rendering */ -void iui_nav_rail_end(iui_context *ctx, iui_nav_rail_state *state); +void iui_nav_rail_end(iui_context *ctx, const iui_nav_rail_state *state); /* Toggle rail expanded/collapsed state */ void iui_nav_rail_toggle(iui_nav_rail_state *state); @@ -2227,7 +2227,7 @@ bool iui_nav_bar_item(iui_context *ctx, int index); /* End navigation bar rendering */ -void iui_nav_bar_end(iui_context *ctx, iui_nav_bar_state *state); +void iui_nav_bar_end(iui_context *ctx, const iui_nav_bar_state *state); /* Navigation Drawer Component * Side panel navigation for larger screens @@ -2267,7 +2267,7 @@ bool iui_nav_drawer_item(iui_context *ctx, void iui_nav_drawer_divider(iui_context *ctx); /* End navigation drawer rendering */ -void iui_nav_drawer_end(iui_context *ctx, iui_nav_drawer_state *state); +void iui_nav_drawer_end(iui_context *ctx, const iui_nav_drawer_state *state); /* Open/close drawer */ void iui_nav_drawer_open(iui_nav_drawer_state *state); @@ -2339,7 +2339,8 @@ bool iui_bottom_sheet_begin(iui_context *ctx, float screen_height); /* End bottom sheet rendering */ -void iui_bottom_sheet_end(iui_context *ctx, iui_bottom_sheet_state *state); +void iui_bottom_sheet_end(iui_context *ctx, + const iui_bottom_sheet_state *state); /* Open/close bottom sheet */ void iui_bottom_sheet_open(iui_bottom_sheet_state *state); @@ -2387,12 +2388,13 @@ bool iui_bottom_app_bar_action(iui_context *ctx, /* Add FAB (right side). Returns true if clicked */ bool iui_bottom_app_bar_fab(iui_context *ctx, - iui_bottom_app_bar_state *state, + const iui_bottom_app_bar_state *state, const char *icon, iui_fab_size_t size); /* End bottom app bar rendering */ -void iui_bottom_app_bar_end(iui_context *ctx, iui_bottom_app_bar_state *state); +void iui_bottom_app_bar_end(iui_context *ctx, + const iui_bottom_app_bar_state *state); /* Menu Component * Vertical dropdown menu with support for icons, shortcuts, dividers, and diff --git a/ports/headless.c b/ports/headless.c index 519cf5a..beb60e2 100644 --- a/ports/headless.c +++ b/ports/headless.c @@ -710,7 +710,7 @@ const uint32_t *iui_headless_get_framebuffer(iui_port_ctx *ctx) /* * Get framebuffer dimensions. */ -void iui_headless_get_framebuffer_size(iui_port_ctx *ctx, +void iui_headless_get_framebuffer_size(const iui_port_ctx *ctx, int *width, int *height) { @@ -1165,7 +1165,7 @@ void iui_headless_process_shm_events(iui_port_ctx *ctx) /* Process all pending events */ while (hdr->event_read_idx != hdr->event_write_idx) { uint32_t idx = hdr->event_read_idx % IUI_SHM_EVENT_RING_SIZE; - iui_shm_event_t *ev = &events[idx]; + const iui_shm_event_t *ev = &events[idx]; switch (ev->type) { case IUI_SHM_EVENT_MOUSE_MOVE: diff --git a/ports/headless.h b/ports/headless.h index e6dbdc2..1fd9ef5 100644 --- a/ports/headless.h +++ b/ports/headless.h @@ -83,7 +83,7 @@ void iui_headless_inject_text(iui_port_ctx *ctx, uint32_t codepoint); const uint32_t *iui_headless_get_framebuffer(iui_port_ctx *ctx); /* Get framebuffer dimensions */ -void iui_headless_get_framebuffer_size(iui_port_ctx *ctx, +void iui_headless_get_framebuffer_size(const iui_port_ctx *ctx, int *width, int *height); diff --git a/src/basic.c b/src/basic.c index f04251e..b3413b5 100644 --- a/src/basic.c +++ b/src/basic.c @@ -113,12 +113,10 @@ void iui_segmented(iui_context *ctx, uint32_t text_color = is_selected ? ctx->colors.on_secondary_container : ctx->colors.on_surface; - /* Draw checkmark icon on selected segment */ - float icon_size = IUI_SEGMENTED_ICON_SIZE, - text_w = iui_get_text_width(ctx, entries[i]); - if (is_selected) { /* Calculate total content width: checkmark + gap + text */ + float icon_size = IUI_SEGMENTED_ICON_SIZE, + text_w = iui_get_text_width(ctx, entries[i]); float gap = 8.f, content_width = icon_size + gap + text_w, content_x = seg_x + (seg_width - content_width) / 2.f, icon_cx = content_x + icon_size / 2.f, diff --git a/src/container.c b/src/container.c index 4bea696..2b7a1a7 100644 --- a/src/container.c +++ b/src/container.c @@ -655,7 +655,7 @@ bool iui_bottom_sheet_begin(iui_context *ctx, return true; } -void iui_bottom_sheet_end(iui_context *ctx, iui_bottom_sheet_state *state) +void iui_bottom_sheet_end(iui_context *ctx, const iui_bottom_sheet_state *state) { if (!ctx || !state) return; @@ -1186,7 +1186,7 @@ void iui_table_row_end(iui_context *ctx, iui_table_state *state) 0.f, ctx->colors.outline_variant, ctx->renderer.user); } -void iui_table_end(iui_context *ctx, iui_table_state *state) +void iui_table_end(iui_context *ctx, const iui_table_state *state) { if (!ctx || !ctx->current_window || !state) return; diff --git a/src/core.c b/src/core.c index 7c62be9..5e140eb 100644 --- a/src/core.c +++ b/src/core.c @@ -1515,7 +1515,7 @@ void iui_pop_layer(iui_context *ctx) ctx->input_layer.current_z_order = 0; } else { /* Restore to previous stack entry */ - iui_layer_entry_t *prev = + const iui_layer_entry_t *prev = &ctx->input_layer.layer_stack[ctx->input_layer.layer_depth - 1]; ctx->input_layer.current_layer_id = prev->layer_id; ctx->input_layer.current_z_order = prev->z_order; @@ -1665,7 +1665,7 @@ void iui_register_textfield(iui_context *ctx, void *buffer) /* Probe entire table until finding empty slot or duplicate */ for (int probe = 0; probe < IUI_MAX_TRACKED_TEXTFIELDS; probe++) { - void *cached = ctx->field_tracking.textfield_ids[idx]; + const void *cached = ctx->field_tracking.textfield_ids[idx]; if (!cached) { /* Found empty slot, insert here */ ctx->field_tracking.textfield_ids[idx] = buffer; @@ -1715,7 +1715,7 @@ bool iui_textfield_is_registered(const iui_context *ctx, const void *buffer) /* Probe entire table looking for match or empty slot */ for (int probe = 0; probe < IUI_MAX_TRACKED_TEXTFIELDS; probe++) { - void *cached = ctx->field_tracking.textfield_ids[idx]; + const void *cached = ctx->field_tracking.textfield_ids[idx]; if (cached == buffer) return true; if (!cached) @@ -1725,7 +1725,7 @@ bool iui_textfield_is_registered(const iui_context *ctx, const void *buffer) return false; } -bool iui_slider_is_registered(const iui_context *ctx, uint32_t slider_id) +static bool iui_slider_is_registered(const iui_context *ctx, uint32_t slider_id) { if (slider_id == 0) return false; diff --git a/src/fab.c b/src/fab.c index ea4b8b4..bbe4784 100644 --- a/src/fab.c +++ b/src/fab.c @@ -77,9 +77,9 @@ static bool iui_fab_internal(iui_context *ctx, iui_internal_draw_text(ctx, content_x, label_y, label, content_color); } else { /* Standard FAB: centered icon only */ - float icon_cx = x + fab_w * 0.5f; - float icon_cy = center_y; if (icon) { + float icon_cx = x + fab_w * 0.5f; + float icon_cy = center_y; iui_draw_fab_icon(ctx, icon_cx, icon_cy, icon_size, icon, content_color); } diff --git a/src/input.c b/src/input.c index a5cdeb0..e9a61de 100644 --- a/src/input.c +++ b/src/input.c @@ -459,11 +459,11 @@ static void textfield_move_left(const char *buffer, state->cursor = prev; } while (state->cursor > 0) { - size_t prev = iui_utf8_prev(buffer, state->cursor); - uint32_t cp = iui_utf8_decode(buffer, prev, len); + size_t prev2 = iui_utf8_prev(buffer, state->cursor); + uint32_t cp = iui_utf8_decode(buffer, prev2, len); if (!iui_utf8_is_word_char(cp)) break; - state->cursor = prev; + state->cursor = prev2; } } else { state->cursor = iui_utf8_prev(buffer, state->cursor); diff --git a/src/internal.h b/src/internal.h index d26c056..6c56fb6 100644 --- a/src/internal.h +++ b/src/internal.h @@ -11,11 +11,24 @@ #ifndef IUI_INTERNAL_H #define IUI_INTERNAL_H +#include #include #include #include #include +/* Assert macro that is invisible to the Clang Static Analyzer. + * Preserves debug-mode abort and release-mode recovery (defense-in-depth) + * without triggering alpha.deadcode.UnreachableCode false positives. + */ +#ifdef __clang_analyzer__ +#define IUI_ASSERT(cond) \ + do { \ + } while (0) +#else +#define IUI_ASSERT(cond) assert(cond) +#endif + #include "font.h" #include "iui.h" #include "iui_config.h" @@ -1212,7 +1225,7 @@ void iui_field_tracking_frame_end(iui_context *ctx); bool iui_textfield_is_registered(const iui_context *ctx, const void *buffer); /* Check if a slider was registered this frame */ -bool iui_slider_is_registered(const iui_context *ctx, uint32_t slider_id); +/* iui_slider_is_registered is static in core.c */ /* MD3 Runtime Validation (Debug Builds) * These macros validate that rendered components conform to MD3 specifications. diff --git a/src/layout.c b/src/layout.c index 9b915e4..643d534 100644 --- a/src/layout.c +++ b/src/layout.c @@ -810,10 +810,11 @@ void iui_end_window(iui_context *ctx) /* Assert balanced clips and no abandoned scroll regions. * Clear current_window BEFORE iui_pop_clip so the floor guard * (depth >= 1 while inside a window) allows this final pop. */ - assert(!ctx->active_scroll && - "abandoned scroll region: iui_scroll_end not called inside window"); - assert(ctx->clip.depth == 1 && - "unbalanced iui_push_clip/iui_pop_clip inside window"); + IUI_ASSERT( + !ctx->active_scroll && + "abandoned scroll region: iui_scroll_end not called inside window"); + IUI_ASSERT(ctx->clip.depth == 1 && + "unbalanced iui_push_clip/iui_pop_clip inside window"); ctx->clip.depth = 1; /* safety: discard any leaked clips */ ctx->current_window = NULL; /* must precede pop for floor guard bypass */ iui_pop_clip(ctx); @@ -855,7 +856,8 @@ void iui_end_frame(iui_context *ctx) ctx->active_scroll = NULL; /* Each begin_window/end_window pair must balance its clip push/pop. */ - assert(ctx->clip.depth == 0 && "leaked clip region across frame boundary"); + IUI_ASSERT(ctx->clip.depth == 0 && + "leaked clip region across frame boundary"); if (ctx->clip.depth != 0) { /* Release recovery: reset both logical and renderer clip state so the * next frame starts clean rather than with stale clipping applied. */ diff --git a/src/navigation.c b/src/navigation.c index 1e147d1..c7b2dac 100644 --- a/src/navigation.c +++ b/src/navigation.c @@ -188,7 +188,7 @@ bool iui_nav_rail_item(iui_context *ctx, return false; } -void iui_nav_rail_end(iui_context *ctx, iui_nav_rail_state *state) +void iui_nav_rail_end(iui_context *ctx, const iui_nav_rail_state *state) { if (!ctx || !state) return; @@ -258,8 +258,6 @@ bool iui_nav_bar_item(iui_context *ctx, /* Calculate vertical positions */ float indicator_y = item_y + 12.f; float icon_cy = indicator_y + IUI_NAV_BAR_INDICATOR_HEIGHT * 0.5f; - float label_y = - item_y + 12.f + IUI_NAV_BAR_INDICATOR_HEIGHT + IUI_NAV_BAR_LABEL_GAP; /* Draw selection indicator */ if (selected) { @@ -295,6 +293,8 @@ bool iui_nav_bar_item(iui_context *ctx, /* Draw label */ if (label) { + float label_y = item_y + 12.f + IUI_NAV_BAR_INDICATOR_HEIGHT + + IUI_NAV_BAR_LABEL_GAP; float label_width = iui_get_text_width(ctx, label); float label_x = item_x + (item_width - label_width) * 0.5f; uint32_t label_color = @@ -311,7 +311,7 @@ bool iui_nav_bar_item(iui_context *ctx, return false; } -void iui_nav_bar_end(iui_context *ctx, iui_nav_bar_state *state) +void iui_nav_bar_end(iui_context *ctx, const iui_nav_bar_state *state) { if (!ctx || !state) return; @@ -480,14 +480,12 @@ void iui_nav_drawer_divider(iui_context *ctx) ctx->layout.y += 8.f; float x = ctx->layout.x + IUI_NAV_DRAWER_PADDING_H; float w = IUI_NAV_DRAWER_WIDTH - 2 * IUI_NAV_DRAWER_PADDING_H; - if (w > 0.f) { - ctx->renderer.draw_box((iui_rect_t) {x, ctx->layout.y, w, 1.f}, 0.f, - ctx->colors.outline_variant, ctx->renderer.user); - } + ctx->renderer.draw_box((iui_rect_t) {x, ctx->layout.y, w, 1.f}, 0.f, + ctx->colors.outline_variant, ctx->renderer.user); ctx->layout.y += 1.f + 8.f; } -void iui_nav_drawer_end(iui_context *ctx, iui_nav_drawer_state *state) +void iui_nav_drawer_end(iui_context *ctx, const iui_nav_drawer_state *state) { if (!ctx || !state) return; @@ -565,7 +563,7 @@ bool iui_bottom_app_bar_action(iui_context *ctx, } bool iui_bottom_app_bar_fab(iui_context *ctx, - iui_bottom_app_bar_state *state, + const iui_bottom_app_bar_state *state, const char *icon, iui_fab_size_t size) { @@ -618,7 +616,8 @@ bool iui_bottom_app_bar_fab(iui_context *ctx, return comp_state == IUI_STATE_PRESSED; } -void iui_bottom_app_bar_end(iui_context *ctx, iui_bottom_app_bar_state *state) +void iui_bottom_app_bar_end(iui_context *ctx, + const iui_bottom_app_bar_state *state) { if (!ctx || !state) return; @@ -721,7 +720,7 @@ bool iui_side_sheet_begin(iui_context *ctx, /* Draw sheet background (surface color, elevated slightly if standard) */ iui_rect_t sheet_rect = {animated_x, 0, sheet_width, sheet_height}; iui_elevation_t elevation = - state->modal ? IUI_ELEVATION_1 : IUI_ELEVATION_1; + state->modal ? IUI_ELEVATION_2 : IUI_ELEVATION_1; /* Draw left border for standard sheets (outline variant) */ if (!state->modal) { diff --git a/src/pickers.c b/src/pickers.c index b1dcc80..8756d3b 100644 --- a/src/pickers.c +++ b/src/pickers.c @@ -471,7 +471,6 @@ bool iui_time_picker(iui_context *ctx, /* Add space for AM/PM toggle if using 12H format */ float ampm_width = 0.f; - float ampm_total_height = IUI_TIME_PICKER_AMPM_HEIGHT; /* 96dp total */ if (!picker->use_24h) { ampm_width = IUI_TIME_PICKER_AMPM_WIDTH; /* 52dp */ dialog_w += ampm_width + padding; @@ -584,6 +583,7 @@ bool iui_time_picker(iui_context *ctx, /* AM/PM toggle (12H only) */ if (!picker->use_24h) { + float ampm_total_height = IUI_TIME_PICKER_AMPM_HEIGHT; /* 96dp total */ float ampm_x = dialog_x + dialog_w - padding - ampm_width; /* MD3: total height 96dp with 12dp gap = 42dp per button */ float ampm_gap = 12.f; /* material_clock_period_toggle_vertical_gap */ diff --git a/tests/example.c b/tests/example.c index f92d137..eb5a663 100644 --- a/tests/example.c +++ b/tests/example.c @@ -449,7 +449,7 @@ static void draw_vector_demo_window(iui_context *ui, float progress_value) /* Concentric circles with rotating dots */ float cx3 = area.x + 310.f; float cy3 = area.y + 55.f; - uint32_t ring_colors[] = {COLOR_PURPLE, COLOR_BLUE, COLOR_CYAN}; + const uint32_t ring_colors[] = {COLOR_PURPLE, COLOR_BLUE, COLOR_CYAN}; for (int ring = 0; ring < 3; ring++) { float r = 20.f + ring * 15.f; @@ -506,9 +506,9 @@ static void draw_vector_demo_window(iui_context *ui, float progress_value) /* Starburst */ float cx5 = area.x + 180.f; float cy5 = area.y + 160.f; - uint32_t star_colors[] = {COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, - COLOR_GREEN, COLOR_CYAN, COLOR_BLUE, - COLOR_PURPLE, COLOR_PINK}; + const uint32_t star_colors[] = {COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, + COLOR_GREEN, COLOR_CYAN, COLOR_BLUE, + COLOR_PURPLE, COLOR_PINK}; for (int i = 0; i < 16; i++) { float angle = (float) i * (float) M_PI / 8.f + progress_value * (float) M_PI; @@ -526,7 +526,8 @@ static void draw_vector_demo_window(iui_context *ui, float progress_value) prev_x = bx; prev_y = by + bh; - uint32_t colors[] = {COLOR_PURPLE, COLOR_BLUE, COLOR_CYAN, COLOR_GREEN}; + const uint32_t colors[] = {COLOR_PURPLE, COLOR_BLUE, COLOR_CYAN, + COLOR_GREEN}; for (int i = 1; i <= 12; i++) { float t = (float) i / 12.f; float x = bx + t * bw; @@ -1126,7 +1127,6 @@ static void draw_iui_components_window(iui_context *ui, float delta_time) } else if (tab_sel == 4) { /* Extras Tab - Tooltips, Badges, Banner, Data Table */ static bool show_banner = true; - static int banner_action = 0; /* Tooltip demo */ iui_text_label_small(ui, IUI_ALIGN_LEFT, "TOOLTIPS"); @@ -1194,7 +1194,7 @@ static void draw_iui_components_window(iui_context *ui, float delta_time) .action2 = "Dismiss", .icon = "warning", }; - banner_action = iui_banner(ui, &banner_opts); + int banner_action = iui_banner(ui, &banner_opts); if (banner_action == 1) { printf("Banner: Retry clicked\n"); } else if (banner_action == 2) { @@ -1211,7 +1211,7 @@ static void draw_iui_components_window(iui_context *ui, float delta_time) /* Data Table demo */ iui_text_label_small(ui, IUI_ALIGN_LEFT, "DATA TABLE"); static iui_table_state table = {0}; /* user-provided state */ - float col_widths[] = {-1.f, 80.f, 60.f}; + const float col_widths[] = {-1.f, 80.f, 60.f}; iui_table_begin(ui, &table, 3, col_widths); iui_table_header(ui, &table, "Name"); @@ -1773,7 +1773,7 @@ static void fe_draw_char_grid(iui_context *ui, fe_state_t *st) ui->renderer.user); /* Draw character label */ - char label[2] = {(char) ch, '\0'}; + const char label[2] = {(char) ch, '\0'}; float tw = iui_text_width_vec(label, ui->font_height); float tx = cell.x + (cell_w - tw) * 0.5f; float ty = cell.y + (cell_h - ui->font_height) * 0.5f; @@ -1861,7 +1861,7 @@ static void fe_handle_mouse(iui_context *ui, fe_state_t *st) } /* Handle keyboard input for point movement and undo */ -static void fe_handle_keys(iui_context *ui, fe_state_t *st) +static void fe_handle_keys(const iui_context *ui, fe_state_t *st) { int key = ui->key_pressed; if (key == 0) { @@ -2952,7 +2952,7 @@ static const char *dropdown_options[] = {"Option 1", "Option 2", "Option 3", * Clears all show_* flags except the one being activated. * Only used when control panel is active (requires MODULE_BASIC). */ -static void demo_close_other_windows(demo_state_t *state, bool *keep_open) +static void demo_close_other_windows(demo_state_t *state, const bool *keep_open) { #ifdef CONFIG_DEMO_CALCULATOR if (&state->show_calculator != keep_open) @@ -3232,14 +3232,14 @@ static void example_frame(void *arg) #ifdef CONFIG_MODULE_PICKER if (iui_tonal_button(ui, "Date Picker", IUI_ALIGN_CENTER)) { time_t now = time(NULL); - struct tm *t = localtime(&now); + const struct tm *t = localtime(&now); iui_date_picker_show(&state->date_picker, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday); } iui_grid_next(ui); if (iui_tonal_button(ui, "Time Picker", IUI_ALIGN_CENTER)) { time_t now = time(NULL); - struct tm *t = localtime(&now); + const struct tm *t = localtime(&now); iui_time_picker_show(&state->time_picker, t->tm_hour, t->tm_min, false); } #endif diff --git a/tests/forthsalon-demo.c b/tests/forthsalon-demo.c index 86f978d..a67bd8f 100644 --- a/tests/forthsalon-demo.c +++ b/tests/forthsalon-demo.c @@ -202,9 +202,6 @@ typedef struct { int jump_stack[32]; int jump_top; - /* Temp word body being compiled */ - fs_inst_t word_body[FS_MAX_CODE / 4]; - int word_body_len; int defining_word; /* -1 = compiling to main, >=0 = compiling word index */ } fs_compiler_t; @@ -399,7 +396,7 @@ static bool fs_handle_token(fs_compiler_t *c, const char *token) return false; /* unknown word */ } -bool fs_compile(fs_program_t *prog, const char *source) +static bool fs_compile(fs_program_t *prog, const char *source) { fs_compiler_t compiler; fs_compiler_init(&compiler, prog); diff --git a/tests/pixelwall-demo.c b/tests/pixelwall-demo.c index f11abb0..020b1a1 100644 --- a/tests/pixelwall-demo.c +++ b/tests/pixelwall-demo.c @@ -177,7 +177,7 @@ static bool snake_safe(const pw_grid_t *g, pw_pos_t p) !snake_is_worm(g, p); } -static void snake_ai(pw_grid_t *g, SnakeState *st) +static void snake_ai(const pw_grid_t *g, SnakeState *st) { pw_pos_t head = st->worm.body[0].position; pw_pos_t fp = st->fruit_pos; @@ -464,7 +464,11 @@ static bool tetris_piece_cell(int type, int rot, int r, int c) return (bits >> (15 - idx)) & 1; } -static bool tetris_collides(TetrisData *td, int type, int rot, int px, int py) +static bool tetris_collides(const TetrisData *td, + int type, + int rot, + int px, + int py) { for (int r = 0; r < 4; r++) { for (int c = 0; c < 4; c++) { @@ -481,7 +485,7 @@ static bool tetris_collides(TetrisData *td, int type, int rot, int px, int py) return false; } -static int tetris_field_height(TetrisData *td) +static int tetris_field_height(const TetrisData *td) { int h = 0; for (int c = 0; c < FIELD_W; c++) { @@ -497,7 +501,7 @@ static int tetris_field_height(TetrisData *td) return h; } -static int tetris_count_holes(TetrisData *td) +static int tetris_count_holes(const TetrisData *td) { int holes = 0; for (int c = 0; c < FIELD_W; c++) { @@ -512,7 +516,7 @@ static int tetris_count_holes(TetrisData *td) return holes; } -static int tetris_count_complete(TetrisData *td) +static int tetris_count_complete(const TetrisData *td) { int n = 0; for (int r = 0; r < FIELD_H; r++) { @@ -751,7 +755,7 @@ static int invaders_enemy_grid_col(int c) return c * INV_ENEMY_SPACING; } -static int invaders_nearest_col(InvadersData *id) +static int invaders_nearest_col(const InvadersData *id) { int best = -1, best_dist = 100; for (int c = 0; c < INV_ENEMY_COLS; c++) { @@ -1054,8 +1058,8 @@ static void pacman_march_update(pw_grid_t *g, void *data) pd->tick++; if (pd->tick >= PM_SPEED) { pd->tick = 0; - int total = 2 * PM_SPACING + PM_SPRITE_W; if (pd->phase == 0) { + int total = 2 * PM_SPACING + PM_SPRITE_W; pd->x--; if (pd->x + total < 0) pm_start_phase(pd, 1); diff --git a/tests/test-focus.c b/tests/test-focus.c index 45c4763..5bb679d 100644 --- a/tests/test-focus.c +++ b/tests/test-focus.c @@ -13,7 +13,7 @@ static void test_focus_initial_state(void) { TEST(focus_initial_state); void *buffer = malloc(iui_min_memory_size()); - iui_context *ctx = create_test_context(buffer, false); + const iui_context *ctx = create_test_context(buffer, false); ASSERT_NOT_NULL(ctx); /* Initially no widget should have focus */ diff --git a/tests/test-init.c b/tests/test-init.c index 4e82744..bacc852 100644 --- a/tests/test-init.c +++ b/tests/test-init.c @@ -43,7 +43,7 @@ static void test_init_zero_font_height(void) }, }; ASSERT_FALSE(iui_config_is_valid(&config)); - iui_context *ctx = iui_init(&config); + const iui_context *ctx = iui_init(&config); ASSERT_NULL(ctx); free(buffer); PASS(); @@ -65,7 +65,7 @@ static void test_init_negative_font_height(void) }, }; ASSERT_FALSE(iui_config_is_valid(&config)); - iui_context *ctx = iui_init(&config); + const iui_context *ctx = iui_init(&config); ASSERT_NULL(ctx); free(buffer); PASS(); @@ -87,7 +87,7 @@ static void test_init_missing_draw_box(void) }, }; ASSERT_FALSE(iui_config_is_valid(&config)); - iui_context *ctx = iui_init(&config); + const iui_context *ctx = iui_init(&config); ASSERT_NULL(ctx); free(buffer); PASS(); @@ -109,7 +109,7 @@ static void test_init_missing_set_clip(void) }, }; ASSERT_FALSE(iui_config_is_valid(&config)); - iui_context *ctx = iui_init(&config); + const iui_context *ctx = iui_init(&config); ASSERT_NULL(ctx); free(buffer); PASS(); diff --git a/tests/test-layout.c b/tests/test-layout.c index 47bbaa5..4fb8c52 100644 --- a/tests/test-layout.c +++ b/tests/test-layout.c @@ -88,7 +88,7 @@ static void test_window_autosize_expands_to_content(void) /* After frame 1: min_width should be updated to fit content */ /* content (450) + padding*4 (32) = 482, but also consider title min */ - iui_window *w = &ctx->windows[0]; + const iui_window *w = &ctx->windows[0]; ASSERT_TRUE(w->min_width >= 450.0f); /* Should accommodate content */ /* Frame 2: Window should auto-expand */ @@ -121,7 +121,7 @@ static void test_window_autosize_only_grows(void) iui_end_window(ctx); iui_end_frame(ctx); - iui_window *w = &ctx->windows[0]; + const iui_window *w = &ctx->windows[0]; float width_after_frame1 = w->width; /* Frame 2: Window should NOT shrink */ @@ -155,7 +155,7 @@ static void test_window_no_autosize_ignores_content_width(void) iui_end_window(ctx); iui_end_frame(ctx); - iui_window *w = &ctx->windows[0]; + const iui_window *w = &ctx->windows[0]; /* Frame 2: Width should NOT expand (no auto-size) */ iui_begin_frame(ctx, 1.0f / 60.0f); diff --git a/tests/test-modal.c b/tests/test-modal.c index 19b8944..1cdb4dc 100644 --- a/tests/test-modal.c +++ b/tests/test-modal.c @@ -769,7 +769,7 @@ static void test_input_layer_initial_state(void) { TEST(input_layer_initial_state); void *buffer = malloc(iui_min_memory_size()); - iui_context *ctx = create_test_context(buffer, false); + const iui_context *ctx = create_test_context(buffer, false); ASSERT_TRUE(ctx != NULL); /* Initial state: no active layers */ diff --git a/tests/test-slider.c b/tests/test-slider.c index 5f92ce9..24b6ff3 100644 --- a/tests/test-slider.c +++ b/tests/test-slider.c @@ -292,7 +292,7 @@ static void test_slider_click_to_value(void) ASSERT_NOT_NULL(ctx); /* Test that slider renders correctly at different values */ - float test_values[] = {0.f, 25.f, 50.f, 75.f, 100.f}; + const float test_values[] = {0.f, 25.f, 50.f, 75.f, 100.f}; for (int i = 0; i < 5; i++) { iui_begin_frame(ctx, 1.0f / 60.0f); iui_begin_window(ctx, "Test", 0, 0, 400, 300, 0); diff --git a/tests/test-spec.c b/tests/test-spec.c index 3cf08c3..96629ae 100644 --- a/tests/test-spec.c +++ b/tests/test-spec.c @@ -827,7 +827,7 @@ static void test_wcag_contrast_functions(void) static void test_a11y_callbacks_disabled_by_default(void) { TEST(a11y_callbacks_disabled_by_default); - iui_context *ctx = test_init_context(); + const iui_context *ctx = test_init_context(); ASSERT_NOT_NULL(ctx); /* Accessibility should be disabled by default */ ASSERT_FALSE(iui_a11y_enabled(ctx)); diff --git a/tests/test-tracking.c b/tests/test-tracking.c index f108707..59a55e4 100644 --- a/tests/test-tracking.c +++ b/tests/test-tracking.c @@ -116,7 +116,7 @@ static void test_slider_stale_state_cleared(void) iui_update_mouse_pos(ctx, 220.0f, 150.0f); iui_begin_frame(ctx, 1.0f / 60.0f); iui_begin_window(ctx, "Test", 100, 100, 300, 200, 0); - value = iui_slider_ex(ctx, value, 0.0f, 100.0f, 1.0f, NULL); + (void) iui_slider_ex(ctx, value, 0.0f, 100.0f, 1.0f, NULL); iui_end_window(ctx); iui_end_frame(ctx); @@ -646,7 +646,7 @@ static void test_slider_unrendered_clears_active(void) iui_update_mouse_pos(ctx, 220.0f, 150.0f); iui_begin_frame(ctx, 1.0f / 60.0f); iui_begin_window(ctx, "Test", 100, 100, 300, 200, 0); - value = iui_slider_ex(ctx, value, 0.0f, 100.0f, 1.0f, NULL); + (void) iui_slider_ex(ctx, value, 0.0f, 100.0f, 1.0f, NULL); iui_end_window(ctx); iui_end_frame(ctx);